什么是CSS Modules

套用CSS Modules官网的话来说,一个css文件代表一个模块,这个css文件中的类名(class names)和 动画名(animation names)默认都有一个局部作用域,简单来说就是css也有作用域了(其实这里不是很准确,只是利用了名字转换,让css像是拥有了作用域一般)。
Just show me the code
原始代码

    /*style.css*/
    .header {
        color: red;
        font-size: 20px;
    }
    /*或者*/
    :lcoal(.header) {
        color: red;
        font-size: 20px;
    }
    import style from './style.css';
    ...
    render() {
        return <div className="page">
            <section className={style.header}>
                header
            </section>
        </div>
    }

转换之后代码

    .style__header___YHKJLJH {
        color: red;
        font-size: 20px;
    }
    <div class="page">
        <div class="style__header___YHKJLJH">
            header
        </div>
    </div>

为什么要用CSS Modules

个人觉得主要是CSS Modules用于提供css局部作用域的能力,让css更加可控,避免污染全局样式。以往开发过程中往往只能靠命名规范,已经程序员自觉遵守团队的命名规范来避免样式污染。当项目越来越大,团队成员越来越多,就很难避免出现样式被污染的情况。此时改起样式难免有点束手束脚了,那么如果现在有一种方式(CSS Modules)能让你写的样式完全与其他的样式隔离,是多么爽的事情。

先来了解css-loader中与CSS Modules相关的配置

CSS Modules其实并不是官方的功能,而是项目在编译打包阶段来修改类名,替换对应的class,实质上webpack打包时是依赖css-loader来进行处理,让CSS Modules生效的。

  1. modules: 默认值为false;true表示开启CSS Modules
  2. sourceMap:默认为false,不开启sourceMap; true开启sourceMap,开发环境下开启比较实用
  3. getLocalIdent:默认为undefined,用function自定义生成的类名
  4. localIdentName: 默认[path]___[name]__[local]___[hash:base64:5] ,可以自定义了类名的模板,可以进行适当修改

开始实战吧

这里我们就以reactjs为例,来开启CSS Modules之旅。

这里我们做一个默认的约定,使用.module.css或者module.scss作为文件后缀,区分全局的样式和局部样式。

  • 首先使用create-react-app创建一个my-app项目, 然后运行项目(创建过程需要一定时间,因为这里会把依赖包都安装好。)
npx create-react-app my-app

npm run start

演示项目使用的版本信息

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "react-scripts": "3.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
  • 不做任何修改即可使用CSS Modules

在my-app/src目录添加一个style.module.css文件

  .title {
      font-size: 25px;
  }
  import React from 'react';
  import logo from './logo.svg';
  import './App.css';
  import style from './style.module.css';

  function App() {
  return (
      <div className="App">
      <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p className={style.title}>Hello CSS Modules</p>
      </header>
      </div>
  );
  }

  export default App;

保存后查看浏览器,可以在控制台中看到p标签的class="style_title__2t5Z0",对应的样式类名title也转换成style_title__2t5Z0。可见使用create-react-app, 默认已经支持CSS Modules。
默认运行支持CSS MOdules

  • 查看webpack配置

新版本create-react-app创建的项目把打包构建的脚本都放在npm包中了,所以需要用npm run eject命令解压出相关文件夹,运行成功后项目目录下会增加两个文件夹config,scripts,我们主要查看config/webpack.config.js,直接查看oneOf里面的内容中的这一块

  /*css*/
  {
      test: cssModuleRegex,
      use: getStyleLoaders({
      importLoaders: 1,
      sourceMap: isEnvProduction && shouldUseSourceMap,
      modules: true,
      getLocalIdent: getCSSModuleLocalIdent,
      }),
  },
  /*scss*/
  {
      test: sassModuleRegex,
      use: getStyleLoaders(
      {
          importLoaders: 2,
          sourceMap: isEnvProduction && shouldUseSourceMap,
          modules: true,
          getLocalIdent: getCSSModuleLocalIdent,
      },
      'sass-loader'
      ),
  },

  //cssModuleRegex是匹配cssModule的正则,/\.module\.css$/
  //getStyleLoaders 是用于获取对应的loader,我们只需关注其中的css-loader,css-loader使用的options就是getStyleLoaders的第一个参数,modules: true 打开css modules,其实css modules是由css-loader提供支持
  const getStyleLoaders = (cssOptions, preProcessor) => {
      const loaders = [
          ...
          {
              loader: require.resolve('css-loader'),
              options: cssOptions,
          },
          ...
          return loaders;

  }

可见在create-react-app创建的项目中CSS Modules已经开箱可用了(支持css ,scss),只需要修改一下使用样式的方式(import),以及使用module.css 和module.scss(sass)后缀

  • 已有项目中引入CSS Modules

实际情况中,我们经常是需要在自己搭建的脚手架中引入CSS Modules,那也是非常简单的事情,只需要修改css-loader相关配置即可。sourceMap只在开发环境下打开,localIdentName在开发环境下显示比较详细,可以自由配置,而生产环境下直接显示编码后的hash。推荐使用module.scss,module.css作为CSS Modules文件的后缀,和已有的样式文件区分处理。

    //是否开发环境
    const isDevelop = true;
    ...
     {
        test: /\.module\.scss$/,
        use: [
            'style-loader',
            {
                'css-loader',
                options: {
                    sourceMap: isDevelop,
                    modules: true,
                    localIdentName: dev ? '[path]___[name]__[local]___[hash:base64:5]' : '[hash:base64]',
                }
            }
            'postcss-loader',
            'sass-loader'
    },
  • 终极武器 babel-plugin-react-css-modules

现在已经解决了在样式文件中不再需要:local来标志哪些类名需要进行转换的问题,那么在使用的时候还是有一些问题,CSS Modules推荐使用驼峰命名,What?
show me code

    import style from './style.module.css';

    <div className={style.pageHeader}></div>

    <div className={style['page-header']}></div>

很明显使用驼峰简洁许多了,官方推荐也不是没有道理的。除此之外,每次写className时,都要用a.b方式,能不能更简单一些?当然可以:

    import './style.module.css';
    ...
    render() {
        return (
            <div styleName="title"></div>
        )
    }

这样是不是感觉简单高效了许多。不过还需要借助一个babel插件babel-plugin-react-css-modules,这个插件会在打包阶段把styleName属性的值转换为对应的名字(generateScopedName定义的格式),之后把styleName属性值加到已有className中(如果没有className则创建)。

    npm install babel-plugin-react-css-modules --save-dev
    //用于支持scss
    npm install postcss-scss

需要在webpack配置文件的babel-loader添加配置如下:

    {
        test: /\.(js|mjs|jsx|ts|tsx)$/,
        loader: 'babel-loader',
        options: {
            plugins: [
                ...
                [
                    'react-css-modules',
                    {
                        webpackHotModuleReloading: true,
                        autoResolveMultipleImports: true,
                        generateScopedName: '[path]___[name]__[local]___[hash:base64:5]',
                        filetypes: {
                            '.scss': {
                                syntax: 'postcss-scss',
                            }
                        }
                    }
                ]
            ]
        }
    }    

其中generateScopedName如果配置,则需要和style-loader中的localIdentName保持一致,否则会导致styleName使用的名字与实际生成的classname不一致,样式无效!filetypes的配置是为了支持scss,对于scss样式会先由postcss-scss做一次处理。

总结

以上就是个人关于CSS Modules初次探索,并且已经开始在项目中使用CSS Modules,写起样式来可以随心所欲,再也不用担心样式污染了。


我系小艾
6 声望0 粉丝

负责平台项目开发,工程打包自动化流程改进