Javascript代碼是如何被壓縮的
隨著前端的發(fā)展,特別是 React,Vue 等構(gòu)造單頁應(yīng)用的興起,前端的能力得以很大提升,隨之而來的是項目的復(fù)雜度越來越大。此時的前端的靜態(tài)資源也越來越龐大,而毫無疑問 javascript 資源已是前端的主體資源,對于壓縮它的體積至為重要。
為什么說更小的體積很重要呢:更小的體積對于用戶體驗來說意味著更快的加載速度以及更好的用戶體驗,這也能早就企業(yè)更大的利潤。另外,更小的體積對于服務(wù)器來說也意味更小的帶寬以及更少的服務(wù)器費用。
前端構(gòu)建編譯代碼時,可以使用 webpack 中的 optimization.minimizer 來對代碼進(jìn)行壓縮優(yōu)化。但是我們也需要了解如何它是壓縮代碼的,這樣當(dāng)在生產(chǎn)環(huán)境的控制臺調(diào)試代碼時對它也有更深刻的理解。
如何查看資源的體積
對于我們所編寫的代碼,它在操作系統(tǒng)中是一個文件,根據(jù)文件系統(tǒng)中的 stat 信息我們可以查看該文件的大小。
stat 命令用來打印文件系統(tǒng)的信息:
- $ stat config.js
- File: ‘config.js’
- Size: 3663 Blocks: 8 IO Block: 4096 regular file
- Device: fd01h/64769d Inode: 806060 Links: 1
- Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
- Access: 2020-02-13 13:43:54.851381702 +0800
- Modify: 2020-02-13 13:43:52.668417641 +0800
- Change: 2020-02-13 13:43:52.691417262 +0800
- Birth: -
stat 打印的信息過大,如果只用來衡量體積,可以使用 wc -c
- $ wc -c config.js
- 3663 config.js
如何壓縮代碼體積?
去除多余字符: 空格,換行及注釋
- // 對兩個數(shù)求和
- function sum (a, b) {
- return a + b;
- }
先把一個抽象的問題給具體化,如果是以上一段代碼,那如何壓縮它的體積呢:
此時文件大小是 62 Byte, 一般來說中文會占用更大的空間。
多余的空白字符會占用大量的體積,如空格,換行符,另外注釋也會占用文件體積。當(dāng)我們把所有的空白符合注釋都去掉之后,代碼體積會得到減少。
去掉多余字符之后,文件大小已經(jīng)變?yōu)?30 Byte。 壓縮后代碼如下:
- function sum(a,b){return a+b}
替換掉多余字符后會有什么問題產(chǎn)生呢?
有,比如多行代碼壓縮到一行時要注意行尾分號。 這就需要通過以下介紹的 AST 來解決。
壓縮變量名:變量名,函數(shù)名及屬性名
- function sum (first, second) {
- return first + second;
- }
如以上 first 與 second 在函數(shù)的作用域中,在作用域外不會引用它,此時可以讓它們的變量名稱更短。但是如果這是一個 module 中,sum 這個函數(shù)也不會被導(dǎo)出呢?那可以把這個函數(shù)名也縮短。
- // 壓縮: 縮短變量名
- function sum (x, y) {
- return x + y;
- }
- // 再壓縮: 去除空余字符
- function s(x,y){return a+b}
在這個示例中,當(dāng)完成代碼壓縮 (compress) 時,代碼的混淆 (mangle) 也捎帶完成。但此時縮短變量的命名也需要 AST 支持,不至于在作用域中造成命名沖突。
更簡單的表達(dá):合并聲明以及布爾值簡化
合并聲明的示例如下:
- // 壓縮前
- const a = 3;
- const b = 4;
- // 壓縮后
- const a = 3, b = 4;
布爾值簡化的示例如下:
- // 壓縮前
- !b && !c && !d && !e
- // 壓縮后
- b||c||d||e
這個示例更是需要解析 AST 了
AST
AST,抽象語法樹,js 代碼解析后的最小詞法單元,而這個過程就是通過 Parser 來完成的。
那么 AST 可以做什么呢?
- eslint: 校驗?zāi)愕拇a風(fēng)格
- babel: 編譯代碼到 ES 低版本
- taro/mpvue: 各種可以多端運行的小程序框架
- GraphQL: 解析客戶端查詢
我們在日常工作中經(jīng)常會不經(jīng)意間與它打交道,如 eslint 與 babel,都會涉及到 js 與代碼中游走。不同的解析器會生成不同的 AST,司空見慣的是 babel 使用的解析器 babylon,而 uglify 在代碼壓縮中使用到的解析器是 UglifyJS。
你可以在 AST Explorer[3] 中直觀感受到,如下圖:
那壓縮代碼的過程:code -> AST -> (transform)一顆更小的 AST -> code,這與 babel 和 eslint 的流程一模一樣。
UglifyJS
不要重復(fù)造輪子!
于是我找了一個久負(fù)盛名的關(guān)于代碼壓縮的庫: UglifyJS3[4],一個用以代碼壓縮混淆的庫。那它是如何完成一些壓縮功能的,比如替換空白符,答案是 AST。
webpack 中內(nèi)置的代碼壓縮插件就是使用了它,它的工作流程大致如下:
- // 原始代碼
- const code = `const a = 3;`
- // 通過 UglifyJS 把代碼解析為 AST
- const ast = UglifyJS.parse(code);
- ast.figure_out_scope();
- // 轉(zhuǎn)化為一顆更小的 AST 樹
- compressor = UglifyJS.Compressor();
- astast = ast.transform(compressor);
- // 再把 AST 轉(zhuǎn)化為代碼
- code = ast.print_to_string();
而當(dāng)你真正使用它來壓縮代碼時,你只需要面向配置編程即可,文檔參考 uglify 官方文檔[5]
- {
- {
- ecma: 8,
- },
- compress: {
- ecma: 5,
- warnings: false,
- comparisons: false,
- inline: 2,
- },
- output: {
- ecma: 5,
- comments: false,
- ascii_only: true,
- }
- }
在 webpack 中壓縮代碼
在知道代碼壓縮是怎么完成的之后,我們終于可以把它搬到生產(chǎn)環(huán)境中去壓縮代碼。終于到了實踐的時候了,雖然它只是簡單的調(diào)用 API 并且調(diào)調(diào)參數(shù)。
一切與性能優(yōu)化相關(guān)的都可以在 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
- })
- ]
- }