CSS Modules 详解及应用
在最近的React项目中,遇到了CSS处理的问题:
- 由于是多人开发,各自对样式类的命名规则不统一
- 样式全局有效,产生了样式覆盖,不得不重新定义样式类或者使用 !important;
平时的项目开发中,还会有一些类似的CSS问题:
- 全局污染
- 命名混乱
- 依赖引入复杂
- 无法共享变量
- 代码冗余
使用CSS模块化可以很好的解决上述问题。目前主要分为两类:
一类是彻底抛弃 CSS,使用 JS 或 JSON 来写样式。Radium, jsxstyle ,react-style 属于这一类。优点是能给 CSS 提供 JS 同样强大的模块化能力;缺点是不能利用成熟的 CSS 预处理器(或后处理器) Sass/Less/PostCSS, :hover 和 :active 伪类处理起来复杂。
另一类是依旧使用 CSS,但使用 JS 来管理样式依赖,代表是CSS Modules。CSS Modules 能最大化地结合现有 CSS 生态和 JS 模块化能力。发布时依旧编译出单独的 JS 和 CSS。它并不依赖于 React,只要你使用 Webpack,可以在 Vue/Angular/jQuery 中使用。
相关概念

CSS Modules是什么
- CSS Modules 是使所有的类名和animation 动画名称默认为局部样式的CSS文件,它既不是官方标准也不是浏览器特性,而是在构建过程中,对CSS类名限定作用域的一种方法。
原理
- CSS Modules 产生局部作用域的原理是将每个类名编译成独一无二的哈希串
CSS Modules 仅作用于class和id选择器 对其他选择器无效
使用CSS Modules 有什么优势
- 所有样式都是 local 的,解决了命名冲突和全局污染问题
- class 名生成规则配置灵活,可以此来压缩 class 名
- 可以与CSS预处理器、后处理器配合使用
- 只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS
- 依然是 CSS,几乎 0 学习成本...
启用方法
CSS Modules 通过webpack配置引入项目,不依赖于任何框架,只要使用webpack配置后就可以用于React/Vue/Angular/jQuery 项目中.
- 在webpack.config.js的module中添加如下配置:
module.exports = {
entry: __dirname + '/index.js',
output: {
publicPath: '/',
filename: './bundle.js'
},
module: {
rules: [
...
{
test: /\.css$/,
use:[
{loader:'style-loader'},
{
loader:'css-loader',
option:{
modules:true
}
}
]
},
]
}
}
上面代码中,关键的是在css-loader的option里配置option:{modules:true},表示打开 CSS Modules 功能。
使用这种配置方式,css-loader默认将类名编译为唯一的hash串,但不利于class类名的语义化,如:
/*header.css*/
.root {
text-align: center;
}
.header {
background-color: #536587;
height: 150px;
padding: 20px;
color: white;
}
.title {
font-size: 1.5em;
}
/*header.js*/
import React from 'react';
import styles from './header.css';
const Header =()=>{
return (
<div className={styles.root}>
<header className={styles.header}>
<h1 className={styles.title}>Welcome to React</h1>
</header>
</div>
)
}
export default Header;
<h1/>将被编译为:<h1 class="_1yHZnBWcll0vdb7BCk5Ufm">Welcome to React</h1>
同时 style.css文件将被编译为
._1yHZnBWcll0vdb7BCk5Ufm {
font-size: 1.5em;
}
因此,需要在配置webpack时多做一点,定制编译生成的哈希类名:
rules:[
{
test:/.css$/,
use:[
{loader:'style-loader'},
{loader:'css-loader',
option:{modules:true,localIdentName:'[path][name]__[local]-[hase:base64:5]'}
]
}
]
配置localIdentName之后 上面的类名将生成 如下格式:

CSS Modules的使用
-
局部变量和全局变量
- 局部变量 默认
:local 构建时按照 localIdentName 做规则处理
- 全局变量 需要声明全局变量时,使用
:global,全局变量,编译后类名不变
.normal {
color: green;
}
/* 以上与下面等价 */
:local(.normal) {
color: green;
}
/* 定义全局样式 */
:global(.btn) {
color: red;
}
/* 定义多个全局样式 */
:global {
.link {
color: green;
}
.box {
color: yellow;
}
}
复用内部样式
.userInfoBox {
position: relative;
padding: 0 24px 0 32px;
height: 40px;
color: white;
line-height: 40px;
background: #1f1f1f;
transition: background .2s ease 0s;
}
.docDownloadBox {
composes: userInfoBox;
margin-right: 10px;
border-right: 1px solid #444;
}
使用docDownloadBox类的HTML
<a className={styles.docDownloadBox}>
文档&下载
</a>
编译为两个class

复用外部样式
/* settings.css */
.primary-color {
color: #f40;
}
/* components/Button.css */
.base { /* 所有通用的样式 */ }
.primary {
composes: base;
composes: primary-color from './settings.css';
/* primary 其它样式 */
}
对于大多数项目,有了 composes 后已经不再需要 Sass/Less/PostCSS。但如果你想用的话,由于 composes 不是标准的 CSS 语法,编译时会报错。就只能使用预处理器自己的语法来做样式复用了。
配合classnames使用
npm install --save classnames
直接使用
/* components/submit-button.js */
import React, { Component } from 'react';
import classNames from 'classnames';
import styles from './submit-button.css';
export default class SubmitButton extends Component {
render () {
let text = this.props.store.submissionInProgress ? 'Processing...' : 'Submit';
let className = classNames({
[`${styles.base}`]: true,
[`${styles.inProgress}`]: this.props.store.submissionInProgress,
[`${styles.error}`]: this.props.store.errorOccurred,
[`${styles.disabled}`]: this.props.form.valid,
});
return <button className={className}>{text}</button>;
}
};
使用 classnames/bind
/* components/submit-button.js */
import React, { Component } from 'react';
import classNames from 'classnames/bind';
import styles from './submit-button.css';
let cx = classNames.bind(styles);
export default class SubmitButton extends Component {
render () {
let text = this.props.store.submissionInProgress ? 'Processing...' : 'Submit';
let className = cx({
base: true,
inProgress: this.props.store.submissionInProgress,
error: this.props.store.errorOccurred,
disabled: this.props.form.valid,
});
return <button className={className}>{text}</button>;
}
};
CSS Moduels 本身没有变量的概念,如果需要使用变量,要借助预处理器/后处理器。
定义变量
安装 PostCSS 和 postcss-icss-values。
npm install --save postcss-loader postcss-icss-values
把postcss-loader加入webpack.config.js。
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules:true,
localIdentName:'[name]--[local]--[hash:base64:5]'
}
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
require('postcss-icss-values')
],
},
},
],
},
然后,定义变量,如variable.css
@value primary #0c77f8;
@value green #aaf200;
变量和值之间 写不写冒号都可以,但如果和sass配合使用,不要写分号
一次只定义一个变量
在其他css文件中使用变量,
App.css中引入变量
@value variables: "./variable.css";
@value primary from variables;
//或者直接 @value primary from "variables.css"
.title {
background-color: primary;
}
css和js共享变量
//variable.css
@value primary #0c77f8;
//App.js
import { primary } from './variable.css';
console.log(primary); // -> #0c77f8
css变量在js中只读
在webpack.conf.js中添加exclude/include配置,定义不同的解析规则即可
// webpack.config.js 局部
{
test: /\.css$/,
exclude:/common.css/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules:true,
localIdentName:'[name]--[local]--[hash:base64:5]'
}
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
require('postcss-icss-values')
],
},
},
],
},
{
test:/\.css$/,
include:/common.css/,
use:['style-loader','css-loader','postcss-loader']
},
设置为全局规则的css类,不会被模块化处理。
-
使用babel-plugin-react-css-modules
babel-plugin-react-css-modules 可以实现使用styleName属性自动加载CSS模块。只需要把className换成styleName即可获得CSS局部作用域的能力,babel插件来自动进行语法树解析并最终生成className。改动成本极小,不会增加JSX的复杂度,也不会给项目带来额外的负担。
import React from 'react';
import './table.css';
export default class Table extends React.Component {
render () {
return <div styleName='table'>
<div styleName='row'>
<div styleName='cell'>A0</div>
<div styleName='cell'>B0</div>
</div>
</div>;
}
export default Table;
使用babel-plugin-react-css-modules:
- 不必使用驼峰命名;
- 不用使用styles Object
- 可以和全局变量自由组合;
<div className='global-css' styleName='local-module'></div>
参考文献:
- CSS Modules 详解及 React 中实践
- CSS Modules 用法教程 - 阮一峰的网络日志
- CSS Modules 入门及 React 中实践 | AlloyTeam
- GitHub - css-modules/css-modules: Documentation about css-modules
- GitHub - css-modules/postcss-icss-values: Pass arbitrary constants between your module files
- GitHub - gajus/babel-plugin-react-css-modules: Transforms styleName to className using compile time CSS module resolution.
CSS Modules 详解及应用
在最近的React项目中,遇到了CSS处理的问题:
平时的项目开发中,还会有一些类似的CSS问题:
使用CSS模块化可以很好的解决上述问题。目前主要分为两类:
相关概念
CSS Modules是什么
原理
使用CSS Modules 有什么优势
启用方法
CSS Modules 通过webpack配置引入项目,不依赖于任何框架,只要使用webpack配置后就可以用于React/Vue/Angular/jQuery 项目中.
上面代码中,关键的是在
css-loader的option里配置option:{modules:true},表示打开 CSS Modules 功能。使用这种配置方式,css-loader默认将类名编译为唯一的hash串,但不利于class类名的语义化,如:
<h1/>将被编译为:<h1 class="_1yHZnBWcll0vdb7BCk5Ufm">Welcome to React</h1>同时 style.css文件将被编译为
因此,需要在配置webpack时多做一点,定制编译生成的哈希类名:
配置localIdentName之后 上面的类名将生成 如下格式:
CSS Modules的使用
局部变量和全局变量
:local构建时按照localIdentName做规则处理:global,全局变量,编译后类名不变Compose 组合Class
对于样式复用,CSS Modules 只提供了唯一的方式来处理:
composes组合复用内部样式
使用docDownloadBox类的HTML
编译为两个class

复用外部样式
层叠多个class
配合
classnames使用npm install --save classnames直接使用
使用 classnames/bind
定义和使用变量
CSS Moduels 本身没有变量的概念,如果需要使用变量,要借助预处理器/后处理器。
定义变量
安装 PostCSS 和 postcss-icss-values。
把postcss-loader加入webpack.config.js。
然后,定义变量,如
variable.css变量和值之间 写不写冒号都可以,但如果和sass配合使用,不要写分号
一次只定义一个变量
在其他css文件中使用变量,
App.css中引入变量css和js共享变量
css变量在js中只读
模块化样式和全局样式共存
在
webpack.conf.js中添加exclude/include配置,定义不同的解析规则即可设置为全局规则的css类,不会被模块化处理。
使用babel-plugin-react-css-modules
使用
babel-plugin-react-css-modules:<div className='global-css' styleName='local-module'></div>参考文献: