主题
代码压缩
原理
“更精简”意味着可以适当 —— 甚至完全牺牲可读性、语义、优雅度而力求用最少字符数的方式书写代码。比如说 const name = 'tecvan';
,这个看起来非常简单的赋值语句就有不少可以精简的字符:
- 变量名
name
语义很明确,大多数“人”看到就基本明白是干什么用的,但这对计算机并没有什么意义,我们完全可以将name
修改为a
—— 从 4 个字符精简为 1 个字符,但仍保持改动前后逻辑、功能效果完全一致; - 赋值操作符
=
前后都有空格,这种格式对阅读代码的“人”很友好,视觉效果非常舒适、整齐,但对计算机而言同样毫无意义,我们可以将这前后两个空格删掉 —— 精简了两个字符; - 虽然
const
与let
关键词的功能不同,但特定情况下我们同样能牺牲一部分功能性,用let
替换const
,从 5 个字符精简为 1 个字符。
经过上面三个步骤之后,代码从 const name = 'tecvan';
—— 22 个字符,精简为 let a='tecvan';
—— 18 个字符,往大了说是节省了 18% 的代码体积。其它语言的代码压缩规则也基本都是按照上面这种套路实现的。
我们可以先将字符串形态的代码转换为结构化、容易分析处理的 AST(抽象语法树)形态,之后在 AST 上应用上面的规则做各种语法、语义、逻辑推理与简化替换,最后按精简过的 AST 生成结果代码。
JS压缩:TerserWebpackPlugin
Terser 是当下 最为流行 的 ES6 代码压缩工具之一,支持 Dead-Code Eliminate、删除注释、删除空格、代码合并、变量名简化等等一系列代码压缩功能。Terser 的前身是大名鼎鼎的 UglifyJS,它在 UglifyJS 基础上增加了 ES6 语法支持,并重构代码解析、压缩算法,使得执行效率与压缩率都有较大提升:
Webpack5.0 后默认使用 Terser 作为 JavaScript 代码压缩器,简单用法只需通过 optimization.minimize
配置项开启压缩功能即可:
js
module.exports = {
//...
optimization: {
minimize: true
}
};
提示:使用
mode = 'production'
启动生产模式构建时,默认也会开启 Terser 压缩。
可以手动创建 terser-webpack-plugin 实例并传入压缩配置实现更精细的压缩功能
js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
reduce_vars: true,
pure_funcs: ["console.log"],
},
// ...
},
}),
],
},
};
terser-webpack-plugin 还提供下述配置项:
test
:只有命中该配置的产物路径才会执行压缩include
:在该范围内的产物才会执行压缩exclude
:与include
相反,不在该范围内的产物才会执行压缩parallel
:是否启动并行压缩,默认值为true
,此时会按os.cpus().length - 1
启动若干进程并发执行minify
:用于配置压缩器,支持传入自定义压缩函数,也支持swc/esbuild/uglifyjs
等值terserOptions
:传入minify
—— “压缩器”函数的配置参数extractComments
:是否将代码中的备注抽取为单独文件,可配合特殊备注如@license
使用
配置项总结下来有两个值得关注的逻辑:
- 可以通过
test/include/exclude
过滤插件的执行范围,这个功能配合minimizer
的数组特性,可以实现针对不同产物执行不同的压缩策略
js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
test: /foo\.js$/i,
extractComments: "all",
}),
new TerserPlugin({
test: /bar\.js/,
extractComments: false,
}),
],
},
};
- 它是一个代码压缩功能骨架,底层还支持使用 SWC、UglifyJS、ESBuild 作为压缩器,使用时只需要通过
minify
参数切换即可,例如:
js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
// `terserOptions` 将被传递到 `swc` (`@swc/core`) 工具
// 具体配置参数可参考:https://swc.rs/docs/config-js-minify
terserOptions: {},
}),
],
},
};
提示:TerserPlugin 内置如下压缩器:
TerserPlugin.terserMinify
:依赖于terser
库;TerserPlugin.uglifyJsMinify
:依赖于uglify-js
,需要手动安装yarn add -D uglify-js
;TerserPlugin.swcMinify
:依赖于@swc/core
,需要手动安装yarn add -D
@swc/core
;TerserPlugin.esbuildMinify
:依赖于esbuild
,需要手动安装yarn add -D esbuild
。另外,
terserOptions
配置也不仅仅专供terser
使用,而是会透传给具体的minifier
,因此使用不同压缩器时支持的配置选项也会不同。
CSS 压缩:CssMinimizerWebpackPlugin
使用 cssnano 压缩后,大致可以:
- 删除注释
- 删除重复代码
- 合并成一行
- 简写,例如
margin: 10px 20px 10px 20px;
简写为margin: 10px 20px;
- 等等
使用:
- 安装依赖
shell
npm i -D css-minimizer-webpack-plugin
- 修改 Webpack 配置:
js
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()],
};
HTML 压缩:HtmlMinifierTerser
支持一系列压缩特性:
collapseWhitespace
:删除节点间的空字符串removeComments
:删除备注collapseBooleanAttributes
:删除 HTML 的 Boolean 属性值- 等等
使用
- 安装依赖
shell
npm i -D html-minimizer-webpack-plugin
- 修改 Webpack 配置
js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
// Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
"...",
new HtmlMinimizerPlugin({
minimizerOptions: {
// 折叠 Boolean 型属性
collapseBooleanAttributes: true,
// 使用精简 `doctype` 定义
useShortDoctype: true,
// ...
},
}),
],
},
plugins: [
// 简单起见,这里我们使用 `html-webpack-plugin` 自动生成 HTML 演示文件
new HtmlWebpackPlugin({
templateContent: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta charset="UTF-8" />
<title>webpack App</title>
</head>
<body>
<input readonly="readonly"/>
<!-- comments -->
<script src="index_bundle.js"></script>
</body>
</html>`,
}),
],
};