CSS Module
什么是 CSS Module?
CSS Modules 既不是官方标准,也不是浏览器的特性,而是在构建步骤(例如使用 Webpack 或 Vite)中对 CSS 类名和选择器限定作用域的一种方式(类似于命名空间)。
目的:解决 CSS 中全局作用域的问题
CSS Modules 是一种 CSS 文件的模块化解决方案。 它的主要思想是将 CSS 文件作为一个模块,通过模块化的方式来管理 CSS 文件,避免全局污染,同时还能够实现 CSS 文件的复用。
开启 CSS Module
一般只需将 CSS 文件的后缀名改为 .module.css
即可使用 CSS Modules。
在 React 中默认开启了 CSS Module,样式表文件需要以 xxx.module.sass/less/css 命名
我们也可以通过配置 webpack 来开启 CSS Module
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
loader: "css-loader",
options: {
modules: true,
localIdentName: "[name]_[local]__[hash:base64:5]"
}
}
]
}
};
localIdentName 可以定义生产的哈希类名,默认是 [hash:base64]
详细配置见: css-loader
localIdentName选项的占位符有:
[name] 源文件名称 (样式文件的文件名)
[folder] 文件夹相对于 compiler.context 或者 modules.localIdentContext 配置项的相对路径。
[path] 源文件相对于 compiler.context 或者 modules.localIdentContext 配置项的相对路径。
[file] - 文件名和路径。
[ext] - 文件拓展名。
[hash] - 字符串的哈希值。基于 localIdentHashSalt、localIdentHashFunction、localIdentHashDigest、localIdentHashDigestLength、localIdentContext、resourcePath 和 exportName 生成。
[<hashFunction>:hash:<hashDigest>:<hashDigestLength>] - 带有哈希设置的哈希。
[local] - 原始类名。
局部作用域
没有 CSS Module 的组件样式 默认 CSS 的规则是全局生效的,任何一个组件下的 CSS 样式,都会影响其他组件中使用相同类名的地方。
注意:
对于本地类名,我们推荐使用 camelCase。
虽然没有强制执行,但是 camelCase 是首选的,因为当尝试使用点符号访问 styles.class-name 时,kebab-case 可能会导致意外行为。 您仍然可以使用括号符号(例如 style [‘ class-name’]
)来处理 kebab-case,但是首选的是 styles.className。
CSS Module 是怎么局部作用 CSS 样式的?
答案:产生局部作用域的唯一方法,就是使用一个独一无二的 class 的名字,不会与其他选择器重名。这就是 CSS Modules 的做法。
这里就拿 React 项目来进行解释
在 React 中,默认是开启 CSS Module 的。但是对于样式表文件的命名一个约束。需要以.module.less/css/sass结尾
全局作用域(:global)
通常在我们的日常开发中有这种场景: 我们有一个自己的组件,但是这个组件使用了一些第三方的组件库,对于我们使用的第三方组件我们又想修改一下它的样式。
需要不对第三方组件的类名进行哈希,保留原始类名,才能起到样式覆盖的作用:global
:global(.className)那么此时这个 className 即使是在组件的样式表中定义的也不会被添加 hash 值, 所以就可以影响全局所有类名为 className 的样式
注意:
此时组件中对该类的样式修改会影响全局所有使用该类名的地方,所以为了将样式修改限制到本组件, 一般推荐将:global 使用在组件自定义类名范围下,然后添加这个自定义类名到组件中
class 的组合
在 CSS Modules 中,一个选择器可以继承另一个选择器的规则,这称为"组合"。
可以有多个合成规则,但合成规则必须在其他规则之前。 扩展仅对局部范围的选择器有效,并且只有当选择器是单个类名时才有效。 当一个类名组成另一个类名时,CSS 模块将导出本地类的两个类名。这可能会累加到多个类名。 也可以使用组合来组合多个类 composes: classNameA classNameB
;
在 Header.module.css 中,让.title 继承.back 。
Header.module.css
.back {
background-color: blue;
}
.title {
composes: back;
color: green;
}
Header.js
import styles from "./Header.module.css";
export default function Header() {
return <h2 className={styles.title}>Header 组件</h2>;
}
编译后 CSS
._src_Header_module__back {
background-color: blue;
}
._src_Header_module__title {
color: green;
}
HTML
<h2 class="_src_Header_module__title _src_Header_module__back">Header 组件</h2>
注意:
当在使用composes组合语句时,类上将附加上伪类选择器
在下面的例子中,otherClassName也将被赋予定义在className上的:hover伪类。
.className {
color: green;
}
.className:hover {
color: red;
}
.otherClassName {
composes: className;
background: black;
}
otherClassName above is the same as defining:
.otherClassName {
color: green;
background: black;
}
.otherClassName:hover {
color: red;
}
继承其他模块(Class复用)
选择器也可以继承其他 CSS 文件里面的规则。
other.module.css
.other {
background-color: chartreuse;
}
Header.module.css
.title {
composes: other from "./other.module.css";
color: green;
}
注意:
导入的类名需要和被导入文件中的类名相同
编译之后的效果和 composes 同一个文件中的 class 效果相同
组合不应该形成循环依赖。 否则,规则的属性是否覆盖组合规则的属性是未定义的。 模块系统可能会发出一个错误
变量
CSS Modules 支持使用变量,不过需要安装 PostCSS 和 postcss-modules-values
安装
npm install --save postcss-loader postcss-modules-values
webpack.config.js
{
loader: "postcss-loader",
"options": {
plugins: [
require('postcss-modules-values'),
]
}
}
使用
@value color: #8A469B;
.header {
background: color;
}
.footer {
// 类组合
composes: header;
color: #FFF;
}
推荐原生的css变量
在ts中同样使用,
因为在这里我们用的是TypeScript,所以可以用typings-for-css-modules-loader这个包, 这个包也可以替代css-loader的功能,此外这个包还能根据.scss文件里面的类名自动生成对应的.d.ts文件:
npm install -D typings-for-css-modules-loader
配置webpack 这个配置接非常简单了,因为要用typings-for-css-modules-loader替代css-loader的功能,所以直接替换即可,将前面sass的配置修改为如下:
配置后在页面中引入,引入可能会提示找不到该模块,出现这个问题的原因是:
因为.scss文件中并没有类似export这样的关键词用于导出一个模块,所以也就导致报错找不到模块,这个问题需要通过ts的模块声明解决。 (declare module)
发现是ts的模块机制,所以需要声明模块,创建一个*.d.ts的文件
style为自定义名,随便取
declare module '*.css' {
const style: any;
export default style;
}
这样就不会报错了
CSS Modules 使用技巧
CSS Modules 是对现有的 CSS 做减法。为了追求简单可控,作者建议遵循如下原则:
不使用选择器,只使用 class 名来定义样式 不层叠多个 class,只使用一个 class 把所有样式定义好 不嵌套 使用 composes
组合来实现复用
如果我在 style 文件中使用了 id 选择器,伪类,标签选择器等呢?
没问题,所有这些选择器将不被转换,原封不动的出现在编译后的 css 中。也就是说 CSS Modules 只会转换 class 名相关样式
css Module里less里嵌套css,使用的时候,style.xxx不用嵌套吗?
在CSS Modules中,使用Less编译器编译并转换成模块化CSS后,你不需要在JavaScript中通过style.xxx
的方式进行嵌套引用。当你在Less文件中使用嵌套语法编写样式时,编译过程会自动将这些嵌套转换为扁平化的类名,并且生成一个映射对象。
例如,在Less文件(假设为styles.module.less
)中:
.container {
color: red;
.item {
background-color: blue;
}
}
编译后,对应的CSS Modules JavaScript对象可能是这样的:
import styles from './styles.module.less';
// styles对象可能类似于:
{
container: 'container__unique-hash',
item:'container__unique-hash-item__unique-hash'
}
然后在React组件或其他JavaScript代码中引用时,直接使用生成的类名即可:
<div className={styles.container}>
<div className={styles.item}>This is an item</div>
</div>
注意这里的styles.item
并不是styles.container.item
,因为在实际应用中,CSS Modules已经将Less中的嵌套结构扁平化了。