2022 年,Babel vs TypeScript,誰更適合程式碼編譯

語言: CN / TW / HK

在現代 Web 應用中,為了讓程式碼能在生產環境高效能的運營,原始碼往往需要被編譯打包,進行死碼刪除,程式碼轉換等處理。

Babel 和 Typescript 是目前最常用的兩個編譯器,本文主要討論兩者的區別,幫助你為專案選擇最佳工具。

介紹

Babel

Babel 是一個 JS 編譯器,能將現代 ES6+ 語法和特性轉換為向後相容語法,以便能夠執行在當前和舊版本的瀏覽器或其他環境中。擁有語法轉換,Polyfill,原始碼轉換等能力,

TypeScript

TS 是目前最常用的程式語言之一,是加了型別系統的 JS,能夠幫助在開發時規避一些錯誤。

TS 有自己的編譯器,可將 ​ ​.ts​ ​ 檔案轉換為 ​ ​.js​ ​ 檔案,然後執行在瀏覽器、Node.js 等任何能執行 JS 的環境中。

兩者對比

雖然同為編譯器,但也有一些區別。

Babel 無法做到型別檢查

TS 在編譯時可以對程式碼進行型別檢查,而 Babel 不支援型別檢查。

可以使用 ​ ​tsc -- noEmit​ ​ 單獨進行 TS 型別檢查

TS 無法自動 polyfill

Babel 和 TS 兩者都只是編譯器,真正完成 API polyfill 的是 ​ ​core-js​ ​。

​core-js​ ​ 是一套模組化的 JS 標準庫,提供了 ​ ​polyfill​ ​ 的核心實現。

Babel 的 ​ ​@babel/polyfill​ ​ 模組包含了 ​ ​core-js​ ​ 和 ​ ​regenerator-runtime​ ​ 來模擬完整的 ES2015+ 環境。因此可以說 Babel 自帶了 polyfill。

​regenerator-runtime​ ​ 是 ​ ​generator​ ​ 以及 ​ ​async/await​ ​ 的執行時依賴。

而 TS 只能通過 ​ ​tsconfig​ ​ 的 ​ ​target​

控制編譯為對應 ECMAScript 版本的語法。比如 const/let 變 var,箭頭函式變 function,async+await 變

Promise.then 這些,不會引入內建物件的擴充套件,比如你要執行的瀏覽器不支援 Promise,編譯後也不會帶一個完整的 Promise

polyfill,想 polyfill 還是得配合 ​ ​core-js​ ​。

Babel 擴充套件性更強

Babel 是自定義程式碼轉換的不二之選,而且社群生態豐富,有各種各樣的外掛可以優化你的程式碼。

而 TS 只支援自己的 ​ ​Transformer API​ ​,生態遠遠比不上 Babel 外掛,知道的人也比較少,能力也更少。

裝飾器(Decorator)差異

隨著 TS 和 ES6 裡引入了類,裝飾器提案 proposal-decorators [1] 誕生了,是我們最熟悉的老朋友。但是此裝飾器非彼裝飾器,歷時多年來該提案已經走到了第三版,仍然卡在 stage-2。

首先我們需要知道,JS 與 TS 中的裝飾器不是一回事,JS 中的裝飾器目前依然停留在 stage-2 階段,並且目前版本的草案與 TS 中的實現差異相當之大( TS 是基於第一版,JS 目前已經第三版了 ),所以二者最終的裝飾器實現必然有非常大的差異。

其次,裝飾器不是

TS 所提供的特性(如型別、介面),而是 TS 實現的 ECMAScript 提案(就像類的私有成員一樣)。TS 實際上只會對 stage-3

以上的語言特性提供支援,但因為一些原因,當 TS 引入裝飾器時,JS 中的裝飾器依然處於 stage-1 階段。 TS 的裝飾器其實是 JS 裝飾器提案的第一版

Babel 編譯裝飾器需要使用 ​ ​@babel/plugin-proposal-decorators​ ​ 外掛,通過 ​ ​version​ ​ 欄位分別支援三版提案:

  • "2021-12"
  • "2018-09"(預設)
  • "legacy"

