该篇为翻译文章,原文地址为:What are CSS Modules and why do we need them?

我最近一直对CSS Modules比较感兴趣。如果你还没有听说过CSS Modules,那么你可以看看这篇文章。我们将把它放在项目当中来看一下它到底有什么样的作用。如果你对此也很感兴趣,那么后期还会有其它想关文章更新,这篇文章只是一个简单的介绍。
该系列文章目录:

第一部分: CSS Modules是什么东西,我们为什么需要它? (当前)

第二部分: 如何使用CSS Modules-译文(原文)

第三部分: CSS Modules和React-译文(原文)

什么是CSS Modules?

根据官方的这篇介绍,CSS modules也是一些CSS文件,但默认情况下这些CSS文件里的一些class名和动画名等都只在文件本身内起作用(生成局部作用域)。

所以,CSS Modules并不是一个官方发布的一个东西,也不是浏览器内部进行实现的。而是我们在利用一些工具(例如Webpack或Browserify)把文件当中的class名和选择器进行一个”命名空间化”的操作过程。

说了这么多,那么它到底是什么样子的呢?并且为什么要这么做呢?在解答这个问题之前,我们来看下平时是怎么写HTML和CSS的:
首先是一段HTML:

<h1 class="title">An example heading</h1>

然后是CSS:

.title {
  background-color: red;
}

只要HTML文件应用了这个CSS文件,那么<h1>标签的背景色就会变成红色。我们不需要处理CSS和HTML文件。因为浏览器可以识别这两种文件。

CSS Modules 是采用了另一种方法来代替以前编写HTML的方式。现在我们需要把所有的HTML结构写到一个JS文件当中,比如index.js。下面我们通过一段代码来预览一下(后面将会有更实际的例子):

import styles from "./styles.css";

element.innerHTML = 
  `</div></h1><h1 class="${styles.title}">
     An example heading
   </h1>`;

在我们构建步骤当中,编译器将将会搜索我们所导入的styles.css文件,然后在我们的JS当中,通过找到styles.title会把.title根据之前的styles.css给解析出来。然后构建工具会同时处理这些东西,把它们生成一个新的HTML和CSS文件,在新的文件当中,我们之前的class名和选择器名将会被一个新的名字所替换掉。

Our generated HTML might look like this:
例如,生成的HTML可能是这样的:

<h1 class="_styles__title_309571057">
  An example heading
</h1>

生成的CSS可能是这样的:

._styles__title_309571057 {
  background-color: red;
}

devtools

之前的class属性和选择器.title全都被这个新的字符串替换掉了。

Hugo Giraudel在他的Understanding the CSS Modules Methodology一文章当中里说了这样一句总结性的话:

class名和选择器名是由构建工具动态生成的,它们是惟一的。

这就是我们前面说的样式只在当前文件内起作用(生成局部作用域)。它们被区域化成一些特定的模板。假设我们有一个buttons.css文件,此时我们只在buttons.js文件当中引入这个css文件,那么假设这个css里的.btnclass名就只会在buttons.js当中起作用,在其它模板文件当中是找不到的(例如在forms.js),除非我们在forms.js当中也引入了buttons.css文件。

但你可能会问:为什么要这么做呢?为什么要把CSS和HTML搞的混在一起呢?

为什么要使用CSS Modules?

其实使用CSS Modules最大的一个好处就是实现组件化(我们引入的所有样式都用在当前的这个组件上)。

我们可以在一个组件上引入多个样式文件:

import buttons from "./buttons.css";
import padding from "./padding.css";

element.innerHTML = `<div class="${buttons.red} ${padding.large}">`;

这么设计也是为了用来解决原本在CSS当中的全局作用域(其实也可以说是命名冲突)带来的问题的。

有时时候,你可能会花比较多的时间来考虑如何避免全名冲突问题。
有时候,你会在需要的时候在你的CSS文件底部添加一些零碎的样式,然后想着以后再去整理这些样式代码,但后来却越加越多,最终放弃治疗…
有时候,你可能会在几个样式文件当中来回修改,可你也不确定它们各自的功能界限,或者有的文件里的大部分内容是用不到的。
有时候,你可能想试着删除一些样式,但发现不好删,因为总有一些地方会用到这些样式,或者样式之间是有依赖关系的。

上面这些”有时候”会让我们很头疼,这些问题会使我们的项目变的非常臃肿而且很乱。

但使用了CSS Modules后,它的局部作用域的概念就解决了上面所说的这些问题。你只需要写出你要的效果就可以了,不需要考虑其它问题。

例如你现在在项目当中使用了CSS Modules,此时你在CSS当中定义了一个random-gross-class类,然后在HTML当中引用了这个类,但是它是不起作用的,因为最终的类名会被解析成一个类似于._style_random-gross-class_0038089这么一串东西。

composes关键字

现在,我们有一个type.css模块是单独用来设置文字样式的。文件里面的内容为:

.serif-font {
  font-family: Georgia, serif;
}

.display {
  composes: serif-font;
  font-size: 30px;
  line-height: 35px;
}

我们现在在模板当中用到上面的样式:

import type from "./type.css";

element.innerHTML = 
  `<h1 class="${type.display}">
    This is a heading
  </h1>`;

最终生成的HTML结构将会是这样的:

<h1 class="_type__display_0980340 _type__serif_404840">
  Heading title
</h1>

可以看到,两个class样式都被加了上去,因为我们这里使用了composes关键字,这样就避免会发生像在Sass中使用@extend带来的问题

我们甚至可以使用composes来使用另外一个CSS文件里的class:

.element {
  composes: dark-red from "./colors.css";
  font-size: 30px;
  line-height: 1.2;
}

不需要使用BEM

当我们在使用CSS Modules的时候,是不需要考虑BEM的。

译者:
简单来说,BEM是把样式按功能分成了三类-Block, Element, Modifier。例如:

/* Block component */
.btn {}

/* Element that depends upon the block */ 
.btn__price {}

/* Modifier that changes the style of the block */
.btn--orange {} 
.btn--big {}

更多关于BEM的知识,可以看一下这两篇文章:
BEM的定义
BEM 101

不需要考虑BEM的原因有两个:

  • 易解析-像type.display这样的代码会很容易很清晰的被解析成类似于.font-size__serif--large这种。现在,貌似生成的class名更加BEM化,选择器的名字更长的,更像是我们想要的那种。

  • 本地作用域-假设,当前有一个CSS模块(文件)当中有一个.big的class,该class的作用是改变font-size。同时在另外一个CSS模块当中,也有一个.big,而这个.big则同时改变了paddingfont-size。不用担心,它们之间是不冲突的。因为它们都有自己的作用域。即使我们在一个模板当中将这两个CSS模块都引入了也没关系,编译器会最终会给它们取成不同的名字。

是不是很牛B?上面说的只是CSS Modules其中一部分好处而已。
如果你还要了解CSS Modules的其它的一些牛B的地方, Glen Madden的这篇CSS Modules-Welcome to the Future会帮到你。
下面一篇文章当中,我们将介绍如何在项目当中通过Webpack来使用CSS Modules,还将会使用到一些ES2015中的新技术。

扩展阅读