掌握前端構建的核心科技
- 2022 年 1 月 11 日
- 筆記
這幾年,前端領域內的構建工具層出不窮,除了 webpack,叫得上號的還有 esbuild、rollup、vite、snowpack、swc、parcle 等等。咋一看,不由得感嘆一句「前端好卷啊~」。其實,細想來看,倒不是大家真的卷,而是前端還處在一個高速發展的過程中,很多方面還不成熟,因此會有很多的工具出現來解決不成熟的問題。而當規範、瀏覽器支持方面逐漸成熟,形成足夠的基礎能力以後,這些工具就會逐步退出歷史舞台。
不管是老牌的 webpack,還是新晉的 esbuild、vite 等,如果我們把注意力都集中在這些工具提供的功能上面,只學習如何使用,那麼當下一個構建工具出現的時候,又需要重頭開始學習,難免有「學不動了」的感覺。
本文將會跳出這些工具的具體細節,嘗試總結一條工程構建領域內具有共性的規律。在這條規律的指導下,我們再來觀察構建工具的能力。
時代的大背景
用「時代」這個詞,稍微顯得有點大,但是當說到 JavaScript 模塊化這個問題時,業內確實也是經過了很多年的努力,才明確了一個大方向,現在也基本得到了主流瀏覽器的支持。
眾所周知,JavaScript 在誕生之初,是沒有模塊的概念的。早些年,我們還在寫 jQuery 的時候,會格外注意每個 JS 文件在 HTML 中的順序,被依賴的 JS 要在前面被加載。
隨着前端的發展,Web 上的應用越來越複雜,JS 代碼越來越多,單靠人來維護各個 JS 文件之間的依賴關係已經不可實現。於是在工程實踐層面先後誕生了 AMD、CMD 等規範,對應的實現有 RequireJS 和 SeaJS。在 NodeJS 誕生後,在服務端領域,引入了 CommonJS 規範。為了能讓 JS 模塊既能在瀏覽器運行,又能在服務端運行,又誕生了綜合 AMD 和 CommonJS 的 UMD 規範。這些年,經過大家的不懈努力,JS 的模塊化規範 ES Modules 終於落地,並已經得到大部分主流瀏覽器的支持。
規範不同,語法格式也不同。不同格式的轉換工作理所當然的落到了構建工具的身上。在現在的工程實踐中,webpack 是集大成者,可以根據需要構建成各種模塊格式,是當之無愧的業內主流。但當 ES Modules 被大部分主流瀏覽器支持以後,在新的基礎能力之上成長起來的構建工具就可以丟棄沉重的歷史包袱,輕裝上陣,比如 vite、esbuild 都是基於 ES Modules 規範來構建。由於瀏覽器承擔了一部分打包的工作,新的構建工具不再需要分析所有模塊的代碼且減少了很多對源代碼的分析和轉換,性能上提升了很多。
下面是 vite 官方給出的兩張圖,可以看出基於 ES Modules 構建和基於 Bundle 構建的差異。


