主题
代码压缩
原理
“更精简”意味着可以适当 —— 甚至完全牺牲可读性、语义、优雅度而力求用最少字符数的方式书写代码。比如说 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>`,
}),
],
};