在谈论 CSS Modules 之前,我们先来聊聊 CSS。

浅谈 CSS

Cascading Style Sheets,简写 CSS,中文名称层叠样式表。在 web 页面中,开发者一般使用 HTML 描述页面的内容,使用 CSS 来控制页面的样式。CSS 分为三个等级,CSS1 现已废弃,CSS2.1 是推荐标准,CSS3 分成了多个小模块进行标准化。

CSS 的语法十分简单,由多组“规则”组成。每个规则由“选择器”(selector)、“属性”(property)和“值”(value)组成。

  1. div {
  2. color: red;
  3. }

实际上,CSS 的选择器还有很多,但是这里不做讨论。

CSS 的层叠性与作用域

层叠性

CSS 称为层叠样式表,那么如何解释层叠呢?

  1. div {
  2. color: red;
  3. background: blue;
  4. }
  5. div {
  6. color: purple;
  7. }

我们来试着用 『CSS 是由多个“规则”组成的』这句话来描述上面的 CSS 代码。

  1. 规则一
    1. div 元素中的字体颜色为 red
    2. div 元素中的背景颜色为 blue
  2. 规则二
    1. div 元素的字体背景颜色为 purple

现在出现了一个问题 —— 两个规则出现了冲突。字体的颜色到底应该是 red 还是 blue 呢?如果你了解 CSS 的话,你几乎可以脱口而出,字体的颜色当然是 purple 了。没错,字体的颜色就是 purple ,而这样的现象就体现出了 CSS 的层叠

因为同一个 CSS 规则可以被反复的添加,因此在编写 CSS 的时候会频繁的出现“规则”冲突。为了解决冲突,CSS 引入了一套规则(规则的优先级)去解决冲突,这就是所谓层叠性(cascading)的体现。

作用域

CSS 中所有的规则都是全局作用域。我们通过一个例子来认识 CSS 的作用域。

我们创建一个 demo,文件结构如下:

  1. .
  2. ├── index.html
  3. ├── other.css
  4. └── style.css

文件代码如下:

  1. <!-- index.html -->
  2. <head>
  3. <title>index</title>
  4. <link rel="stylesheet" href="style.css">
  5. <link rel="stylesheet" href="other.css">
  6. </head>
  7. <body>
  8. <h1 class="header">hello</h1>
  9. </body>
  1. /* style.css */
  2. .header {
  3. color: purple;
  4. }
  1. /* other.css */
  2. .header {
  3. color: red;
  4. }

结果如下:
image.png

  1. index.html 引入了两个样式文件:style.cssother.css
  2. style.css 中存在一个规则,类名为 header 的标签,字体颜色为 purple
  3. other.css 中存在一个规则,类名为 header 的标签,字体颜色为 red
  4. 由于 CSS 的作用域是全局的,因此这两个规则冲突了
  5. 根据 CSS 的规则处理冲突,后出现的规则覆盖先出现的规则,因此字体颜色为 red

我们可以直接将上面的代码理解成下面的形式。

  1. <head>
  2. <style>
  3. .header {
  4. color: purple;
  5. }
  6. .header {
  7. color: red;
  8. }
  9. </style>
  10. </head>
  11. <body>
  12. <h1 class="header">hello</h1>
  13. </body>

因为 CSS 的全局作用域,因此 style.cssother.css 两个文件中的样式实际上被提升到了一起。

CSS 的问题

成也萧何,败也萧何,CSS 的层叠性和全局作用域是一把双刃剑,既方便了开发者,又限制住了开发者。

随着项目的增大,CSS 的类名将会变得越来越不可控,很有可能会出现重复的类名,而全局作用域则会将所有的 CSS 类名提升到一起。

在大型项目中,开发者很有可能会使用到一样的选择器(类名),而由于 CSS 的层叠性和全局作用域,样式便很有可能在开发者不知情的情况下发生冲突。

BEM(Block Element Modifier)

为了解决命名的冲突,以及更好的语义化,“可怜的”前端开发者发明了一种编写 CSS 的规范。

这里给出几个链接,给感兴趣的朋友

救世主 CSS Modules

任何时候我们改变一个 CSS 文件,我们都需要小心翼翼地考虑全局环境是否产生冲突。没有其他前端技术是需要如此之多的规范和约束,而这仅仅是为了保持最低级别的可维护性。

编写可维护的 CSS 代码是值得提倡的,但不是通过谨慎地遵守一个命名约定,而是在开发的过程中通过独立的封装。

