36

文章同步于Github Pines-Cheng/blog

随着前端这几年的风生水起,CSS作为前端的三剑客之一,各种技术方案也是层出不穷。从CSS prepocessor(SASS、LESS、Stylus)到后来的后起之秀 PostCSS,再到 CSS ModulesStyled-Component 等。有人维护了一份完整的 CSS in JS 技术方案的对比,里面已经有将近50种技术方案。CSS Modules就是其中一种。

CSS Modules 介绍

要弄懂CSS Modules是什么,可以先看官方介绍:GitHub – css-modules/css-modules: Documentation about css-modules

通过上面介绍可以看出,CSS Modules既不是官方标准,也不是浏览器的特性,而是在构建步骤(例如使用Webpack或Browserify)中对CSS类名选择器限定作用域的一种方式(通过hash实现类似于命名空间的方法)。例如我们在buttons.js里引入buttons.css文件,并使用.btn的样式,在其他组件里是不会被.btn影响的,除非它也引入了buttons.css.

CSS模块化

JS已经全面实现了模块化,但是css还处于探索阶段。为什么我们需要css模块化?主要由一下几个原因。

CSS全局作用域问题

CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效。现在的前端工程大多是基于组件开发,随着工程的页面数量好复杂度的提升,相信写css的人都会遇到样式冲突(污染)的问题。一般我们会采用一下几种方法:

  • class命名写长一点吧,降低冲突的几率

  • 加个父元素的选择器,限制范围

  • 重新命名个class吧,比较保险

可是以上方案只是降低了全局冲突的概率,并不能彻底解决全局冲突的问题。并且,实现方式也不够优雅,还增加了代码的复杂和冗余。

我们的追求

  • 面向组件开发 : 处理 UI 复杂性的最佳实践就是将 UI 分割成一个个的小组件,React 就鼓励高度组件化和分割。我们希望有一个 CSS 架构去匹配。

  • 沙箱化(Sandboxed) : 如果一个组件的样式会对其他组件产生不必要以及意想不到的影响,那么将 UI 分割成组件并没有什么用。就这方面而言,CSS的全局作用域会给你造成负担。

  • 方便 :不会增加开发的负担和代码的冗余。

方案

CSS 模块化的解决方案有很多,但主要有三类。

CSS 命名约定

规范化CSS的模块化解决方案(比如BEM BEM — Block Element Modifier ,OOCSS,AMCSS,SMACSS,SUITCSS)
但存在以下问题:

  • JS CSS之间依然没有打通变量和选择器等

  • 复杂的命名

CSS in JS

彻底抛弃 CSS,用 JavaScript 写 CSS 规则,并内联样式。styled-components 就是其中代表。styled-components可以让CSS真正意义地写到JS里面,同时让标签更具有语意化,这跟HTML5新标签思想相同;该框架让样式也具备组件化思想,让前端完全面向组件化编程,就像java的包装类型。
但存在以下问题:

  • 样式代码也会出现大量重复。

  • 不能利用成熟的 CSS 预处理器(或后处理器)

使用 JS 来管理样式模块

使用JS编译原生的CSS文件,使其具备模块化的能力,代表是 CSS Modules。

CSS Module还是JS和CSS分离的写法,不会改变大家的书写习惯,CSS Module只需修改构建代码和使用模块依赖引入className的方式即可使用,且支持less和sass的语法,

使用CSS Modules可以让组件className控制权转交给JS,我们不会去关心命名冲突污染等问题,同时可以灵活控制生成的命名,样式代码不用修改即可让使用CSS语法的旧项目零成本接入。

CSS Modules 能最大化地结合现有 CSS 生态(预处理器/后处理器等)和 JS 模块化能力,几乎零学习成本。只要你使用 Webpack,可以在任何项目中使用。是目前最好的 CSS 模块化解决方案。

使用

配置

CSS Modules配置非常简单,如果你使用webpack,只需要在配置文件中改动一行即可。

// webpack.config.js
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
 

加上 modules 即为启用,localIdentName 是设置生成样式的命名规则。

编码

css

/* components/Button.css */
.normal { /* normal 相关的所有样式 */ }
 

js

// components/Button.js
import styles from './Button.css';
console.log(styles);
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

上例中 console 打印styles的结果是:

Object {
  normal: 'button--normal-abc53',
  disabled: 'button--disabled-def886',
}
 

注意到 button--normal-abc53 是 CSS Modules 按照 localIdentName 自动生成的 class 名。其中的 abc53 是按照给定算法生成的序列码。经过这样混淆处理后,class 名基本就是唯一的,大大降低了项目中样式覆盖的几率。同时在生产环境下修改规则,生成更短的 class 名,可以提高 CSS 的压缩率。

CSS Modules 对 CSS 中的 class 名都做了处理,使用对象来保存原 class 和混淆后 class 的对应关系。

React实践

手动引用

import React from 'react';
import styles from './table.css';
 
export default class Table extends React.Component {
    render () {
        return <div className={styles.table}>
            <div className={styles.row}>
            </div>
        </div>;
    }
}
 

渲染结果:

<div class="table__table___32osj">
    <div class="table__row___2w27N">
    </div>
</div>

使用babel-plugin-react-css-modules

babel-plugin-react-css-modules 可以实现使用styleName属性自动加载CSS模块。只需要把className换成styleName即可获得CSS局部作用域的能力,babel插件来自动进行语法树解析并最终生成className。改动成本极小,不会增加JSX的复杂度,也不会给项目带来额外的负担。

import React from 'react';
import styles from './table.css';
 
class Table extends React.Component {
    render () {
        return <div styleName='table'>
        </div>;
    }
}
 
export default Table;
 

CSS Modules 很好的解决了 CSS 目前面临的模块化难题。支持与 CSS处理器搭配使用,能充分利用现有技术积累。如果你的产品中正好遇到类似问题,非常值得一试。

参考


Pines_Cheng
6.5k 声望1.2k 粉丝

不挑食的程序员,关注前端四化建设。