CSS Modules 用法教程

作者: 阮一峰

日期: 2016年6月10日

学过网页开发就会知道,CSS 不能算编程语言,只是网页样式的一种描述方法。

为了让 CSS 也能适用软件工程方法,程序员想了各种办法,让它变得像一门编程语言。从最早的Less、SASS,到后来的 PostCSS,再到最近的 CSS in JS,都是为了解决这个问题。

本文介绍的 CSS Modules 有所不同。它不是将 CSS 改造成编程语言,而是功能很单纯,只加入了局部作用域和模块依赖,这恰恰是网页组件最急需的功能。

因此,CSS Modules 很容易学,因为它的规则少,同时又非常有用,可以保证某个组件的样式,不会影响到其他组件。

零、示例库

我为这个教程写了一个示例库,包含六个Demo。通过它们,你可以轻松学会CSS Modules。

首先,克隆示例库。


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

然后,安装依赖。


$ cd css-modules-demos
$ npm install

接着,就可以运行第一个示例了。


$ npm run demo01

打开浏览器,访问http://localhost:8080,查看结果。其他示例的运行方法类似。

一、局部作用域

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

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

下面是一个React组件App.js


import React from 'react';
import style from './App.css';

export default () => {
  return (
    <h1 className={style.title}>
      Hello World
    </h1>
  );
};

上面代码中,我们将样式文件App.css输入到style对象,然后引用style.title代表一个class


.title {
  color: red;
}

构建工具会将类名style.title编译成一个哈希字符串。


<h1 class="_3zyde4l1yATCOkgn-DBWEL">
  Hello World
</h1>

App.css也会同时被编译。


._3zyde4l1yATCOkgn-DBWEL {
  color: red;
}

这样一来,这个类名就变成独一无二了,只对App组件有效。

CSS Modules 提供各种插件,支持不同的构建工具。本文使用的是 Webpack 的css-loader插件,因为它对 CSS Modules 的支持最好,而且很容易使用。顺便说一下,如果你想学 Webpack,可以阅读我的教程Webpack-Demos

下面是这个示例的webpack.config.js


module.exports = {
  entry: __dirname + '/index.js',
  output: {
    publicPath: '/',
    filename: './bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: {
          presets: ['es2015', 'stage-0', 'react']
        }
      },
      {
        test: /\.css$/,
        loader: "style-loader!css-loader?modules"
      },
    ]
  }
};

上面代码中,关键的一行是style-loader!css-loader?modules,它在css-loader后面加了一个查询参数modules,表示打开 CSS Modules 功能。

现在,运行这个Demo。


$ npm run demo01

打开 http://localhost:8080 ,可以看到结果h1标题显示为红色。

二、全局作用域

CSS Modules 允许使用:global(.className)的语法,声明一个全局规则。凡是这样声明的class,都不会被编译成哈希字符串。

App.css加入一个全局class


.title {
  color: red;
}

:global(.title) {
  color: green;
}

App.js使用普通的class的写法,就会引用全局class


import React from 'react';
import styles from './App.css';

export default () => {
  return (
    <h1 className="title">
      Hello World
    </h1>
  );
};

运行这个示例。


$ npm run demo02

打开 http://localhost:8080,应该会看到h1标题显示为绿色。

CSS Modules 还提供一种显式的局部作用域语法:local(.className),等同于.className,所以上面的App.css也可以写成下面这样。


:local(.title) {
  color: red;
}

:global(.title) {
  color: green;
}

三、定制哈希类名

css-loader默认的哈希算法是[hash:base64],这会将.title编译成._3zyde4l1yATCOkgn-DBWEL这样的字符串。

webpack.config.js里面可以定制哈希字符串的格式。


module: {
  loaders: [
    // ...
    {
      test: /\.css$/,
      loader: "style-loader!css-loader?modules&localIdentName=[path][name]---[local]---[hash:base64:5]"
    },
  ]
}

运行这个示例。


$ npm run demo03

你会发现.title被编译成了demo03-components-App---title---GpMto

四、 Class 的组合

在 CSS Modules 中,一个选择器可以继承另一个选择器的规则,这称为"组合"("composition")。

App.css中,让.title继承.className


.className {
  background-color: blue;
}

.title {
  composes: className;
  color: red;
}

App.js不用修改。


import React from 'react';
import style from './App.css';

export default () => {
  return (
    <h1 className={style.title}>
      Hello World
    </h1>
  );
};

运行这个示例。


$ npm run demo04

打开http://localhost:8080,会看到红色的h1在蓝色的背景上。

App.css编译成下面的代码。


._2DHwuiHWMnKTOYG45T0x34 {
  color: red;
}

._10B-buq6_BEOTOl9urIjf8 {
  background-color: blue;
}

相应地, h1class也会编译成<h1 class="_2DHwuiHWMnKTOYG45T0x34 _10B-buq6_BEOTOl9urIjf8">

