代码压缩是非常基础的优化手段。
虽然基础,但是性价比很高:经过压缩后的代码,体积被大幅度减小,这样就能大幅度节省网络传输资源,从而提升页面加载速度。
这里学习了webpack5压缩JS、压缩CSS、压缩HTML的方法:都是通过optimization的配置,配合一定的plugin来实现的(minimizer中指定,minimizer是个数组,可以放多个plugin)。

压缩JS

terser是目前最为流行的es6压缩工具。其前身是UglifyJS(它在 UglifyJS 基础上增加了 ES6 语法支持,并重构代码解析、压缩算法)。
下图为各大压缩工具的性能对比
image.png
webpack5.0 后默认使用 Terser 作为 JavaScript 代码压缩器,一般直接通过optimization.minimize 配置项,可开启(使用 mode = ‘production’ 启动生产模式构建时,默认也会开启 Terser 压缩。):

  1. module.exports = {
  2. //...
  3. optimization: {
  4. minimize: true
  5. }
  6. };

只有 minimize = true’ 时才会调用 minimizer 声明的压缩器数组,执行压缩操作。
还有一些其他常见配置:

  • dead_code:是否删除不可触达的代码 —— 也就是所谓的死代码;
  • booleans_as_integers:是否将 Boolean 值字面量转换为 0、1;
  • join_vars:是否合并连续的变量声明,如 var a = 1; var b = 2; 合并为 var a=1,b=2;

手动创建 terser-webpack-plugin 实例,也可以更精细的压缩代码:

  1. const TerserPlugin = require("terser-webpack-plugin");
  2. module.exports = {
  3. // ...
  4. optimization: {
  5. minimize: true,
  6. minimizer: [
  7. new TerserPlugin({
  8. terserOptions: {
  9. compress: {
  10. reduce_vars: true,
  11. pure_funcs: ["console.log"],
  12. },
  13. // ...
  14. },
  15. }),
  16. ],
  17. },
  18. };

这里列了一些常用的其他配置,更多配置见:

  • test:只有命中该配置的产物路径才会执行压缩;
  • include:在该范围内的产物才会执行压缩;
  • exclude:与 include 相反,不在该范围内的产物才会执行压缩;
  • parallel:是否启动并行压缩,默认值为 true,此时会按 os.cpus().length - 1 启动若干进程并发执行(参考多进程并行:优化:并行构建);
  • minify:用于配置压缩器,支持传入自定义压缩函数,也支持 swc/esbuild/uglifyjs 等值;
  • terserOptions:传入 minify —— “压缩器”函数的配置参数;(terser-webpack-plugin并不只是 Terser 的简单包装,它更像是一个代码压缩功能骨架,底层还支持使用 SWC、UglifyJS、ESBuild 作为压缩器,使用时只需要通过 minify 参数切换即可, eg: https://webpack.js.org/plugins/terser-webpack-plugin/#swc)
  • extractComments:是否将代码中的备注抽取为单独文件,可配合特殊备注如 @license 使用。

    压缩CSS

    下面是压缩css配置逻辑:
  1. 使用 mini-css-extract-plugin 将 CSS 代码抽取为单独的 CSS 产物文件,这样才能命中 css-minimizer-webpack-plugin 默认的 test 逻辑(loader这里用的是 MiniCssExtractPlugin.loader 而不是 style-loader);
  2. 使用 css-minimizer-webpack-plugin 压缩 CSS 代码。 ```javascript const CssMinimizerPlugin = require(“css-minimizer-webpack-plugin”); const MiniCssExtractPlugin = require(“mini-css-extract-plugin”);

module.exports = { //… module: { rules: [ { test: /.css$/, // 注意,这里用的是 MiniCssExtractPlugin.loader 而不是 style-loader use: [MiniCssExtractPlugin.loader, “css-loader”], }, ], }, optimization: { minimize: true, minimizer: [ // Webpack5 之后,约定使用 '...' 字面量保留默认 minimizer 配置 “…”, new CssMinimizerPlugin(), ], }, // 需要使用 mini-css-extract-plugin 将 CSS 代码抽取为单独文件 // 才能命中 css-minimizer-webpack-plugin 默认的 test 规则 plugins: [new MiniCssExtractPlugin()], };

  1. terser-webpack-plugin 类似,css-minimizer-webpack-plugin 也支持 testincludeexcludeminifyminimizerOptions 配置,其中 minify 支持cssnanoclean-cssesbuildpaecel-css等:默认 cssnano 压缩代码,这个绝大多数场景就能满足需求;
  2. <a name="Aq68k"></a>
  3. #### 压缩HTML
  4. 现代web框架中,html代码的比例不高,不过某些SSGStatic Site Generation)场景下可能就需要对html进行压缩了。webpack中可以使用这个:html-minifier-terser
  5. ```javascript
  6. const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
  7. optimization: {
  8. minimize: true,
  9. minimizer: [
  10. // Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
  11. "...",
  12. new HtmlMinimizerPlugin({
  13. minimizerOptions: {
  14. // 折叠 Boolean 型属性
  15. collapseBooleanAttributes: true,
  16. // 使用精简 `doctype` 定义
  17. useShortDoctype: true,
  18. // ...
  19. },
  20. }),
  21. ],
  22. },

上面介绍了js、css、html的压缩,那么三种同时压缩最简单的配置:

  1. optimization: {
  2. minimize: true, // terser压缩js
  3. minimizer: [
  4. // Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
  5. "...",
  6. // 压缩css
  7. new CssMinimizerPlugin(),
  8. // 压缩html
  9. new HtmlMinimizerPlugin({
  10. minimizerOptions: {
  11. // 折叠 Boolean 型属性
  12. collapseBooleanAttributes: true,
  13. // 使用精简 `doctype` 定义
  14. useShortDoctype: true,
  15. // ...
  16. },
  17. }),
  18. ],
  19. },

代码压缩原理

“代码压缩”最关键的问题是:如何用“更精简”的代码表达“同一套”程序逻辑?

  • “更精简”意味着可以适当 —— 甚至完全牺牲可读性、语义、优雅度而力求用最少字符数的方式书写代码
  • “同一套”意味着修改前后必须保持一致的代码逻辑、执行流程、功能效果等;

那么,代码压缩的原理是什么呢?

  1. 转换为结构化、容易分析处理的 AST形态。
  2. 在 AST 上做各种语法、语义、逻辑推理与简化替换;
  3. 最后按精简过的 AST 生成结果代码。

image.png

社区曾经出现过非常非常多 JavaScript、HTML、CSS 代码压缩工具,基本上都是按照上面这种套路实现的,包括: Terser、ESBuild、CSS-Nano、babel-minify、htmlMinifierTerser 等,幸运的是,我们可以在 Webpack 中轻松接入这些工具,实现代码压缩

另外说说,代码压缩和代码混淆。
代码压缩的目的是为了减少代码体积,代码混淆的目的是降低代码的可读性使之变得不易理解;
两者的目的不同但是都使用了一些相同的手段,比如变量重命名,代码转换等。
代码混淆会有字符串提取提取加密,随机加入代码进行混淆等手段来降低代码可读性。这是与代码压缩有区别的地方。