新一代前端構建工具彙總

語言: CN / TW / HK

說起前端構建,大家一定首先想到 Webpack,確實它是前端構建的老大哥了,大而全,什麼場景都能滿足,社群生態爆炸。但是社群裡也有許多其他優秀的構建工具,他們或許不如 Webpack 那樣“包治百病”,但他們都有一些獨特的優勢,如果在一些特定的場景你覺得使用 Webpack 太臃腫了,那你或許可以考慮下面的一些工具。

Parcel

一個號稱「「0 配置」」的打包工具,開箱即用,同時預設使用 Worker 程序充分發揮多核 cpu 優勢來提升構建速度,因此在打包效率上還是不錯的,而且 Parcel 2.0 在 SWC 基礎上用 Rust 改寫了 JS/CSS Transformer,進一步提升了構建效率。

Parcel 程式碼實現得非常「模組化」,有非常多內建的外掛來完成各種各樣的工作,使用者可以針對自己的需求來使用不同的內建外掛,只要在 .parcelrc 檔案裡配置即可,parcel 會自動讀取這個配置檔案,不過要注意 .parcelrc 是 JSON5 格式的檔案。

檔案型別

與 Webpack 不同的是,在 Parcel 中,所有檔案都是一等公民,一視同仁,因此不需要使用者去針對不同型別的檔案配置各種 Loader,Parcel 會幫你做好不同型別檔案的處理。

  • 支援 JS/TS/JSX/TSX,Parcel 2.0 開始使用了 Rust 實現的 JS Transformer,能更高效地進行轉譯,同時也支援轉譯到 ES5,對於 React17 新的 JSX 也能支援。另外 Minification,Tree Shaking 等也是支援的。
  • 支援 CSS,功能基本上對齊 CSS Loader,還支援各種 CSS 預處理語言,支援 Tree Shaking,Minification 等。另外支援以文字形式引入 CSS 資源,方便使用者手動將 css 放入 Style Tag 中,值得一提的是,Parcel2.0 還用 Rust 實現了 CSS 的 Transformer。
  • 支援 HTML。
  • 支援 Vue,完全支援 Vue3 語法。
  • 支援圖片,豐富的圖片檔案處理,支援圖片型別的轉換以及裁剪。
  • ...

構建特性

支援 Code Splitting,不過和 esbuild 一樣只能支援比較有限的分割邏輯,被多個入口引用的共用模組或者使用 import() 動態引入的模組會被分割成單獨的 Chunk。

  • 支援 Tree Shaking。
  • 支援 Scope Hoist。
  • 支援 Minification。
  • 支援 Compression,可生成 Gzip 和 Brotli 兩種壓縮格式的產物。
  • 支援內聯 Bundle,即可以以文字或者其他格式引入轉譯後的資源,例如上面提到的以文字格式引入編譯後的 CSS 檔案,亦可以直接以 dataURL 的格式引入二進位制檔案等。
  • 支援開發階段的 DevServer,HMR 等。
  • 支援瀏覽器快取,產物檔名預設帶上檔案內容 hash。
  • 支援差異化構建,預設會同時構建出 ESM 的產物以及非 ESM 的產物。
  • ...

優點

零配置,告別繁瑣的工程化配置,能夠滿足大多數場景。在 JS 和 CSS 的轉譯上使用了 Rust ,效率上會有所提升。

缺點

擴充套件性不強,幾乎沒有類似 Webpack 的那種開放性外掛特性,因此如果遇到 Parcel 現階段無法實現或有 Bug 的東西,使用者無能為力,只能等 Parcel 去補齊。

使用對比

打包 React + Threejs 專案,Webpack:

Parcel 首次構建:

Parcel 非首次構建:

Parcel 每次構建完都會生成 .parcel-cache 檔案記錄各種模組的依賴關係,可以大大節省後續構建的用時,不過這個快取能力在 Webpack 5.0 也內建了,不算是什麼獨特的能力。

在產物體積上,雙方大致打平。

總結

目前 Parcel 最大的賣點就在於無需配置,使用體驗也確實不錯,效能方面在使用 Rust 改造後相信未來也能得到更大的提升,開箱即用可以滿足許多場景,但是封裝性好帶來的副作用就是擴充套件性差,因此對於有大量定製化構建需求的大型專案來說 Parcel 現階段或許不算是一個很好的選擇。

Rollup