五、输入其他模块

选择器也可以继承其他CSS文件里面的规则。

another.css


.className {
  background-color: blue;
}

App.css可以继承another.css里面的规则。


.title {
  composes: className from './another.css';
  color: red;
}

运行这个示例。


$ npm run demo05

打开http://localhost:8080,会看到蓝色的背景上有一个红色的h1

六、输入变量

CSS Modules 支持使用变量,不过需要安装 PostCSS 和 postcss-modules-values


$ npm install --save postcss-loader postcss-modules-values

postcss-loader加入webpack.config.js


var values = require('postcss-modules-values');

module.exports = {
  entry: __dirname + '/index.js',
  output: {
    publicPath: '/',
    filename: './bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: {
          presets: ['es2015', 'stage-0', 'react']
        }
      },
      {
        test: /\.css$/,
        loader: "style-loader!css-loader?modules!postcss-loader"
      },
    ]
  },
  postcss: [
    values
  ]
};

接着,在colors.css里面定义变量。


@value blue: #0c77f8;
@value red: #ff0000;
@value green: #aaf200;

App.css可以引用这些变量。


@value colors: "./colors.css";
@value blue, red, green from colors;

.title {
  color: red;
  background-color: blue;
}

运行这个示例。


$ npm run demo06

打开http://localhost:8080,会看到蓝色的背景上有一个红色的h1

(完)

留言(67条)

阮大师真是高产

shadow dom可以实现样式隔离

谢谢分享,又学习了 ^_^

勤奋啊!说转技术就转技术了,厉害!

刚刚学习完 CSS Module,想写个博文记录一下,不知从哪里开始写,把想法表达清楚真是一种能力,必须要理解透彻才行。佩服阮老师。

阮老师高产似母猪.
说实话,看阮老师的blog,工资涨得好快.

学习了,感谢阮先生。明天去实验一下,说不定正好可以以此解决一个固有问题(同套js[vue+webpack实现]逻辑下,因需求不同衍生出不同themes的多版本activity)。

相比 postcss,使用 less 或 sass 结合 module 更好用

阮大师我的学习目标。

用这个会对浏览器调试有影响吧

阮老师,请问有了CSS Module,是不是不需要Sass了。或者说,有结合使用Sass的必要吗?

请问css modules 可以用于web端网站吗?

看了您的博客,突然发觉自己以前的思维太狭隘了,现在真的有一种站在巨人肩膀上的感觉。

在react项目中,我一个标签需要用到多个样式时 转换成

在react项目中,我一个标签需要用到多个样式时 <div className="title1 title2 title3"> 转换成 <div className={style.title1} 这种形式,怎么写?

引用Alvin的发言:

在react项目中,我一个标签需要用到多个样式时 <div className="title1 title2 title3">转换成 <div className={style.title1} 这种形式,怎么写?

后来我想到一个办法,这样 :className={[style.title1,style.title2,style.title3].join(" ")}

哈希之后,代码后期维护会变得很困难,找个类半天

引用Alvin的发言:

后来我想到一个办法,这样 :className={[style.title1,style.title2,style.title3].join(" ")}

用 classnames 包

base64 转换 .title 结果不是 ._3zyde4l1yATCOkgn-DBWEL,应该还加入了其他信息。

感觉不是很好用呀,后期调试,名称不统一。

到目前为止看到的各种技术都只是在工程化方向发展而无本质的进化~~~偷懒而已,还美其名曰“提高效率,节约成本”

引用zhaozhiming的发言:

用 classnames 包

直接用es6的 `${}` 链接就好了


`${style.title1} ${style.title2}`

可以在非 React 中使用么

请问怎么在global下面 生成sass的全局变量 和函数呢??

请问react-css-modules是不是有兼容问题
在android微信里react-css-modules写法页面打不开

阮老师,css modules算是一种主流的做法吗

好好的css给糟蹋成什么样子了 —— 乱七八糟。

less和sass应该怎么做到局部作用域?

class是不是只能用驼峰的写法?能用className={style.pass-bar}的写法吗?貌似会报错,求解决办法

想请问一下 css modules 有没有 sublime 的语法插件?针对 :global{} 多层嵌套的颜色高亮有点反常

引用菜鸟的发言:

好好的css给糟蹋成什么样子了 —— 乱七八糟。

这并不是糟蹋,你只是没去用,这些东西都是非常好的处理细节,当项目非常大,这些东西的威力就会体现出来

很给力 一遍就看懂了 3Q !

扶我起来^_^

原来dva里面使用的就是这种方式,以前在css都没用到这些方法

全局css样式表也会被处理,这个怎么解决

@keyframes 怎么用:local

请问引入cssmodule这个时候再引入第三方UI库就会导致第三方UI库也被改成哈希类名,但是第三方库的js却没有变,导致样式显示不出

引用Alvin的发言:

