代码压缩(Minification)——从源代码中删除所有非必要字符的过程:空格、制表符、换行符和注释。代码压缩可在不改变程序执行逻辑的情况下减小JavaScript、CSS和HTML文件的大小。根据MDN Web Docs,压缩可将文件大小减少50–70%,直接影响应用程序的加载速度和首次内容绘制(FCP)。
要点
代码压缩(Minification)——自动转换源代码,删除所有不影响程序执行的字符。空格、制表符、换行符、注释和多于括号被删除或缩短。压缩应用于JavaScript、CSS、HTML以及较少情况下的JSON配置文件,在部署到生产环境之前进行。
压缩的主要目标是减少通过网络传输的文件大小。一个300KB的JavaScript文件在压缩后可以占用120–150KB,在慢速3G连接下可将加载时间缩短200–400毫秒。Google PageSpeed Insights和Lighthouse直接推荐压缩代码:这是评估性能和Core Web Vitals的标准之一。
压缩是构建流水线(build pipeline)中不可或缺的一部分。在TypeScript或Babel转译之后,代码经过Tree Shaking(删除未使用的函数),然后通过压缩器,最后才被组装到最终包中。在现代打包器(Webpack、Rollup、esbuild)中,压缩由插件在构建的最后阶段执行。
| 元素 | 示例(前) | 示例(后) | 节省 |
|---|---|---|---|
| 空格和制表符 | let x = 5; | let x=5; | ~10–20% |
| 换行符 |
行之间 | 全部在一行 | ~2–5% |
| 注释 | // comment | 已删除 | ~5–30% |
| 多于括号 | if ((x > 0)) | if(x>0) | ~1–3% |
删除空格和换行符——最简单和最直接的技术。压缩器的解析器遍历AST(抽象语法树),删除所有不是字符串字面量一部分的空白字符。大多数压缩器还将操作符合并到一行中,这在使用GZip或Brotli压缩时提供了额外的节省。
用短名称替换变量名(名称混淆/mangling)——一种更激进的技术。局部变量和函数参数被重命名为单字母标识符:a、b、c。这可将大小额外减少15–30%。Terser和esbuild支持名称混淆,并带有保留特定名称的选项(例如通过mangle.props.reserved保留公共API)。
死代码消除(Dead code elimination, DCE)——删除永远不会执行的代码分支。压缩器分析始终为false的条件(if (false))并删除相应的代码块。更高级的分析在压缩之前的Tree Shaking阶段执行,但压缩器级别的DCE会捕获在模块级别未检测到的局部死代码情况。
名称混淆(Mangling)——将局部变量和函数参数重命名为短标识符(一到两个字母)。Terser基于作用域执行名称混淆:一个函数内的变量获得唯一短名称,不与外部作用域冲突。mangle.reserved选项允许保留特定名称——例如库的公共API。
名称混淆不影响全局变量、对象属性(除非启用了mangle.props)以及使用new的类名。为了保护库的公共API免受名称混淆影响,应使用带正则表达式的mangle.props.reserved:reserved: ["_prop", /^private_/]。如果没有此设置,如果外部代码按名称访问对象属性,名称混淆可能会破坏库。
// 源代码
function calculateTotal(price, tax) {
var result = price + (price * tax);
// 返回最终总和
return result;
}
var total = calculateTotal(100, 0.2);
console.log(total);// Terser压缩后
function calculateTotal(a, b){return a + a * b}
var c = calculateTotal(100, .2);
console.log(c);在第二个示例中,Terser删除了注释、空格和换行符,将price重命名为a,tax重命名为b,result重命名为c。大小从197个字符减少到79——节省了60%。同时代码的功能完全保留:calculateTotal(100, 0.2)返回120。
Terser——Webpack生态系统和大多数现代打包器中的标准JavaScript压缩器。Terser支持ES6+语法(箭头函数、async/await、扩展运算符)、带公共API保护的名称混淆、source maps和按CPU数量并行运行。从5.16版本开始,Terser包含TypeScript的优化——删除type-only导入和接口。
esbuild——用Go编写的压缩器,嵌入在同名的打包器中。esbuild凭借原生代码和并行处理,比Terser快10–100倍。然而esbuild支持的优化较少:它不执行带属性保留的名称混淆,不删除type-only导入,并且优化表达式不够激进。
// webpack.config.js — TerserPlugin配置
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
dead_code: true,
},
mangle: {
reserved: ["React", "Component"],
},
output: {
comments: false,
},
},
parallel: true,
}),
],
},
};TerserPlugin的配置包括三个关键部分。compress负责表达式优化:drop_console删除console.log,drop_debugger删除debugger,dead_code删除不可达分支。mangle管理变量的重命名,使用reserved保护公共名称。parallel按CPU数量启用多线程。
// esbuild:用于压缩JS和CSS的CLI命令
// esbuild app.js --bundle --minify --outfile=dist/app.min.js
// esbuild app.css --bundle --minify --outfile=dist/app.min.css
const esbuild = require("esbuild");
esbuild.build({
entryPoints: ["src/app.js", "src/styles.css"],
bundle: true,
minify: true,
sourcemap: true,
target: ["es2015"],
outdir: "dist",
}).catch(() => process.exit(1));esbuild API接受一行配置。minify: true启用JS和CSS的压缩。sourcemap: true生成用于调试的代码映射。target指定最低ES级别——esbuild自动将现代代码转译为所选版本。esbuild在构建时间至关重要的项目中特别有效:CI/CD流水线、热模块替换(HMR)和快速原型开发。
React Native使用Metro Bundler——它自己的打包器,包含基于Terser的压缩。在生产模式下,Metro自动对应用程序的JavaScript包应用压缩。Metro中的压缩配置通过metro.config.js在transformer.minifierConfig部分设置。此外,可以禁用inline-require以减小iOS上的包大小。
移动应用程序中的WebView也受益于压缩。在WebView中加载的HTML、CSS和JS在嵌入应用程序或从服务器加载之前必须经过压缩。对于本地资源(Assets),压缩尤为重要——APK/IPA的大小直接影响应用商店中的安装转化率。
Hermes——由Facebook开发的React Native JavaScript引擎。Hermes执行提前(AOT)字节码编译,将应用程序启动时间减少30–50%。对于Hermes,JavaScript包的压缩在通过Terser编译为字节码之前执行。Hermes支持自己的HBC(Hermes字节码)格式,但JS压缩阶段仍然是必要的。
// metro.config.js — 压缩配置
const defaultConfig = require("metro-config/src/defaults");
module.exports = require("metro-config").mergeConfig(
defaultConfig,
{
transformer: {
minifierConfig: {
compress: {
drop_console: true,
},
mangle: {
safari10: true,
},
},
},
}
);带有minifierConfig.drop_console的Metro从React Native生产包中删除所有console.log。mangle.safari10参数防止重命名破坏Safari 10(iOS 10)的标识符。这对于支持运行iOS 10/11的旧iPad和iPhone 5s设备的应用程序尤为重要。
代码压缩和打包(gzip、brotli)是不同的优化阶段。压缩在源代码级别工作,在发送到服务器之前减少字符数。打包在传输协议级别工作,由服务器(nginx、Apache、CDN)在将文件传输给客户端时应用。压缩在打包之前减小大小,这提供了双重效果:gzip压缩已经缩短的数据。
一个300KB的JavaScript文件在压缩后——120KB,gzip后——35–40KB。如果仅将gzip应用于未压缩的文件,大小将为55–70KB。压缩 + gzip比单独的gzip提供30–40%更好的结果。Brotli(级别6)在压缩后比gzip额外高效15–20%。
| 方法 | 级别 | 文件大小 | 减少 |
|---|---|---|---|
| 原始文件 | — | 300 KB | 0% |
| 压缩后 | 代码 | 120 KB | 60% |
| gzip后(未压缩) | 传输 | 65 KB | 78% |
| 压缩 + gzip | 代码 + 传输 | 38 KB | 87% |
| 压缩 + Brotli | 代码 + 传输 | 30 KB | 90% |
常见问题
UglifyJS——一种过时的压缩工具,不支持ES6+语法。Terser是UglifyJS的一个分支,支持现代JavaScript。所有现代打包器(Webpack 5、Rollup、esbuild)都使用Terser或自己的压缩器,而UglifyJS仅用于旧项目(legacy)。
Source maps——单独的.map文件,不包含在生产包中。服务器应仅向授权开发人员提供source maps(通过Authorization标头)。在生产环境中,source maps不会被用户浏览器加载,因此不影响包大小。
破坏风险存在于激进的对象属性名称混淆时。如果代码通过data属性访问DOM元素或使用JSON字符串命名字段,名称混淆可能会重命名公共属性。解决方案——通过mangle.reserved排除或禁用特定命名空间的名称混淆。
CSS压缩比JS压缩带来的收益小(15–25%),但为了符合Core Web Vitals是必需的。CSS压缩器删除空格、合并相同选择器、缩短十六进制颜色(#ff0000 → #f00)并删除未使用的@keyframes。CleanCSS和esbuild是流行的CSS压缩工具。
Source maps(.map文件)将压缩后的代码与原始代码连接起来。在启用了source maps的Chrome DevTools和Safari Web Inspector中,显示原始文件。为了正确运行,source maps必须上传到服务器(即使是在生产环境中)并通过开发者工具启用。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。