# javascript 代码是如何被压缩的
在 jquery
时代,当我们引用它的 CDN 地址时,在线上需要引用的是 jquery.min.js
。
在现代前端时代,当我们对资源进行打包后,同样需要对代码进行压缩。在生产环境的 JS 仅有一行代码,变量也被压缩为单字符,体积大大减小。
我们是如何对 JS 代码进行压缩的呢?
# 如何压缩代码体积?
这里介绍几种关于压缩代码体积的几种思路
# 去除多余字符: 空格,换行及注释
// 对两个数求和
function sum (a, b) {
return a + b;
}
此时文件大小是 62 Byte
, 一般来说中文会占用更大的空间。
多余的空白字符会占用大量的体积,如空格,换行符,另外注释也会占用文件体积。当我们把所有的空白符合注释都去掉之后,代码体积会得到减少。
去掉多余字符之后,文件大小已经变为 30 Byte
。 压缩后代码如下:
function sum(a,b){return a+b}
替换掉多余字符后会有什么问题产生呢?
有,比如多行代码压缩到一行时要注意行尾分号。 这就需要通过以下介绍的 AST 来解决。
# 压缩变量名:变量名,函数名及属性名
function sum (first, second) {
return first + second;
}
如以上 first
与 second
在函数的作用域中,在作用域外不会引用它,此时可以让它们的变量名称更短。但是如果这是一个 module
中,sum
这个函数也不会被导出呢?那可以把这个函数名也缩短。
// 压缩: 缩短变量名
function sum (x, y) {
return x + y;
}
// 再压缩: 去除空余字符
function s(x,y){return x+y}
在这个示例中,当完成代码压缩 (compress
) 时,代码的混淆 (mangle
) 也捎带完成。 但此时缩短变量的命名也需要 AST 支持,不至于在作用域中造成命名冲突。
# 解析程序逻辑:合并声明以及布尔值简化
通过分析代码逻辑,可对代码改写为更精简的形式。
合并声明的示例如下:
// 压缩前
const a = 3;
const b = 4;
// 压缩后
const a = 3, b = 4;
布尔值简化的示例如下:
// 压缩前
!b && !c && !d && !e
// 压缩后
!(b||c||d||e)
# 解析程序逻辑: 编译预计算
在编译期进行计算,减少运行时的计算量,如下示例:
// 压缩前
const ONE_YEAR = 365 * 24 * 60 * 60
// 压缩后
const ONE_YAAR = 31536000
以及一个更复杂的例子,简直是杀手锏级别的优化。
// 压缩前
function hello () {
console.log('hello, world')
}
hello()
// 压缩后
console.log('hello, world')
# AST
AST
,抽象语法树,js 代码解析后的最小词法单元,而这个过程就是通过 Parser 来完成的。
那么 AST 可以做什么呢?
- eslint: 校验你的代码风格
- babel: 编译代码到 ES 低版本
- taro/mpvue: 各种可以多端运行的小程序框架
- GraphQL: 解析客户端查询
我们在日常工作中经常会不经意间与它打交道,如 eslint
与 babel
,都会涉及到 js
与代码中游走。不同的解析器会生成不同的 AST,司空见惯的是 babel 使用的解析器 babylon
,而 uglify
在代码压缩中使用到的解析器是 UglifyJS
。
你可以在 AST Explorer (opens new window) 中直观感受到,如下图:
那压缩代码的过程:code -> AST -> (transform)一颗更小的AST -> code,这与 babel
和 eslint
的流程一模一样。
# uglify、terser 与 swc
一个久负盛名的关于代码压缩的库: uglify (opens new window),一个用以代码压缩混淆的库。但它有一个致命弱点,不支持 ES6
。
一个更加适应现代化前端用以代码压缩的库 terser (opens new window) 诞生了,它来自于 uglify,与它保持一致的 API,但是它对 ES6
有更好的支持,同时也是 webpack 内置进行代码压缩的库。
那它是如何完成一些压缩功能的,答案是 AST。
webpack
中内置的代码压缩插件就是使用了 terser
,看一段它工作的代码:
const { minify } = require('terser')
const code = 'function add(first, second) { return first + second; }'
const result = await minify(code, { sourceMap: true })
console.log(result.code)
console.log(result.map)
而当你真正使用它来压缩代码时,你只需要面向配置编程即可,文档参考 uglify 官方文档 (opens new window)
{
{
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
output: {
ecma: 5,
comments: false,
ascii_only: true,
}
}
# 在 webpack 中压缩代码
在知道代码压缩是怎么完成的之后,我们终于可以把它搬到生产环境中去压缩代码。终于到了实践的时候了,虽然它只是简单的调用 API 并且调调参数。
一切与性能优化相关的都可以在 optimization
中找到,TerserPlugin
是一个底层基于 uglifyjs
的用来压缩 JS 的插件。
optimization: {
minimize: isEnvProduction,
minimizer: [
new TerserPlugin({
terserOptions: {
parse: {
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
sourceMap: true
})
]
}
打包 →