Rollup 是當前流行的庫打包器,它比 Webpack 晚幾年出現,也是在 ESM 之後出現的,主打的特點是能夠支援並且提倡開發者使用 ESM 模組語法進行開發。

檔案型別

幾乎只支援 JS,其他型別的檔案均需要使用外掛來處理。

特點

Rollup 推崇 ESM 模組標準開發,這個特點也是藉助瀏覽器對 ESM 的支援,Rollup 打包的產物對比 Webpack 會乾淨很多。例如同一個專案打包產物:

Webpack 產物:

Rollup 產物:

可以看到 Webpack 產物裡是有大量的諸如 __webpack_require__之類的程式碼,這些都是 Webpack 自身 Polyfill 的在執行時的模組載入,就是為了讓產物程式碼在所有瀏覽器都能執行,因為 wepack 出現的時候還沒有 ESM ,當時的模組標準還很混亂,Webpack 抹平了差異。用 IIFE 實現模組之間的隔離,並且用__webpack_require__ __webpack_exports__ 等 Polyfill 實現在瀏覽器環境裡模擬 CJS 模組載入,所以我們用 Webpack 打包後的程式碼實際上更像是跑在 Webpack 給我們實現的“虛擬 Runtime”上。

而 Rollup 誕生在 ESM 模組標準出來之後,所以 Rollup 完全遵從 ESM 標準,也就不需要像 Webpack 那樣做很多 Runtime Polyfill,完全把程式碼交給瀏覽器執行。對於一些專案裡依賴的老舊的 CJS 的包,也可以通過外掛來對這些依賴處理。

「Rollup」 「精簡的產物在體積上也是要比」 「webpack」 「來的小。」

