我在项目中如何使用 Sass
背景
本文主要与大家分享,自己在项目中如何使用 sass 一些心得总结。
本文仅个人理解,可能存在对有些概念表述不当。
1. 使用 dart-sass 代替 node-sass
之前项目一直都使用 node-sass,但有一个让人头痛的点是:只要切换 node 版本,运行必报错 🤣。所以果断进行切换,主要 dart-sass 是官方推荐,提供了 dart-sass JS 编译版本 dart-sass(JS),方便集成。另外,dart-sass 将作为 SASS 开发团队的未来主要开发方向。而 node-sass 不再做新功能的版本迭代,会被逐渐边缘化。虽然 dart-sass(JS) 编译速度不如 node-sass,但表现上可以忽略不计。如果追求编译速度,可以直接使用 dart-sass,编译速度贼快 😀。
singsong: 本文中 dart-sass(JS) 表示 dart-sass JS 编译版本。项目中使用也是 dart-sass(JS)。
与 node-sass 区别
- dart-sass(JS) : dart-sass JS 编译版本。
- node-sass:node 调用 C++ 编写的 libsass SASS 编译器来编译 sass,依赖于 nodejs 版本。
2. 使用 Partials
加速编译
在创建 sass 样式文件时,命名以 _
开头,会创建一个 sass partial 文件。如 _partial.scss
,这样会让 sass 编译器知道该文件是个 partial,不应该输出 css 文件。如下是项目中的样式结构:
-
样式结构
src/styles ├── base │ ├── _animate.scss │ └── _reset.scss ├── lib │ └── _iconfont.scss ├── sass │ ├── _config.scss │ ├── _function.scss │ ├── _helper.scss │ ├── _index.scss │ ├── _layout.scss │ ├── _minxin.scss │ └── _theme.scss └── styles.scss
-
组件样式结构
├── Home │ ├── Home.scss │ └── Home.tsx ├── User │ ├── User.scss │ ├── User.tsx │ └── UserSkeleton.tsx └── index.tsx
singsong: 为什么组件样式没有采用 Partials,主要因为组件样式是一个独立结构。
3. 使用 @use
与 @forward
组合代替 @import
更符合模块化组织方式
The Sass team discourages the continued use of the @import rule. Sass will gradually phase it out over the next few years, and eventually remove it from the language entirely. Prefer the @use rule instead. (Note that only Dart Sass currently supports @use. Users of other implementations must use the @import rule instead.)
Sass 团队不鼓励继续使用 @import
。 Sass 将在未来几年逐步淘汰它,并最终将其从语言中完全删除。主推 @use
。主要因为 @import
存在如下问题:
@import
破坏模块化组织方式。因为通过@import
导入variables
、mixins
、和functions
都会变成全局可访问的。由于是全局的,还可能会导致命名冲突错误。@extend
规则也是全局的,这使得很难预测将扩展哪些样式规则。@import
也是一个 CSS 特性,两者之间的区别可能会让人感到困惑。@import
允许多次导入同一个样式文件,这样不仅会影响编译速度,而且可能会产生一些冗余输出。- 无法定义下游样式表无法访问的私有成员或占位符选择器
新版模块系统及 @use
很好地解决了上述问题。如果想迁移到 @use
,可以使用官方提供的迁移工具
@use
-
namespace(命名空间)避免全局导入。导入的成员都会挂载到 namespace:
<namespace>.<variable>
,<namespace>.<function>()
, 或@include <namespace>.<mixin>()
等。默认,模块的命名空间是其 URL 结尾去掉文件扩展名(和前缀"_"
符)的部分。如src/_corners.scss
,命名空间为corners
。singsong: 简单描述,就是文件名😄
// src/_corners.scss $radius: 3px; @mixin rounded { border-radius: $radius; } // style.scss 引用 src/_corners.scss @use "src/corners"; .button { // 导入的成员会挂载在 `corners` @include corners.rounded; padding: 5px + corners.$radius; }
当然,有时也存在定制命名空间需求,语法为
@use "<url>" as <namespace>
// src/_corners.scss $radius: 3px; @mixin rounded { border-radius: $radius; } // style.scss 引用 src/_corners.scss @use "src/corners" as c; // 定制命名空间为 "c" .button { @include c.rounded; padding: 5px + c.$radius; }
如果引用时,觉得书写
namespace
麻烦,可以将成员挂载到全局上。此时与@import
导入引用等效。语法:@use "<url>" as *
。该种方式需要注意命名冲突。// src/_corners.scss $radius: 3px; @mixin rounded { border-radius: $radius; } // style.scss 引用 src/_corners.scss @use "src/corners" as *; // 无命名空间 .button { @include rounded; // 直接引用 padding: 5px + c.$radius; }
-
私有成员
Sass 提供了基于模块的私有成员,该成员只能在定义的文件内可访问。导出到其他引入样式文件是不可访问的。成员名以
"_"
、"-"
开头的成员:_memberName
、-memberName
// src/_corners.scss $-radius: 3px; @mixin rounded { border-radius: $-radius; } // style.scss @use "src/corners"; .button { @include corners.rounded; // This is an error! $-radius isn't visible outside of `_corners.scss`. padding: 5px + corners.$-radius; }
-
配置模块
在定义变量时,结尾添加
!default
,可让其变成可配置变量。在引入可配置模块时,使用@use <url> with (<variable>: <value>, <variable>: <value>)
语法引用。指定的配置值会覆盖掉默认值。// _library.scss 添加 "!default" 指定为可配置变量 $black: #000 !default; $border-radius: 0.25rem !default; $box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default; code { border-radius: $border-radius; box-shadow: $box-shadow; } // style.scss 指定可配置变量的值 @use 'library' with ( $black: #222, $border-radius: 0.1rem );
-
使用内置模块。sass 提供了
math
,color
,string
,list
,map
,selector
, andmeta
等内置模块。在使用这些模块时,必须先导入。@use 'sass:map'; @use 'sass:string'; $map-get: map.get( ( 'key': 'value', ), 'key' ); $str-index: string.index('string', 'i');
-
@use
加载解析- 不需明确加载模块的后缀,sass 会自动解析。如
@use "variables"
,会依次查找variables.scss
,variables.sass
, 或variables.css
。 - sass 是按 url 解析模块不是按文件路径。分隔符只存在
"/"
- sass 总是相对于当前目录进行查找,如果没有查找到,才按照 Load Paths 指定路径继续查找。
- 在导入 Partials,可以省掉前缀
"_"
。
- 不需明确加载模块的后缀,sass 会自动解析。如
@forward
在将一个大文件分割到不同子文件中时,如果要重组,可使用 @forward
进行转发重组。假如现在有一些表格相关的样式表,希望将这些样式表的成员导入到同一个 namespace 下。
-
重组样式
forms/_index.scss
// forms/_index.scss @forward 'input'; @forward 'textarea'; @forward 'select'; @forward 'buttons';
-
引用,导入同一个 namespace 下
// styles.scss @use 'forms'; // forms 命名空间
默认情况下,所有非私有成员都会被转发。我们还可以通过 show
、hide
关键字做进一步定制
// 只转发 "input" 模块下名为 `border()` mixin 和 `$border-color` 变量
@forward 'input' show border, $border-color;
// 转发 "buttons" 模块下除 `gradient()` function 外的所有成员
@forward 'buttons' hide gradient;
如果将多个不同的样式文件转发的到同一个 namespace 下,可能存在命名冲突的可能,此时可以通过指定前缀的方式解决 @forward "<url>" as <prefix>-*
:
// forms/_index.scss
// 假如 "input" 与 "buttons" 模块同时存在 `background()` mixin
@forward 'input' as input-*;
@forward 'buttons' as btn-*;
// style.scss
@use 'forms';
@include forms.input-background();
@include forms.btn-background();
4. 基于 webpack 注入全局样式
项目中样式结构:
src/styles
├── base
│ ├── _animate.scss
│ └── _reset.scss
├── lib
│ └── _iconfont.scss
├── sass
│ ├── _config.scss
│ ├── _function.scss
│ ├── _helper.scss
│ ├── _index.scss
│ ├── _layout.scss
│ ├── _minxin.scss
│ └── _theme.scss
└── styles.scss
其中 sass 目录下的样式都是 variables
、mixins
、和 functions
等一些常用的样式。并使用 @forward
转发重组到 sass/_index.scss
:
singsong: 这些样式文件不会生成 css 代码,都是些变量、辅助函数等。
// common styles
@forward './helper';
@forward './function';
@forward './config';
@forward './minxin';
@forward './theme';
@forward './layout';
在 react 组件中会常用引用这些样式。但引用前,必须先导入。感觉这样很麻烦!查阅一番,了解到 sass-loader
提供了 additionalData
可以解决这个麻烦。
singsong:
sass-loader
v9.0.0 以前使用的是prependData
属性。
additionalData
Type: String|Function Default: undefined
Prepends Sass/SCSS code before the actual entry file. In this case, the sass-loader will not override the data option but just prepend the entry's content.
大意:sass-loader 会根据
additionalData
选项,会在每个加载的 sass 文件之前添加additionalData
的值。
知道解决方法,就可以对 create-react-app
生成的开发环境做调整了。
-
eject
的情况(直接修改配置文件):const getStyleLoaders = (cssOptions, preProcessor) => { const loaders = [ /* ..... */ ].filter(Boolean); if (preProcessor) { loaders.push( { loader: require.resolve('resolve-url-loader'), options: { sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, root: paths.appSrc, }, }, { loader: require.resolve(preProcessor), options: { sourceMap: true, additionalData: '@import "~@/styles/sass/index";', }, } ); } return loaders; };
-
非
eject
的情况(基于react-app-rewired
修改配置):module.exports function override(config, env) { const SASS_RULES_LEN = config.module.rules.length; const SASS_ONEOF_LEN = config.module.rules[SASS_RULES_LEN - 1].oneOf.length; // 为什么需要处理两项,因为有一项是对 CSS Module 的处理 // 不同 webpack 版本,这里配置不太一样,请按实际情况自行配置。 config.module.rules[SASS_RULES_LEN - 1].oneOf[SASS_ONEOF_LEN - 2].use[4].options.additionalData = '@use "~@/styles/sass/index" as *;'; config.module.rules[SASS_RULES_LEN - 1].oneOf[SASS_ONEOF_LEN - 3].use[4].options.additionalData = '@use "~@/styles/sass/index" as *;'; return config; }
配置完,就可以愉快地在
src
目录下的任何地方使用了 sass 目录下任何声明了 😀。
5. stylelint 检测样式代码
项目中引入 stylelint 确保 sass 样式的规范。
相关包
- stylelint
- stylelint-config-recommended
- stylelint-scss
- stylelint-config-property-sort-order-smacss,更多关于 stylelint-order。
配合 vscode,pre-commit,可让开发流程更加规范。
参考文章
转载自:https://juejin.cn/post/7137234474290380813