css 算是前端发展的一个痛点吧. 以前是裸写css, 接着到后来的 inline css, 然后是, scss/sass. 从单一 file 到可以模块化书写css. 不过, 随着前端发展, 现在的要求是组件化, 那么, 以前那种直接 < link> 脚本也逐渐变为痛点. so, 2015/6 左右, 出来了 css-module 这个概念. 可以说, 这是一个专门为 component 编程而生的. 因为,他是和 js 紧密结合在一起的.
css-module 更具官方的说法就是: css-module 其实就是原来的 css 文件。只是他的 className 和 animation 最后都会被编译为 object 形式的映射对象.
举个简单的例子就是:

// 未编译前的 style.css
.title {
  background-color: red;
}

// 通过js 脚本调用
import styles from "./styles.css";

element.innerHTML = 
  `<h1 class="${styles.title}">
    demo
   </h1>`;

// 最后实际输出为:
# css 格式
._styles__title_309571057 {
  background-color: red;
}
# HTML 格式
<h1 class="_styles__title_309571057">
  demo
</h1>

上面简单的介绍了, css-module 到底是啥?
Ps: 上面漏了一点, 怎么将css 编译嘞? 这里,方法很多,有webpack, gulp等等. 看同学们自己的选择了

命名方式

首先,在 css-module 里面. 以前在 css 中的命名方式,都变得 nonsense. 在 css-module 推荐一切命名方式使用 camelCase 的形式. 因为, 在写组件的时候我们并不是来写全局的样式, 我们仅仅只是来完成我们当前组件的渲染. 所以, 这就要求, 这些组件所需和隐藏的 css 属性, 我们必须一个不拉的全部写上去, 比如: display, font, text-align 等等.

// 写一个 btn 的样式
# 原始的css
.btn-normal{...}

# 使用 css-module
# 实际该文件的保存名就是 btn-style.css
.normal{...}
.clickStyle{...}

组合 composition

既然, 上文已经说了 css-module 里面每个 style 都必须全部写出所需的样式, 那么, 这样重复的工作实在太多的... 谁 TM 还和你来什么 module 不 module 的. 所以, 为了解决这样的痛点, css-module 提供了 composes 这个概念. 相当于, 就是我们以前 css 中的嵌套.

// 原始 css
# 直接嵌套
.button{...}
.button .normal{...}

// 在 css-module 中
# 使用 composes 进行嵌套
.button{...}
.normal{
    composes:button;
    ...
}

这个 composes 概念,就有点和 sass 中的 @extends 类似. 但,他俩编译的结果点都不像. composes 是直接在 DOM 渲染时, 添加不同的 class. 而 @extends 只是将 class 变为嵌套而已.

// @extends 语法
.Button--common { ... }
.Button--normal {
  @extends .Button--common;
    ...
}

// 编译结果
.Button--common, .Button--normal {...}
.Button--normal {...}

但,这样并不符合 css-module 实际编译后改变 className 的 feature, 并且, @extends 的结果, 会造成很大的 className 冗余.
具体说一下 css-module composes 的过程.

// 正常书写 css-module
.common { ...}
.normal { composes: common; ... }

// 经过编译得出
# 注意,这里并没有存在嵌套的情况,每个className 都是分离的.
.components_submit_button__common__abc5436 { ... }
.components_submit_button__normal__def6547 { ... }

// 通过 import 的 css 暴露的接口为:
styles: {
  common: "components_submit_button__common__abc5436",
  normal: "components_submit_button__common__abc5436 components_submit_button__normal__def6547",
}

// 渲染出来的HTML DOM 节点内容
# 添加 style.normal 样式
<button class="components_submit_button__common__abc5436 
               components_submit_button__normal__def6547">
  Submit
</button>

当然, composes 也可以引入其他 css 文件中的某个 class.

// colors.css
.primary {
  color: #720;
}

// button.css
.normal {
  composes: primary from "../shared/colors.css";
}

另外, 写好一个 css-module 有很多原则可以遵循的. 最出名的应该就是 单一职责原则.

单一职责

因为 composes 的限制, 我们一般只能引入单一的 className 去包裹我们想要的 style 样式. 但这样,感觉有点浪费. 这点,感觉 sass 做的还是挺棒的.

@mixin subtle-shadow {
  box-shadow: 0 0 4px -2px;
}
// 直接通过 mixin 引进
.some_element {
  @include subtle-shadow;
}

所以, 为了在 css-module 达到同样的目的. 我们一般只能使用单一的文件去包裹,所需的样式内容. 像下面的 demo:

// 直接从其他文件中引进
.element {
  composes: large from "./typography.css";
}

具体实例

css-module 主要是和 react 一起使用. 因为, react 存在, 才使前端组件化得到蓬勃的发展. so, 我们这里,就需要借助 webpack 对 import 的 css 文件进行编译.
这里就不多说了, 直接把配置放出来吧.

// 引入所需的插件
var ExtractTextPlugin = require('extract-text-webpack-plugin');


# 下面就是具体的 module.exports 里面的内容
 module: {
 // 关键语句. 处理 css-module的内容.
    loaders: [
      { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader') }
    ]
  },

  postcss: [
    require('autoprefixer-core'),
    require('postcss-color-rebeccapurple')
  ],

  resolve: {
    modulesDirectories: ['node_modules', 'components']
  },

  plugins: [
    new ExtractTextPlugin('style.css', { allChunks: true })
  ]

如果,想更快的了解的话, 那就直接去线上 demo 看吧.


villainhr
7.8k 声望2.2k 粉丝