代码压缩是非常基础的优化手段。
虽然基础,但是性价比很高:经过压缩后的代码,体积被大幅度减小,这样就能大幅度节省网络传输资源,从而提升页面加载速度。
这里学习了webpack5压缩JS、压缩CSS、压缩HTML的方法:都是通过optimization的配置,配合一定的plugin来实现的(minimizer中指定,minimizer是个数组,可以放多个plugin)。
压缩JS
terser是目前最为流行的es6压缩工具。其前身是UglifyJS(它在 UglifyJS 基础上增加了 ES6 语法支持,并重构代码解析、压缩算法)。
下图为各大压缩工具的性能对比
webpack5.0 后默认使用 Terser 作为 JavaScript 代码压缩器,一般直接通过optimization.minimize 配置项,可开启(使用 mode = ‘production’ 启动生产模式构建时,默认也会开启 Terser 压缩。):
module.exports = {
//...
optimization: {
minimize: true
}
};
只有 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 实例,也可以更精细的压缩代码:
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
reduce_vars: true,
pure_funcs: ["console.log"],
},
// ...
},
}),
],
},
};
这里列了一些常用的其他配置,更多配置见:
- 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配置逻辑:
- 使用 mini-css-extract-plugin 将 CSS 代码抽取为单独的 CSS 产物文件,这样才能命中 css-minimizer-webpack-plugin 默认的 test 逻辑(loader这里用的是
MiniCssExtractPlugin.loader
而不是style-loader
); - 使用 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()],
};
与 terser-webpack-plugin 类似,css-minimizer-webpack-plugin 也支持 test、include、exclude、minify、minimizerOptions 配置,其中 minify 支持cssnano、clean-css、esbuild、paecel-css等:默认 cssnano 压缩代码,这个绝大多数场景就能满足需求;
<a name="Aq68k"></a>
#### 压缩HTML
现代web框架中,html代码的比例不高,不过某些SSG(Static Site Generation)场景下可能就需要对html进行压缩了。webpack中可以使用这个:html-minifier-terser
```javascript
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
optimization: {
minimize: true,
minimizer: [
// Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
"...",
new HtmlMinimizerPlugin({
minimizerOptions: {
// 折叠 Boolean 型属性
collapseBooleanAttributes: true,
// 使用精简 `doctype` 定义
useShortDoctype: true,
// ...
},
}),
],
},
上面介绍了js、css、html的压缩,那么三种同时压缩最简单的配置:
optimization: {
minimize: true, // terser压缩js
minimizer: [
// Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
"...",
// 压缩css
new CssMinimizerPlugin(),
// 压缩html
new HtmlMinimizerPlugin({
minimizerOptions: {
// 折叠 Boolean 型属性
collapseBooleanAttributes: true,
// 使用精简 `doctype` 定义
useShortDoctype: true,
// ...
},
}),
],
},
代码压缩原理
“代码压缩”最关键的问题是:如何用“更精简”的代码表达“同一套”程序逻辑?
- “更精简”意味着可以适当 —— 甚至完全牺牲可读性、语义、优雅度而力求用最少字符数的方式书写代码
- “同一套”意味着修改前后必须保持一致的代码逻辑、执行流程、功能效果等;
那么,代码压缩的原理是什么呢?
- 转换为结构化、容易分析处理的 AST形态。
- 在 AST 上做各种语法、语义、逻辑推理与简化替换;
- 最后按精简过的 AST 生成结果代码。
社区曾经出现过非常非常多 JavaScript、HTML、CSS 代码压缩工具,基本上都是按照上面这种套路实现的,包括: Terser、ESBuild、CSS-Nano、babel-minify、htmlMinifierTerser 等,幸运的是,我们可以在 Webpack 中轻松接入这些工具,实现代码压缩
另外说说,代码压缩和代码混淆。
代码压缩的目的是为了减少代码体积,代码混淆的目的是降低代码的可读性使之变得不易理解;
两者的目的不同但是都使用了一些相同的手段,比如变量重命名,代码转换等。
代码混淆会有字符串提取提取加密,随机加入代码进行混淆等手段来降低代码可读性。这是与代码压缩有区别的地方。