1. 程式人生 > 程式設計 >javascript 程式碼是如何被壓縮的示例程式碼

javascript 程式碼是如何被壓縮的示例程式碼

隨著前端的發展,特別是 ReactVue 等構造單頁應用的興起,前端的能力得以很大提升,隨之而來的是專案的複雜度越來越大。 此時的前端的靜態資源也越來越龐大,而毫無疑問 javascript 資源已是前端的主體資源,對於壓縮它的體積至為重要。

為什麼說更小的體積很重要呢:更小的體積對於使用者體驗來說意味著更快的載入速度以及更好的使用者體驗,這也能早就企業更大的利潤。另外,更小的體積對於伺服器來說也意味更小的頻寬以及更少的伺服器費用。

前端構建編譯程式碼時,可以使用 webpack 中的 optimization.minimizer 來對程式碼進行壓縮優化。但是我們也需要了解如何它是壓縮程式碼的,這樣當在生產環境的控制檯除錯程式碼時對它也有更深刻的理解。

如何檢視資源的體積

對於我們所編寫的程式碼,它在作業系統中是一個檔案,根據檔案系統中的 stat 資訊我們可以檢視該檔案的大小。

stat 命令用來列印檔案系統的資訊:

$ 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

如何壓縮程式碼體積?

去除多餘字元: 空格,換行及註釋

// 對兩個數求和
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 a+b}

在這個示例中,當完成程式碼壓縮 ( compress ) 時,程式碼的混淆 ( mangle ) 也捎帶完成。 但此時縮短變數的命名也需要 AST 支援,不至於在作用域中造成命名衝突。

更簡單的表達:合併宣告以及布林值簡化

合併宣告的示例如下:

// 壓縮前
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: 校驗你的程式碼風格
  • babel: 編譯程式碼到 ES 低版本
  • taro/mpvue: 各種可以多端執行的小程式框架
  • GraphQL: 解析客戶端查詢

我們在日常工作中經常會不經意間與它打交道,如 eslint 與 babel ,都會涉及到 js 與程式碼中游走。不同的解析器會生成不同的 AST,司空見慣的是 babel 使用的解析器 babylon ,而 uglify 在程式碼壓縮中使用到的解析器是 UglifyJS 。

你可以在 AST Explorer [3] 中直觀感受到,如下圖:

javascript 程式碼是如何被壓縮的示例程式碼

那壓縮程式碼的過程:code -> AST -> (transform)一顆更小的 AST -> code,這與 babel 和 eslint 的流程一模一樣。

javascript 程式碼是如何被壓縮的示例程式碼

UglifyJS

不要重複造輪子!

於是我找了一個久負盛名的關於程式碼壓縮的庫: UglifyJS3 [4] ,一個用以程式碼壓縮混淆的庫。那它是如何完成一些壓縮功能的,比如替換空白符,答案是 AST。

webpack 中內建的程式碼壓縮外掛就是使用了它,它的工作流程大致如下:

// 原始程式碼
const code = `const a = 3;`

// 通過 UglifyJS 把程式碼解析為 AST
const ast = UglifyJS.parse(code);
ast.figure_out_scope();


// 轉化為一顆更小的 AST 樹
compressor = UglifyJS.Compressor();
ast = ast.transform(compressor);

// 再把 AST 轉化為程式碼
code = ast.print_to_string();

而當你真正使用它來壓縮程式碼時,你只需要面向配置程式設計即可,文件參考 uglify 官方文件 [5]

{
 {
  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,output: {
     ecma: 5,sourceMap: true
  })
 ]
}

參考資料 [1]

shfshanyue/blog: https://github.com/shfshanyue/blog

[2]

前端工程化系列: https://github.com/shfshanyue/blog/tree/master/frontend-engineering

[3]

AST Explorer: https://astexplorer.net/

[4]

UglifyJS3: https://github.com/mishoo/UglifyJS2

[5]

uglify 官方文件: https://github.com/mishoo/UglifyJS2#parse-options

到此這篇關於javascript 程式碼是如何被壓縮的的文章就介紹到這了,更多相關js程式碼壓縮內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!