后来我想到一个办法,这样 :className={[style.title1,style.title2,style.title3].join(" ")}

用 classnames 这个包也可以

按照阮教授这么解释css modules的用法,我是不是可以在项目里定义一个全局的.css文件(例如app.css),里面全部都是:global。然后在每个js里都引用一下(目的是确保统一的公共样式,以便以后要更换公共样式的时候,我只需要修改app.css就可以了)。然后在引一个属于自己的.css文件(用来编写:local的样式)。

阮老师你好,请问这个css modules能直接定义html标签吗?就是不使用class定义样式

阮老师您好,我有一个问题:在开发的过程中,使用到css-modules来加载css,但是现在我们的配置是对自己写的样式采用css-modules,而node_modules里采用全局引入,现在我的一个全局引入的样式覆盖了其他人引入的全局样式;我想问,有没有一种方法--我把node_modules里的css引入到我自定义的css文件中,变成局部样式,且引入的样式不会使用css-modules来进行hash转义?请阮老师指教,谢谢[/抱拳]

less怎么配置css modules?

请问各位大哥大姐CSSModule转换了keyframes动画名怎么破,:global 也没用啊。

引用chjiyun的发言:

请问各位大哥大姐CSSModule转换了keyframes动画名怎么破,:global 也没用啊。

同求解

请问如果是用create-react-app, loader: "style-loader!css-loader?modules" 应该添加到哪里呢?

引用eell的发言:

请问如果是用create-react-app,loader: "style-loader!css-loader?modules" 应该添加到哪里呢?

需要把webpack打包出来

引用chjiyun的发言:

请问各位大哥大姐CSSModule转换了keyframes动画名怎么破,:global 也没用啊。

@keyframes :global(blockly-shake) {
20% {
transform: translate(-1rem, 0) scale(0.75);
}
40% {
transform: translate(1rem, 0) scale(0.75);
}
60% {
transform: translate(-1rem) scale(0.75);
}
80% {
transform: translate(1rem, 0) scale(0.75);
}
0%, 100% {
transform: translate(0, 0) scale(0.75);
}
}

引用NicholasNC的发言:

阮老师,请问有了CSS Module,是不是不需要Sass了。或者说,有结合使用Sass的必要吗?

sass 个 css module 根本就是两个东西,你没理解清楚吧,一个是预处理器,一个是做模块化。

在dva中,将一个less文件文件作为变量使用,在该文件中引入图片,使用相对定位引入不到,该less文件引入到其他less文件中使用,这样编译的话就没办法找到图片,想问下,怎么引入该图片

css的模块化

使用less或sass,处理诸如变量这样的特性很方便,另外还可以在less/sass中使用:global处理全局作用域,个人感觉这样配合使用比较好。

阮老师,这个css-loader配置modules:true就支持变量了,这是为啥呢,不用postcss插件了

options: {
modules: true, // 指定启用css modules
localIdentName: '[name]__[local]--[hash:base64:5]', // 指定css的类名格式
},
配置之后,css确实可以实现样式隔离,但是 scss 如何做到这一点,可以请教一下吗?

精简,讲到了最有用的部分????

引用Alvin的发言:

后来我想到一个办法,这样 :className={[style.title1,style.title2,style.title3].join(" ")}


用 classnames 这个npm包

引用彭大葱的发言:

在dva中,将一个less文件文件作为变量使用,在该文件中引入图片,使用相对定位引入不到,该less文件引入到其他less文件中使用,这样编译的话就没办法找到图片,想问下,怎么引入该图片

遇到了类似的问题,想问下你有解决么?是什么原因?

第六条输入变量,webpack4版本下已经不需要安装postcss-loader postcss-modules-values这两个包,
相应也不需要写相关配置,内置css-loader已经支持变量,和前几条用一个配置写法就好。ps.踩完坑分享..

想问一下,使用了css-module之后,怎么修改引用的ui组件里面的样式

引用Bessic小蟹子的发言:

想问一下,使用了css-module之后,怎么修改引用的ui组件里面的样式

单独弄一个样式文件重写组件样式,包括主题样式尺寸都可以的!

引用Alvin的发言:

后来我想到一个办法,这样 :className={[style.title1,style.title2,style.title3].join(" ")}

现在好像有classNames这个库可以用。

谢谢这篇文章

可以用id嗎?還是只可以用class?

引用Max的发言:

可以用id嗎?還是只可以用class?

2020了还用id写样式?

写的太好了,感谢

:global(.title) {
color: green;
}

:local(.title) {
color: blue;
}

:local不生效

> npm run demo01
会报:Error: Unknown option '--content-base' 的错误
然后把 package.json 中的 --content-base 给去掉,再启动。
> npm run demo01
会报 [webpack-cli] Failed to load '/home/luke/test/css-modules-demos/demo01/webpack.config.js' config 的错误。

这个该怎么调整?

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接