CSS Modules学习笔记
CSS Modules是一个开源项目,它是一个简单的CSS模块化规范,主要完成两件事情:
- 样式类名、动画名的作用域支持。这可以避免命名冲突
- 模块化支持,允许CSS文件之间的依赖关系
与Less、SASS、PostCSS不同,CM并不尝试把CSS变得像一门编程语言(比如支持控制结构、变量),它仅仅解决模块化的基本问题——作用域和模块依赖。
使用CM时所有 url(...) 和 import 所操作的URL均为模块请求格式(module request format):
- ./xxx和 ../xxx 这样的URL表示想对路径
- xxx和 xxx/yyy 这样的URL表示目标位于模块目录(例如node_modules)内部
使用CM时,样式类名、动画名默认仅具有局部作用域。CM会把CSS文件编译为一个低级别ICSS(Interoperable CSS)格式,不过你编写的时候仍然使用普通CSS语法:
1 2 3 4 |
/* style.css */ .className { color: green; } |
ICSS会导出一个对象,其键是局部名称(即你在CSS中声明的样式类名),其值则是编译后的全局名称。全局名称正是运行时使用的真实CSS样式类名,默认情况下全局名称是依据局部名称生成的哈希串。
从JS模块引入一个CSS模块时,你自然获得上述导出对象,可以通过键引用CSS类名:
1 2 3 4 5 6 7 |
// 导入全部映射 import styles from "./style.css"; // 导入需要的映射 import { className } from "./style.css"; // 基于键引用全局样式类名 element.innerHTML = '<div class="' + styles.className + '">'; |
CM建议局部名称一律使用驼峰式大小写,单这不是必须的。
使用特殊伪类 :global 可以声明一个全局作用域:
1 2 3 4 5 6 |
:global(.className) { color: green; } @keyframes :global(animeName){ } |
全局名称不会被编译成哈希串,在JS中可以直接使用:
1 |
element.innerHTML = '<div class="className">'; |
尽管通常情况下没有必要,你可以显式的声明局部作用域:
1 2 3 |
:local(.className) { color: green; } |
CM支持让一个选择器compose另一个选择器定义的样式规则,并称其为组合(Composition)。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.className { color: green; background: red; } .otherClassName { composes: className; color: yellow; } /* 编译结果 */ .global_name_className{ color: green; background: red; } .global_name_otherClassName{ color: yellow; } |
引用otherClassName的JS代码,会被编译为分别引用上面两个选择器的形式:
1 2 3 |
`<div class="${otherClassName}">` <!-- 编译结果 --> <div class="global_name_className global_name_otherClassName"> |
注意:
- 你可以在单个composes规则中指定多个目标类名,例如 composes: classNameA classNameB;
- 你可以指定多次composes规则,但是必须位于其它规则的前面
- 组合仅仅支持局部选择器,并且选择器必须是单个样式类名
使用组合时,你可以compose来自其它CSS模块的样式类:
1 2 3 4 |
.title { composes: className from './another.css'; color: red; } |
注意: compose其它CSS文件中定义的样式类时,其应用顺序是不确定的,因此你不能假设同名规则的覆盖情况。
CM为流行的构建工具提供了插件支持。使用Webpack时,你可以通过css-loader来支持CM。配置示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Webpack 1.x配置 module.exports = { module: { loaders: [ { test: /\.css$/, // modules参数导致css-loader工作在module模式下 loader: "style-loader!css-loader?modules" // 你可以使用localIdentName参数来定制全局名生成规则,默认规则为[hash:base64] loader: "style-loader!css-loader?modules&localIdentName=[path][name]-[local]-[hash:base64:5]" }, ] } }; |
当工作在modules模式下时,css-loader会把所有局部样式类名编译成唯一的全局名。
CM产生的ICSS文件与SASS/SCSS/LESS之类的CSS预处理器是兼容的。你可以把预处理器的loader添加到加载器链中:
1 2 3 4 5 6 7 8 9 10 |
{ test: /\.scss$/, loaders: [ // 注意链条是从右向左(从下向上)执行的 'style', 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]', 'resolve-url', 'sass' ] } |
在开发React应用程序时,可以考虑使用PostCSS插件React CSS Modules来代替CM,其优势是:
- 不要求你使用驼峰式大小写的类名。使用CM时,Webpack的css-loader强制要求驼峰式大小写
- 不需要在代码中到处引用styles对象中的属性
- 全局CSS与CSS模块很容易区分:
12// RCS使用className引用全局CSS类名,styleName引用局部CSS类名<div className='global-css' styleName='local-module'></div> - 如果styleName属性引用一个未定义的CSS模块,你可以得到一个警告而非错误
RCS扩展了目标React组件的render方法,依据输出元素上的styleName属性的值来寻找styles对象中的CSS模块,然后把找到的全局样式类名附加到元素的className后面。
这意味着,组件必须要被RCS装饰。
在开发环境下,你可能希望启用Sourcemaps和热模块替换(Hot Module Replacement) 。加载器style-loader已经支持HMR,因此HMR是开箱即用的。
参考下面的内容配置加载器:
1 2 3 4 5 6 7 8 |
// 需要预先安装style-loader、css-loader { test: /\.css$/, loaders: [ 'style?sourceMap', 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]' ] } |
在生产环境下,你可能希望把CSS块合并到单个文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 需要预先安装style-loader、css-loader // extract-text-webpack-plugin 用于把CSS块合并到单个文件 { module: { loaders: [ // ExtractTextPlugin v2x { test: /\.css$/, loader: ExtractTextPlugin.extract( { notExtractLoader: 'style-loader', loader: 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]!resolve-url!postcss', } ), } ] }, plugins: [ new ExtractTextPlugin( { filename: 'app.css', allChunks: true } ) ] } |
要使用RCM,你的React组件必须被CSSModules装饰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import React from 'react'; import CSSModules from 'react-css-modules'; import styles from './table.css'; // 可以使用ES7装饰器语法 @CSSModules(styles, options) class Table extends React.Component { render() { return <div styleName='table'> <div styleName='row'> <div styleName='cell'>A0</div> </div> </div>; } } // 必须装饰React组件,否则无法使用styleName属性 export default CSSModules( Table, styles, options); |
其中options支持以下选项:
选项 | 说明 | ||
allowMultiple | 默认false。是否允许多个CSS模块名,如果设置为false,以下代码会导致错误:
|
||
errorWhenNotFound | 默认true。如果styleName指定了无法在styles对象中找到的CSS模块,是否报错 |
被装饰的React组件,会获得 this.props.styles 属性,它的值与CSSModules调用的第2参数是一个对象:
1 2 3 4 5 |
<div> // 这两种写法等价 <p styleName='foo'></p> <p className={this.props.styles.foo}></p> </div>; |
默认的,你不能在子组件中的输出元素中使用styleName属性,因为子组件没有被CSSModules装饰。你可以:
- 使用装饰过的子组件
- 使用从被装饰过的父组件中继承得到的 this.props.styles
Leave a Reply