——全局 CSS 的终结(狗带)[译] AlloyTeam

上面的两段话,体现出了 CSS Modules 存在的价值。通过 CSS Modules 我们把开发的重心放在编写上,而不是绞尽脑汁的想如何遵守规范以及不去造成全局样式冲突。

CSS 的规则都是全局的,所以任何一个组件的样式规则,都对整个页面有效。

而产生局部作用域的唯一方法,就是使用一个独一无二的类名,不会与其他选择器重名。这就是 CSS Modules 的做法。

为了做到独一无二的 class 的名字,CSS Modules 配合 webpack 会生成一个 hash value,并让 hash value 作为类名。

下面,我们来玩一玩 CSS Modules,并且来看一看 CSS Modules 是如何做到的。

玩一玩 CSS Modules

CSS Modules 并不是一个正式的声明或者是浏览器的一个实现,CSS Modules 需要通过一些构建工具去进行构建。

我们通过阮一峰的编写的 CSS Modules 教程来先玩一玩 CSS Modules。阮一峰为这份教程编写了 6 个 demo 来学习 CSS Modules。

这篇教程只是通过该仓库中的代码学习 CSS Modules,避免去配置 webpack。

克隆代码仓库

  1. git clone https://github.com/ruanyf/css-modules-demos.git

安装依赖

  1. cd css-modules-demos
  2. npm install

运行项目

  1. npm run demo01

我们直接关注 demo01

  1. .
  2. ├── components
  3. ├── App.css
  4. └── App.js
  5. ├── index.html
  6. ├── index.js
  7. └── webpack.config.js

App.jsApp.css 文件

  1. import React from 'react';
  2. import style from './App.css';
  3. export default () => {
  4. return (
  5. <h1 className={style.title}>
  6. Hello World
  7. </h1>
  8. );
  9. };
  1. .title {
  2. color: red;
  3. }

可以看到,我们在使用 CSS modules 编写 CSS 代码,和我们直接编写 CSS 代码没什么区别,甚至在这个例子中是一模一样的。

但是在 App.js 中写 className 的时候,我们使用的是 style.title 而不是 字符串 title

  1. import React from 'react';
  2. import style from './App.css';
  3. export default () => {
  4. // 在 App.js 中添加下面这行
  5. console.log(style);
  6. ...
  7. };

我们将 import 进来的 style 打印出来
image.png
我们可以看到,style 是一个对象,其中 keyApp.css 中的类名,value 为一串哈希字符串。

那么 <h1 className={style.title}> 中的 className 的值就应该是那一串哈希字符串。
image.png
现在我们知道了,className 的值,实际上是一段 hash 值。

下面我们用图来厘清 CSS Modules 的逻辑。
image.png

  1. webpack 和 CSS Modules 会生成一个 hash value
  2. 该 hash value 指向的是 App.css 中 类名为 title 的样式代码
  3. 生成一个对象,记录 title 和 hash value 的对应关系
  4. App.js 中引入该对象,通过 title 获取 hash value,最后得到对应的 CSS 代码。

更多的关于 CSS Modules 的用法可以参考阮一峰的教程

最后我们来看一下,CSS Modules 官网的解释。

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

CSS Modules 是一个 CSS 文件,其中所有的类名和动画名默认都在本地作用域内。

CSS Modules 被编译成一种叫做 ICSS 或者 Interoperable CSS 的低级交换格式(low-level interchange format),但 CSS Modules 的编写方式与普通的CSS文件一样。

  1. /* style.css */
  2. .className {
  3. color: green;
  4. }

当我们在 JS Module 中导入 CSS Module 的时候,CSS Modules 会导出一个对象,该对象包含着所有从本地类名到全局类名的映射。

  1. import styles from "./style.css";
  2. // import { className } from "./style.css";
  3. element.innerHTML = '<div class="' + styles.className + '">';

CSS Modules 解决了什么问题

提供了模块化和可复用的 CSS

  • 不会有更多的冲突
  • 显示的依赖关系
  • 无全局作用域

总结

由于 CSS 的全局作用域和层叠性,开发者很有可能会因为编写了重复的类名而导致样式冲突。为了解决这个问题,开发者提出了 BEM,通过严格遵守命名规则保证全局上类名不重复。

严格遵守 BEM 的成本是比较高的,随着构建工具的出现,CSS Modules 实现了 CSS 的局部作用域。

CSS Modules 通过生成一个独一无二的哈希值作为类名来保证全局上的类名不重复,从而实现了局部作用域。

参考