另外對於多入口打包或動態引入的包也會做分包,我們可以直接使用 [output.manualChunks](http://rollupjs.org/guide/en/#outputmanualchunks) 來自定義分包。

外掛系統

Rollup 提供了從 讀取引數 到 構建 到 輸出產物共計 25 種 Hook,足以滿足絕大多數場景,而且目前社群裡的外掛數量也非常多,幾乎該有的都有了,因為 Rollup 本身只認識 Javascript,所以實際使用過程中我們會需要配置比較多的外掛來滿足我們的場景,尤其是專案檔案型別比較多樣的情況下。

總結

Rollup 總體而言是非常優秀的打包工具,產物精簡,符合 ESM 標準,豐富的外掛系統,社群生態也很不錯,是個很現代化 Web Bundler。不過相應的,他需要支援 ESM 標準的瀏覽器,因此對於低版本瀏覽器也實在沒辦法(願天堂沒有低版本瀏覽器:pray:)。

因此對於打包 Web App,使用 Webpack 還是主流,幹啥都行,哪兒都能跑。

打包庫,推薦使用 Rollup,反正產物最終也是當成依賴引入,瀏覽器相容性的事情交給引入方去解決了。

Snowpack

Snowpack 主打的是 Unbundle,極速的開發體驗,在生產環境也同樣能依賴 Rollup 打包出產物。

他主要的做法就是利用了瀏覽器對 ESModule 的支援,而對於專案用到的依賴,為了防止依賴沒采用 ESM 模組規範,Snowpack 會把從依賴入口開始把依賴打包成一個檔案,並確保產物是符合 ESM 標準且可以執行在瀏覽器中的,而這裡主要是依賴了 esinstall 庫,esinstall 又是通過 rollup 來做這個事情的。

檔案型別

  • JavaScript (.js, .mjs)。
  • TypeScript (.ts, .tsx)。
  • JSON (.json)。
  • JSX (.jsx, .tsx),預設使用 ESBuild 來轉譯,雖然 ESBuild 已經有辦法處理新的 JSX 語法了,但 snowpack 似乎沒有相容上,需要降級到 babel 來處理。
  • CSS (.css):對於預處理語言似乎僅支援 Sass,對於程式碼裡 import 進來的 css 檔案,snowpack 會把它處理成 .proxy.js 字尾的 js 檔案,且在 js 檔案裡的邏輯就是建立 style 標籤把 css 內容填進去。
  • CSS Modules (.module.css)。
  • Images & Assets (.svg, .jpg, .png, etc.)。
  • WASM (.wasm)。

外掛系統

Snowpack 的外掛系統也是利用 snowpack 執行的生命週期中提供的 hooks。且這套是沿襲了 Rollup 的那套外掛系統。

  • load: 這個 hook 會在載入特定字尾檔案的時候觸發,通常用於將瀏覽器無法處理的檔案型別轉化成瀏覽器能執行的檔案,除了可以更改檔案內容外,也可以更改最終輸出的檔案型別。例如外掛@snowpack/plugin-vue 對 .vue 檔案的處理就是使用這個 hook 來做的。
  • transform: 在所有檔案都過完 load 之後,會來到 transform hook,這裡可以對檔案內容進行更改。
  • optimize: snowpack 本身是不做打包的,但前面說到它也可以支援生產環境的打包,這裡就是依賴外掛來做的打包,而外掛則是利用的 optimize 這個 hook,在這個 hook 裡可以使用者指定打包工具例如 webpack, rollup, parcel 等進行程式碼的 bundle。

優點

Unbundle 可以提供很快速的開發體驗,另外外掛介面設計不錯,開發者可以藉此擴充套件許多應用場景。

缺點

官方文件不是特別的完善,對於一些配置項沒有很清楚的解釋,而且專案維護者沒什麼精力去維護這個專案,導致 Snowpack 發展比較緩慢。

另外外掛部分也有一些不足,主要表現為社群活躍度不夠,生態不是很完善,可能缺少處理某些場景的外掛,甚至一些現存的外掛在實現上也不是很完善。使用體驗不夠好。

總結

由於是採用 Unbundle 的,Snowpack 本身做的東西就不如 Bundle 方案的那些工具多,實際上它主要要做的事情就是幫我們處理好專案依賴,讓那些專案依賴能跑在瀏覽器上就行了。因此它也比較輕量,但還是上面說到的未來發展的問題,目前更新緩慢,未來會不會繼續維護也成問題。

Esbuild

它是 Figma 的 CTO 主導,使用 Go 語言編寫的打包工具,熟悉 Vite 的同學對它應該不陌生,Vite 中使用 esbuild 做了許多事情,例如轉譯 JSX, TS, TSX;預編譯模組等。

esbuild 提供兩類 API:Transform 和 Build。

Transform

  • 支援轉譯的內容型別有:JS、JSX、TS、TSX、JSON、CSS、二進位制、Text、Base64,不同型別的內容需要使用不同的 loader (這裡指 esbuild 內建的 loader)。
  • 支援壓縮。
  • 支援 SourceMap。
  • 支援指定 Target:轉譯成 js 或 css 時可指定目標語法版本,預設 esnext,即使用最新的特性。
  • 支援 Tree shaking:主要針對 declaration-level。

Build

Build 實際上是包含了 Transform 過程的,因此在 Transform 中可以配置的欄位都能在 Build 中配置。

  • 支援 Bundle:預設不啟用 Bundle。
  • 支援 Watch:監聽檔案變動,重新構建。
  • 支援 DevServer。
  • 支援 Code Splitting。
  • 支援自定義JS plugin:社群已經有不少 plugin 了 http://github.com/esbuild/community-plugins。

優點

不用多說,就是快,壓縮效率也不錯。

缺點

  1. 沒有提供 AST 級別的 API,使用者無法干涉 Transform 過程,加上 Transform 不能完全支援轉譯到 ES5 語法,如果程式碼需要執行到低版本瀏覽器或者專案有依賴 Babel Plugin 的話,就不要用 esbuild 了。
  2. 對 CSS 的支援較為單一,僅支援純 CSS,CSS Modules 在規劃中了,對於 Less,PostCSS 等預處理語言則需要用 Plugin 來處理。
  3. Code Splitting 的功能尚未完善,目前只有當產物是 ESM 的時候才能使用這個特性,而且還有一些 import 順序導致的問題。

對 TS 的支援也不夠完全,且對 React 17 新的 JSX 處理也還不支援。

雖然有 Plugin 機制,但是提供的鉤子數量不多,功能也不夠強大,並且 JS Plugin 會在一定程度上拖慢效率。

總結

目前在業務專案裡單獨拿 esbuild 做構建或者轉譯其實都有不少場景是無法支援到的,不過 esbuild 也在不斷完善,我們需要揚之長避之短,現階段在 library 打包場景還是可以用上 ESBuild 的,或者業務專案裡如果沒有依賴太多的 Babel 外掛的情況下倒是可以利用一下 esbuild 的 Transform 能力,比如像 Vite 那樣。

目前前端社群也有使用 esbuild 結合 Webpack 的實踐,也正是使用 esbuild 的 Transform 能力作為JS/TS/JSX/TSX 的 loader http://github.com/privatenumber/esbuild-loader。

SWC

全稱 Speedy Web Complier,實際上它並不是構建工具,它是基於 Rust 實現的 Complier 工具,但是似乎也有做 Bundle 的規劃,這裡順帶一起介紹了。

得益於 Rust 語言的高效,SWC 的 transform 效率最高可以是 Babel 的 70 倍(官網說的)

SWC is 「20x faster than Babel」 on a single thread and 「70x faster」 on four cores.

SWC官方給出以下幾種包:

  • @swc/cli: swc 的命令列工具,可以通過命令列直接對檔案進行轉譯。
  • @swc/core: swc 的 js 庫,可以在 node 環境中執行。
  • @swc/wasm-web: swc 的 wasm 版,可以在瀏覽器環境中執行。
  • @swc/jest: 服務 Jest 框架。

能力一覽

  • 支援轉譯 JavaScript、TypeScript、J(T)SX、值得注意的是,它還支援轉譯 React 17 版本的新 JSX,也能支援「轉譯到 ES5 語法」。
  • 支援 ESM 或 CJS 等各種模組標準。
  • 支援 Minification。
  • 支援 SourceMap。
  • 支援外掛。
  • ...

SWC 也有自己的外掛系統,並且同時「開放了」 「Rust」 「側和」 「JS」 「側的」 「AST」 「級別的」 「API」,所以目前來說 Rust 實際上可以做到任何 Babel 能做的事情。但是目前使用者量還不夠大,可能會存在一些 bug,生態也還不夠完善。

但是從它開放了 Rust 側的 API 這點來說還是很誘人的,使用 Rust 開發的外掛在執行效率上比 JS 必然會高出不少。

比起 ESBuild, SWC 是更細粒度的一個工具,可定製化程度也更大,因此目前市面上許多工具譬如 Next.js、Parcel、Deno 都選擇基於 SWC 來做程式碼的轉譯。

優點

除了快以外,關鍵 SWC 還開放了 Rust 側 AST 級別的 API,在考慮拓展性的同時還把轉譯效率上限提高了,可謂是殺手鐗了。

缺點

  • 目前使用者量還不夠大,深入開發使用的時候難免踩坑。
  • 生態不夠完善,短期內想要替代 Babel 還有些困難。
  • Rust 學習困難。
  • ...

總結

作為 Transformer,SWC 的潛力很大,難怪眾多工具都押寶 SWC。但是目前來說 SWC 還處於比較早期,會有一些坑要踩,並且如果單純使用 JS 來開發外掛會是轉譯效率大打折扣,因為涉及到不同語言之間 AST 的轉換,具體可以看這裡 http://github.com/swc-project/swc/issues/2175,因此要發揮最好的效果勢必要學習 Rust,這個學習曲線可能比較陡峭。

另外 SWC 也提供了 swc-loader 用作 Webpack 的 loader,有興趣可以嘗試一下。

Vite

最後簡要介紹一下 Vite,許多人對他也不陌生了。與 snowpack 類似,他開發階段採用 unbundle 模式,並且使用 esbuild 做依賴預構建(snowpack 是用的 rollup),生產階段利用 rollup 做構建。至於跟 snowpack 的區別和優劣,官網也有介紹,這裡就不贅述了。http://cn.vitejs.dev/guide/comparisons.html#snowpack

但毋庸置疑的是,Vite 比 snowpack 更成熟,未來發展趨勢也更好,Vite 目前可以穩定用於生產環境的。前面我們說到 rollup 會需要高版本瀏覽器支援,那使用 rollup 做生產構建的 Vite 是不是也會受同樣限制?實際上 Vite 提供了[@vitejs/plugin-legacy](http://github.com/vitejs/vite/tree/main/packages/plugin-legacy) 外掛來讓產物可以執行在低版本瀏覽器上,保證了它作為成熟可用於生產環境的工具的穩定性。

為什麼不展開介紹,就是因為他已經能滿足幾乎場景了,該有的能力都有。當前關鍵還是看社群生態,現在 Vite 大大小小的外掛也有上百種,未來經過更多實際業務的考驗想必能跟 webpack 碰一碰。

「參考資料」:

  • http://esbuild.github.io/。
  • http://swc.rs/docs/getting-started。
  • http://jishuin.proginn.com/p/763bfbd6c888。
  • http://parceljs.org/blog/rc0/。
  • http://juejin.cn/post/7054752322269741064。