Webpack4散记(4)JS和CSS的打包优化

1,019 阅读2分钟

以前有个错误认识:以为在webpack配置中,只要设置mode="production"即可以实现代码的压缩。不知道什么时候留下的这个错误印象,可怕。

本文结合splitChunks,记录一下本次对项目打包深度优化的过程。

为了方便讨论,新建了一个React + Antd项目:github.com/imnull/webp…

WebPack核心相关版本

"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"

因为webpack@^5的周边支持还不是很好,之前写插件发现改动有点大,所以还是使用webpack4。对应的webpack-clidev-server版本,也需要按照上面的配置,才能很好运行。

Build Level 0

除了配置了各种loader、plugin之外,没有对打包输出做任何额外配置。

$ npm run b0

         Asset       Size  Chunks                                Chunk Names
client.eb3b.js    259 KiB       0  [emitted] [immutable]  [big]  client
    index.html  134 bytes          [emitted]                     
Entrypoint client [big] = client.eb3b.js

Entrypoints:
  client (259 KiB)
      client.eb3b.js

看一下产出物,JS代码确实做了压缩。难道是说非单包情况,需要额外增加代码压缩配置?

单个JS文件大小超过了244KiB,下面做一下分包。

Build Level 1 - splitChunks

增加了optimization项,其中splitChunks.cacheGroups配置如下:

cacheGroups: {
    common: {
        name: 'common',
        chunks: 'initial',
        priority: 100,
        reuseExistingChunk: false,
        enforce: true,
        test: m => /\/node_modules\/(react|redux|classnames|prop-types|lodash|acorn|rc-)/.test(m.context),
    },
    antd: {
        name: 'antd',
        chunks: 'initial',
        priority: 90,
        reuseExistingChunk: true,
        enforce: true,
        test: m => /\/node_modules\/(antd|@ant-design)/.test(m.context),
    },
}

这里分了两个包:commonantd

需要注意的cacheGroups的几个特征:

  1. 依赖test检测文件名称,对包分组进行筛选;
  2. priority优先级排序,数组越大优先级越高,优先打入高优先分组;
  3. 剩余内容全部打入entry中的包。 看下运行效果:
         Asset       Size  Chunks                         Chunk Names
  antd.3469.js    136 KiB       0  [emitted] [immutable]  antd
client.f63a.js   62.7 KiB       1  [emitted] [immutable]  client
common.15da.js    169 KiB       2  [emitted] [immutable]  common
    index.html  208 bytes          [emitted]              
Entrypoint client [big] = common.15da.js antd.3469.js client.f63a.js

Entrypoints:
  client (368 KiB)
      common.15da.js
      antd.3469.js
      client.f63a.js

体积果然膨胀了很多

PS: antd已配置为按需打包

Build Level 2 - UglifyJsPlugin

就像文章开头所说,看下产出物,代码格式整整齐齐。

引入uglifyjs-webpack-plugin来做JS压缩:

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
//...
optimization: {
    minimizer: [
        new UglifyJsPlugin(),
    ],
    // ,,,
}

运行效果:

         Asset       Size  Chunks                         Chunk Names
  antd.8c2d.js   99.3 KiB       0  [emitted] [immutable]  antd
client.ca88.js   23.8 KiB       1  [emitted] [immutable]  client
common.fb46.js    136 KiB       2  [emitted] [immutable]  common
    index.html  208 bytes          [emitted]              
Entrypoint client [big] = common.fb46.js antd.8c2d.js client.ca88.js

Entrypoints:
  client (259 KiB)
      common.fb46.js
      antd.8c2d.js
      client.ca88.js

效果非常显著,减少了1/4多,和单包带压缩体积一致。

继续观察产出物,发现JS中内嵌的CSS代码还是未压缩的原始文本,带有换行和空格:

//...
r.push([e.i,".container {\n    border: 3px solid #aaa;\n    padding: 20px;\n    width: 350px;\n    margin: 0 auto;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n}\n\n.container h1 {\n    margin: 0;\n    padding: 0;\n    line-height: 2em;\n}",""])
///...

Build Level 3 - splitChunks剥离样式文件

splitChunks.cacheGroups中增加一项:

styles: {
    name: 'styles',
    test: /\.(scss|css|less)$/,
    chunks: 'all',
    priority: 110,
    enforce: true
},

注意优先级priority是当前最高的:

         Asset       Size  Chunks                         Chunk Names
  antd.06a8.js   23.2 KiB       0  [emitted] [immutable]  antd
client.e11d.js   23.4 KiB       1  [emitted] [immutable]  client
common.33d5.js    136 KiB       2  [emitted] [immutable]  common
    index.html  246 bytes          [emitted]              
styles.848c.js   76.7 KiB       3  [emitted] [immutable]  styles
Entrypoint client [big] = styles.848c.js common.33d5.js antd.06a8.js client.e11d.js

Entrypoints:
  client (259 KiB)
      styles.848c.js
      common.33d5.js
      antd.06a8.js
      client.e11d.js

能看出来,clientantd两个包都有减小,是因为其中的样式文件都归并到了styles分包中。

但体积总量依然是259KiB,查看styles分组内容,发现内嵌样式文本依然是未压缩的。

所以说,虽然该方案通过分包降低了单位体积,但并未实质上实现CSS压缩。

Build Level 4 - MiniCss + OptimizeCss

用到两个工具:

  • mini-css-extract-plugin
  • optimize-css-assets-webpack-plugin

这个方案,是根据chunk分组进行CSS代码提取和压缩,并产生对应的CSS文件,与上面Level 3styles分包配置冲突,所以需要先删掉styles分包配置。

配置loader

在配置中,原有cssless配置的第一项loader都是style-loader,替换为MiniCss.loader,

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// ...
module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,    // 这里
                    'css-loader',
                ],
                exclude: /node_modules/,
            },
            {
                test: /\.less$/i,
                use: [
                    MiniCssExtractPlugin.loader,    // 这里
                    'css-loader',
                    {
                        loader: 'less-loader',
                        options: {
                            lessOptions: {
                                javascriptEnabled: true,
                            }
                        },
                    }
                ],
            }
        ]
    }
}

配置optimization

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// ...
module.exports = {
    optimization: {
        minimizer: [
            new MiniCssExtractPlugin({
                filename: '[name].[contenthash:4].css'
            }),
            new OptimizeCssAssetsPlugin({}),
            // ...
        ]
    }
}

运行效果

          Asset       Size  Chunks                         Chunk Names
   antd.7aee.js   23.2 KiB       0  [emitted] [immutable]  antd
  antd.cd7e.css   56.3 KiB       0  [emitted] [immutable]  antd
 client.3ea8.js   20.4 KiB       1  [emitted] [immutable]  client
client.7dc3.css  175 bytes       1  [emitted] [immutable]  client
 common.fd2a.js    136 KiB       2  [emitted] [immutable]  common
     index.html  298 bytes          [emitted]              
Entrypoint client = common.fd2a.js antd.cd7e.css antd.7aee.js client.7dc3.css client.3ea8.js

webpack-bundle-analyzer中显示的结果(不包含CSS文件)为:

All (179.61 KB)
common.fd2a.js (135.98 KB)
antd.7aee.js (23.22 KB)
client.3ea8.js (20.41 KB)

目录简介显示总体积为242,052字节,转换为KiB,最终体积约为236.38KiB

本次优化结束,以上。