Babel 預設按第二版進行編譯,如果要與 TS 編譯行為一致(也就是第一版),需要傳入 ​ ​"version": "legacy"​ ​。

Babel 支援更多語言特性

從上面裝飾器的例子還可以看出,TS 只會對 stage-3 以上的語言特性提供支援,不支援還在草案階段的特性。

而 Babel 的 ​ ​preset-env​ ​ 支援所有標準特性,還能通過各種 ​ ​@babel/plugin-proposal-<語言特性>​ ​ 外掛來支援更多還未進入標準的特性。

兩者編譯速度相當

在效能上,兩者差別不大。這裡大家可能會有疑問:“Babel 少了型別檢查的步驟,編譯速度應該會比 TS 快才對啊”。

根據 swc-node [2] 文件的 benchmark 我們可以看到, 在關閉型別檢查的情況下,TS 的編譯速度是比 Babel 快的

esbuild x 510 ops/sec ±1.28% (88 runs sampled)
@swc-node/core x 438 ops/sec ±1.00% (88 runs sampled)
typescript x 28.83 ops/sec ±10.20% (52 runs sampled)
babel x 24.21 ops/sec ±10.66% (46 runs sampled)
Transform rxjs/AjaxObservable.ts benchmark bench suite: Fastest is esbuild

而且還可以藉助第三方外掛 fork-ts-checker-webpack-plugin [3] 來提速型別檢查過程(放到單獨的程序中),所以兩者的整體效能其實相差不大。

Babel 產物體積更小

因為 TS 無法自動 polyfill,藉助了 ​ ​core-js​ ​ 也無法做到按需 polyfill。

而配置 Babel 的 ​ ​@babel/preset-env​ ​ 外掛:

​useBuiltIns: "usage"​
​targets: <需要相容的瀏覽器>​

可以根據編譯目標和專案的 API 使用情況來精準新增 polyfill,這會大大降低包的體積。

使用 ​ ​useBuiltIns: "usage"​ ​ 會在全域性新增 polyfill,這會汙染全域性環境。可以使用 ​ ​@babel/plugin-transform-runtime​ ​ 外掛為庫的程式碼提供一個沙盒環境,把 polyfill 變成模組化的引入,程式碼重用的同時避免全域性汙染。

總結

綜上,兩者都有各自的編譯處理方式,整體看下來,Babel 唯一的缺點就是沒有型別檢查,但可以使用 ​ ​tsc --noEmit​ ​ 單獨檢查型別。

因此,如果專案中:

  • 已有 Babel 和 TypeScript,最好 使用 Babel 編譯程式碼,使用 TS 進行型別檢查和生成 ​​.d.ts​ ​ 檔案

TS 文件 [4] 中也更推薦這種方式,但如果構建輸出檔案和原始碼差別不大的話,可直接使用 TS 編譯。

  • 只有 TypeScript,可以保持現狀,將來如果需要 Babel 提供的能力,可以 將 TS 編譯輸出的 JS 再使用 Babel 編譯 ,或者 直接使用 Babel 編譯 TS 檔案
  • 只有 Babel,推薦 使用 TypeScript 對專案進行漸進式改造,保證專案前端質量。

參考

  • https://blog.bitsrc.io/babel-vs-typescript-in-2022-b8e859a9fefc
  • https://ts.xcatliu.com/
  • https://jishuin.proginn.com/p/763bfbd3ba87
  • https://jishuin.proginn.com/p/763bfbd5eecf
  • https://juejin.cn/post/6968636129239105549
  • https://blog.logrocket.com/babel-vs-typescript/
  • https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html#babel-vs-tsc-for-typescript
  • https://www.zhihu.com/question/322722786

參考資料

[1] proposal-decorators: https://github.com/tc39/proposal-decorators

[2] swc-node: https://github.com/Brooooooklyn/swc-node#benchmark

[3] fork-ts-checker-webpack-plugin: https://github.com/TypeStrong/fork-ts-checker-webpack-plugin

[4] TS 文件: https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html