一、我的问题是什么?

  在使用 ant design 的项目中,使用了 Icon 组件的自定义图标,我们把自定义图标的样式放在了 global 中,这样在一个项目里面也不会有问题。但是我们的项目是几个项目组合起来的,就出现了问题:每个项目中都定义了图标的字体,并且都是在 global 中定义的,这样就出现了字体的相互覆盖,展示出现了问题。

  针对上述问题,我们给出的解决方案就是使用 CSS 模块化代替 global ,避免全局污染,所以下面介绍一下 CSS 模块化的相关问题。

二、css modules 能解决什么问题?

1、CSS 作用域问题
  CSS 的规则都是全局的,任何一个组件的样式规则,都对整个页面有效。这样就产生了上述规则覆盖的问题,这种问题之前一般通过 !important这种丑陋的方式解决,这种方法不但丑陋并且也不是一个万能的方法,比如上述问题就不能通过这种方法解决。

2、JSCSS 共享变量问题
  复杂组件要使用 JSCSS 来共同处理样式,就会造成有些变量在 JSCSS 中冗余,Sass/PostCSS/CSS 等都不提供跨 JSCSS 共享变量这种能力。

3、组件真正实现模块化的问题
  我们平时开发一个可复用组件,CSS 也会在全局起作用,需要复用的组件间也会互相影响,这样开发的就不是一个真正意义上的可独立使用的组件。

三、项目中的实践

  既然 CSS 模块化可以解决这么多问题,那么在我们的项目中具体怎么使用呢?
下面的介绍基于 react 项目并且 webpack 4.0 进行打包

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: 'css-loader',
        options: {
          modules: true,
        },
      },
    ],
  },
};

  在 webpack 中设置了 modules: true 就表示项目中的 .css 文件启用了 CSS 模块化。
然后再业务代码中需要手动引入 css 文件的 class 名:

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

export default class Index extends React.Component {
  render () {
    return <div className={styles.wrapper}>
      <div className={styles.btn}>
        Button
      </div>
    </div>;
  }
}

  渲染出来的组件代码:

<div class="-h95cTYFwBAAIlzF-qcc4">
  <div class="_2WlqvlnhuY4sYgg2YppNvQ">
    Button
  </div>
</div>

  通过我们介绍的 css modules解决了全局污染的问题,但是还是没能解决我之前遇到的问题,这是为什么呢?

  经过仔细查看发现虽然我们使用了 CSS 模块化,可是每个项目中都使用了一样的产生 hash 值的方法,上面也介绍了我们项目是几个项目组合起来的,所以产生的样式的名字也是一样的,加入A项目中的弹窗在B项目的页面上运行,产生的样式名一样,A的弹窗就使用了 B的样式,就出现了问题,这个问题该怎样解决呢?这边就需要使用css-loader的一个属性localIdentName,该属性用来设置生成的样式名字,为了区分不同项目的样式我们只需在该属性中加入项目名即可,代码如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: 'css-loader',
        options: {
          modules: true,
          localIdentName: appName + '__[name]__[local]--[hash:base64:5]',
        },
      },
    ],
  },
};

  也可以通过属性 getLocalIdent 来指定样式名字,具体可参考文档。

  但是我一直有个疑问样式名字如果设置很长是不是增加 css 文件的大小,带着这个疑问做了一些实验,主要就是看看 uglifyjs-webpack-plugin 插件能不能按照一定规则进行压缩,结论是并没有。 uglifyjs-webpack-plugin 插件内部使用的是cssnano,这边我们使用cssnano-cli进行了测试。

  我先使用了如下代码进行压缩:

.main__index__iconStyle--KRCN4 {
  font-size: 16px;
}

.main__icon__iconfont--AHO8L {
  color: red;
}

.main__icon__icon-yichang--1cej0 {
  text-align: center;
}

  得到的结果为:

.main__index__iconStyle--KRCN4{font-size:16px}.main__icon__iconfont--AHO8L{color:red}.main__icon__icon-yichang--1cej0{text-align:center}

  然后,我又测试了下面的代码:

.index__iconStyle--KRCN4 {
  font-size: 16px;
}

.icon__iconfont--AHO8L {
  color: red;
}

.icon__icon-yichang--1cej0 {
  text-align: center;
}

  得到的结果为:

.index__iconStyle--KRCN4{font-size:16px}.icon__iconfont--AHO8L{color:red}.icon__icon-yichang--1cej0{text-align:center}

  从上面的结果我个人暂时得到的的结论就是,样式名过长会增大 css 文件的大小,但是仍需要再求证一下。

四、babel-plugin-react-css-modules 插件

  使用了css modules 后,我们每次都需要写className={styles.xxx},比较麻烦,babel-plugin-react-css-modules插件可以实现使用styleName属性自动加载CSS模块。我们通过该babel插件来进行语法树解析并最终生成className。具体事例参考官方文档
  下面再说下webpack的配置:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: 'css-loader',
        options: {
          generateScopedName:appName + '__[name]__[local]--[hash:base64:5]',  // 要与`localIdentName`命名一致
          filetypes: {
              ".less": "sugerss"
           },
        },
      },
    ],
  },
};