因此,從 JavaScript 模塊化發展的角度來看,不同的構建工具,因為利用規範和瀏覽器提供的能力的不同,表現出各方面尤其是性能方面的差異,採用 ES Modules 的構建工具性能更加卓著。
前端構建的核心能力
webpack 是一款出色的構建工具,功能非常強大,我們可以通過配置處理各種場景下的構建問題。但是 webpack 的上手成本也很高,豐富的功能導致文檔非常龐大,初學者很難看懂。很多人都是使用了 webpack 很久之後,才慢慢理解的 webpack。
其實對於工程構建而言,有三個核心能力,大部分功能配置都是圍繞這三個核心能力來展開,分別是:打包器(Bundler)、轉換器(Transformer)和壓縮器(Minimizer)。
打包器
打包器將項目代碼打包成一到多個 JS 文件,每一個文件為一個包(Bundle),裏面包含當前模塊和依賴的其他模塊。比如在 webpack 的輸出結果中,通常包含三個文件:runtime.js、manifest.js 和 main.js 文件。
隨着 ES Modules 的發展,瀏覽器將會承擔起越來越多的模塊加載的工作,且效果也會越來越好,將來我們對打包的需求會越來越弱。現在 vite 在開發階段,已經可以不再打包 JS 文件了,直接使用 ES 模塊,參考上圖。具體細節可以在 vite 官方文檔中找到說明。
轉換器
在前端生態中,典型的轉換器場景就是 Babel,大家也都非常熟悉。Babel 將最新的 JS 語法轉換成 es5 等瀏覽器支持的語法,讓我們可以自由的使用最新的語法標準而不用擔心瀏覽器兼容問題。
一個典型的轉換器的工作過程包含三部分:
- Parse(解析):將源代碼轉換成更加抽象的表示方法(抽象語法樹 AST)
- Transform(轉換):對(抽象語法樹)做一些特殊處理,讓它符合編譯器的期望
- Generate(代碼生成):將第二步經過轉換過的(抽象語法樹)生成新的代碼
轉換器的核心是抽象語法樹 AST,AST 的應用非常廣泛,比如:
- 編輯器的錯誤提示、代碼格式化、代碼高亮、代碼自動補全;
- elint、pretiier 對代碼錯誤或風格的檢查;
- webpack 通過 babel 轉譯 javascript 語法;
在工程構建方面,不管是 webpack,還是 esbuild、vite 都有轉換器,區別是轉換工作的多少。在 esbuild 的文檔中,我們可以看到,esbuild 只在詞法分析、代碼轉換和代碼壓縮時需要處理 JavaScript AST,而其他構建工具,因為需要在多個庫中傳遞數據,因此對代碼的處理存在多個轉換過程,比如 string → TS → JS → string,然後 string → JS → old JS → string,再然後 string → JS → minified JS → string,對於資源的使用更多,性能也會更差。
除了 JavaScript AST 以外,很多構建工具都有 loader 的概念,用於處理圖片、css 文件等各種各樣的資源。這其實也是一種轉換器,將其他靜態資源轉換成了 JS 模塊。
壓縮器
說到壓縮大家都非常熟悉,比如代碼壓縮、圖片壓縮等,業內也有非常多的工具庫可以使用。
目前主流的代碼壓縮工具有 terser、uglify-js 等。比如 terser 就是 webpack 的內置代碼壓縮工具。構建領域的新秀 esbuild 自己實現了代碼壓縮的能力,性能要甩開其他壓縮工具至少一個數量級。
社區里有一個對比各個壓縮工具性能的倉庫,可以對比感受下各個壓縮工具的性能差距。
這裡貼一張壓縮 react 17 源代碼的性能對比圖。

我們應該怎麼辦
到這裡,我們並沒有講什麼實現細節,但是相信大家對構建這塊心裏已經有了一個基本的模型,知道有哪些核心問題了。
現在我們來說說,在面對層出不窮的構建工具時,我們應該怎麼辦。是每個構建工具都學一遍嗎?肯定不是。其實,還是要從核心問題出發,看看這些構建工具具體解決了什麼問題,是否已經足夠成熟可以在生產環境中使用了。
比如,esbuild 還在快速迭代過程中,很多周邊生態還不完善,直接在生產環境中使用風險比較大。但是我們可以在 webpack 使用 esbuild 的壓縮能力來提升構建的性能。
vite 也在高速迭代過程中,已經具備了初步的生產使用的基礎。vite 內部也大量使用了 esbuild 的能力,比如預構建、代碼壓縮等。vite 我們可以保持適當關注,不僅僅因為它是 vue 3 的默認構建工具,同時也給我們提供了 webpack 以外的其他選擇。
常見面試知識點、技術方案分析、教程,都可以掃碼關注公眾號「眾里千尋」獲取,或者來這裡 //everfind.github.io/posts/ 。



