美文网首页
【Webpack4】CSS 配置之 MiniCssExtract

【Webpack4】CSS 配置之 MiniCssExtract

作者: Evelynzzz | 来源:发表于2019-12-09 18:38 被阅读0次

    Github 仓库地址:https://github.com/Evelynzzz/react-webpack-boilerplate

    版本:Webpack 4.39.1

    相关依赖:

    判断是开发模式还是生产模式


    在配置 Webpack 时,需要区分用于开发模式还是生产模式。比如我们只需要在生产模式时压缩 CSS;而在开发模式的时候,我们又希望生成 Sourcemap 便于调试,以及样式热更新。那么,怎么在 webpack.config.js 中判断开发、生产模式呢?

    我通常会定义三个 webpack 配置文件:

    • webpack.config.base.js:通用的配置,比如入口,出口,插件,loader等。以下两个配置文件会引入此配置,再修改添加其他配置。
    • webapck.config.dev.js:开发模式下,启动 webpack-dev-server。
    • webapck.config.prod.js:生产模式下,编译打包。

    然后在 package.json 中分别配置了 startbuild 脚本:

    "scripts": {
        "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js --open",
        "build": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js --progress --colors -p"
      }
    

    注意命令中通过 定义了变量NODE_ENV ,因此在webpack.config.base.js 中可以通过 process.env.NODE_ENV 获取它的值,从而判断时生产模式还是开发模式。

    const devMode = process.env.NODE_ENV === 'development'; // 是否是开发模式
    

    接下来进入正题。

    提取 CSS 到单独的文件中


    在 Webpack 4 之前,我们使用 extract-text-webpack-plugin 插件来提取项目中引入的样式文件,打包到一个单独的文件中。从 Webpack 4 开始,这个插件就过时了,需要使用 MiniCssExtractPlugin

    This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.

    此插件为每个包含 CSS 的 JS 文件创建一个单独的 CSS 文件,并支持 CSS 和 SourceMap 的按需加载。

    注意:这里说的每个包含 CSS 的 JS 文件,并不是说组件对应的 JS 文件,而是打包之后的 JS 文件!接下来会详细说明。

    情景一

    先举一个基础配置的例子。 webpack.config.js

    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    module.exports = {
      plugins: [
        new MiniCssExtractPlugin({
          filename: '[name].css'
        }),
      ],
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              MiniCssExtractPlugin.loader, 'css-loader','postcss-loader' // postcss-loader 可选
            ],
          },{
            test: /\.less$/,
            use: [
              MiniCssExtractPlugin.loader, 'css-loader','postcss-loader','less-loader' // postcss-loader 可选
            ],
          }
        ],
      },
    };
    

    基于以上配置,如果入口 app.js 中引用了 Root,Root 引入了 Topics。而 Root.js 中引用样式 main.css,Topics.js 中引用了 topics.css。

    // 入口文件 app.js
    import Root from './components/Root'
    
    // Root.js
    import '../styles/main.less'
    import Topics from './Topics'
    
    // Topics.js
    import "../styles/topics.less"
    

    这种情况下,Topics 会和 Root 同属一个 chunk,所以会一起都打包到 app.js 中, 结果就是 main.less 和 topics.less 会被提取到一个文件中:app.css。而不是生成两个 css 文件。

                Asset       Size  Chunks                    Chunk Names
              app.css  332 bytes       1  [emitted]         app
               app.js    283 KiB       1  [emitted]  [big]  app
    

    情景二

    但是,如果 Root.js 中并没有直接引入 Topics 组件,而是配置了代码分割 ,比如模块的动态引入,那么结果就不一样了:

                Asset       Size  Chunks                    Chunk Names
              app.css  260 bytes       1  [emitted]         app
               app.js    281 KiB       1  [emitted]  [big]  app
     topics.bundle.js   2.55 KiB       4  [emitted]         topics
           topics.css   72 bytes       4  [emitted]         topics
    

    因为这个时候有两个 chunk,对应了两个 JS 文件,所以会提取这两个 JS 文件中的 CSS 生成对应的文件。这才是“为每个包含 CSS 的 JS 文件创建一个单独的 CSS 文件”的真正含义。

    情景三

    但是,如果分割了 chunk,还是只希望只生成一个 CSS 文件怎么办呢?也是可以做到的。但需要借助 Webpack 的配置 optimization.splitChunks.cacheGroups

    optimization.splitChunks 是干什么的呢?在 Webpack 4 以前,我们使用 CommonsChunkPlugin 来提取重复引入的第三方依赖,比如把 React 和 Jquery 单独提取到一个文件中。而从 Webpack 4 开始,CommonsChunkPluginoptimization.splitChunks 替代了。从命名也能看出来,它是用来拆分 chunk 的。怎么在这里需要用到这个配置呢?先来看看配置怎么写的:

    optimization: {
      splitChunks: {
        cacheGroups: {
          // Extracting all CSS/less in a single file
          styles: {
            name: 'styles',
            test: /\.(c|le)ss$/,
            chunks: 'all',
            enforce: true,
          },
        }
      }
    },
    

    打包结果:

                Asset       Size  Chunks                    Chunk Names
               app.js    281 KiB       2  [emitted]  [big]  app
     styles.bundle.js  402 bytes       0  [emitted]         styles
           styles.css  332 bytes       0  [emitted]         styles
     topics.bundle.js   2.38 KiB       5  [emitted]         topics
    

    可以看出,样式确实都被提取到一个 styles.css 文件中了。但与此同时多了一个 style.bundle.js 文件,这就是 optimization.splitChunks.cacheGroups 的效果。具体原理就不在此深究,感兴趣的话可以研究一下。

    MiniCssExtractPlugin vs. style-loader


    首先这两个插件用途完全不同:MiniCssExtractPlugin 提取 JS 中引入的 CSS 打包到单独文件中,然后通过标签 <link>添加到头部;style-loader 则是通过 <style> 标签直接将 CSS 插入到 DOM 中。

    通常,基本的 CSS 配置都是类似这样的。先 style-loader,然后 css-loader。

    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader', 'css-loader'
                ],
            },
        ],
    }
    

    但后来由于想要提取 CSS 到单独的文件里,就需要用上 MiniCssExtractPlugin。那么问题来了,如下的配置可行吗?

    {
        test: /\.css$/,
            use: [
                'style-loader', MiniCssExtractPlugin.loader, 'css-loader','postcss-loader'
            ],
    }
    

    生产模式

    根据 MiniCssExtractPlugin 文档 中说到的,此插件适用于没有style-loader 的生产模式中,以及需要 HMR 的开发模式。

    This plugin should be used only on production builds without style-loader in the loaders chain, especially if you want to have HMR in development.

    也就是说,在生产模式中,以上的配置同时使用了style-loader 和 MiniCssExtractPlugin 是不合适的(试了一下,style-loader不会起作用)。

    我们只能取其一。也可以如下两者结合,开发模式中使用 style-loader,生产模式中使用 MiniCssExtractPlugin。各取所需,毕竟这两者的作用还是很不同。

    {
        test: /\.css$/,
        use: [
            devMode?'style-loader':MiniCssExtractPlugin.loader,'css-loader','postcss-loader'
        ]
    }
    

    样式文件热更新(HMR)

    从上面引用的那句话也可以看出,在开发模式中, 我们可以用 MiniCssExtractPlugin 实现样式的 HMR(Hot Module Replacement,模块热更新)。

    样式文件的 HMR 是指什么呢?如果没有配置 HMR,开发模式下,修改 CSS 源文件的时候,页面并不会自动刷新加载修改后的样式。需要手动刷新页面,才会加载变化。而 HMR 实现了被修改模块的热更新,使得变化即时显示在页面上,不再需要刷新整个页面。

    但其实 style-loader也实现了 HMR 接口,如 Wepack 文档的 In a Module 中说到的:

    HMR is an opt-in feature that only affects modules containing HMR code. One example would be patching styling through the style-loader. In order for patching to work, the style-loader implements the HMR interface; when it receives an update through HMR, it replaces the old styles with the new ones.

    因此开发环境下,这两个插件都是可以热更新 CSS 的,只是 MiniCssExtractPlugin 的配置可能更丰富一些。比如说:style-loader 只热更新 JS 中引入的样式,如果 index.html 中通过 <link> 引入了服务器中的一个CSS 文件:

    <link rel="stylesheet" href="/vendors/test.css">
    <!-- 通过配置 copy-webpack-plugin 在打包时把 html/vendors/test.css 拷贝到服务器根目录中,因此可以这么链接 -->
    

    如果开发模式下,修改 test.css 的源码,style-loader 不会热更新变化 CSS,而是需要刷新整个页面,但 MiniCssExtractPlugin 则会自动重新加载所有的样式。可能还有其他区别,在此不详细说明了。

    MiniCssExtractPlugin 插件可以这么配置 Less 文件的 HMR:

    const devMode = process.env.NODE_ENV === 'development'; // 是否是开发模式
    //......
    module.exports = {
        //......
        module: {
          rules:[
            {
              test: /\.less$/i,
              use:  [
                {
                  loader: MiniCssExtractPlugin.loader,
                  options: {
                    // 只在开发模式中启用热更新
                    hmr: devMode,
                    // 如果模块热更新不起作用,重新加载全部样式
                    reloadAll: true,
                  },
                },
                'css-loader','postcss-loader','less-loader'
              ]
            },
            // ......
          ]
        }
    }
    

    参考阅读


    相关文章

      网友评论

          本文标题:【Webpack4】CSS 配置之 MiniCssExtract

          本文链接:https://www.haomeiwen.com/subject/fthcgctx.html