前言
關注「Vite」底層實作的同學,我想應該清楚它使用「esbuild」來實作對
.ts
、
jsx
、
.js
代碼的轉化。當然,在「Vite」之前更早使用「esbuild」的就是「Snowpack」。不過,相比較「Vite」擁有的巨大社群,顯然「Snowpack」的關注度較小。
「Vite」的核心是基于浏覽器原生的
ES Module
。但是,相比較傳統的打包工具和開發工具而言,它做出了很多改變,采用「esbuild」來支援
.ts
、
jsx
、
.js
代碼的轉化就是其中之一。
那麼,接下來我們就步入今天的正題,What is esbuild, and how to use it?
1 什麼是 esbuild
「esbuild」官方的介紹:它是一個「JavaScript」
Bundler
打包和壓縮工具,它可以将「JavaScript」和「TypeScript」代碼打包分發在網頁上運作。
目前「esbuild」支援的功能:
- 加載器
- 壓縮
- 打包
- Tree shaking
- Source map 生成
- 将 JSX 和較新的 JS 文法移植到 ES6
- …
這裡,我們列出了幾點常關注的,至于其他,有興趣的同學可以移步官方文檔自行了解。
目前對于「JavaScript」文法轉化不支援的特性有:
- Top-level await
- async await
- BigInt
- Hashbang 文法
需要注意的是對于不支援轉化的文法會原樣輸出。
2 對比現有的打包工具
「esbuild」的作者對比目前現階段類似的工具做了基準測試。最後的結果是:
對于這些基準測試,esbuild 比我測試的其他 JavaScript 打包程式 快至少 100 倍。
100 倍,可以說快到飛起了…而「esbuild」快的原因,這裡我分兩個層面解釋:
2.1 官方解釋
- 它是用「Go」語言編寫的,該語言可以編譯為本地代碼。
- 解析,生成最終檔案和生成 source maps 全部完全并行化。
- 無需昂貴的資料轉換,隻需很少的幾步即可完成所有操作。
- 該庫以提高編譯速度為編寫代碼時的第一原則,并盡量避免不必要的記憶體配置設定。
2.2 語言層面解釋
- 現階段的類似工具,底層的實作都是基于「JavaScript」,其受限于本身是一門解釋型的語言,并不能充分利用 CPU。
- 「Chrome V8」引擎雖然對「JavaScript」的運作做了優化,引進「JIT」的機制,但是部分代碼實作機器碼與「esbuild」全部實作機器碼的形式,性能上的差距不可彌補。
當然,語言層面僅僅是官方解釋中的一點的展開,其他解釋有時間等後續分析其源碼實作後講解。
3 esbuild API 詳解
雖然,「esbuild」早已開源和使用,但是官方文檔隻是簡單介紹了如何使用,而對于 API 介紹部分是欠缺的,建議讀者自己去閱讀源碼中的定義。
「esbuild」總共提供了四個函數:
transform
、
build
、
buildSync
、
Service
。下面,我們從源碼定義的角度來認識一下它們。
3.1 transform
transform
可以用于轉化
.js
、
.tsx
、
ts
等檔案,然後輸出為舊的文法的
.js
檔案,它提供了兩個參數:
- 第一個參數(必填,字元串),指需要轉化的代碼(子產品内容)。
- 第二個參數(可選),指轉化需要的選項,如源檔案路徑
、需要加載的sourcefile
,其中loader
的定義:loader
transform
會傳回一個
Promise
,對應的
TransformResult
為一個對象,它會包含轉化後的舊的
js
代碼、
sourceMap
映射、警告資訊:
interface TransformResult {
js: string;
jsSourceMap: string;
warnings: Message[];
}
3.2 build
build
實作了
transform
的能力,即代碼轉化,并且它還會将轉換後的代碼壓縮并生成
.js
檔案到指定
output
目錄。
build
隻提供了一個參數(對象),來指定需要轉化的入口檔案、輸出檔案、
loader
等選項:
interface BuildOptions extends CommonOptions {
bundle?: boolean;
splitting?: boolean;
outfile?: string;
metafile?: string;
outdir?: string;
platform?: Platform;
color?: boolean;
external?: string[];
loader?: { [ext: string]: Loader };
resolveExtensions?: string[];
mainFields?: string[];
write?: boolean;
tsconfig?: string;
outExtension?: { [ext: string]: string };
entryPoints?: string[];
stdin?: StdinOptions;
}
build
函數調用會輸出
BuildResult
,它包含了生成的檔案
outputFiles
和提示資訊
warnings
:
interface BuildResult {
warnings: Message[];
outputFiles?: OutputFile[];
}
但是,需要注意的是隻有在
outputFiles
為
write
的情況下才會輸出,它是一個
false
。
Uint8Array
3.3 buildSync
buidSync
顧名思義,相比較
build
而言,它是同步的建構方式,即如果使用
build
我們需要借助
async await
來實作同步調用,而使用
buildSync
可以直接實作同步調用。
3.4 Service
Service
的出現是為了解決調用上述 API 時都會建立一個子進行來完成的問題,如果存在多次調用 API 的情況出現,那麼就會出現性能上的浪費,這一點在文檔中也有講解。
是以,使用了
Service
來實作代碼的轉化或打包,則會建立一個長期的用于共享的子程序,避免了性能上的浪費。而在「Vite」中也正是使用
Service
的方式來進行
.ts
、
.js
、
.jsx
代碼的轉化工作。
Service
定義:
interface Service {
build(options: BuildOptions): Promise<BuildResult>;
transform(input: string, options?: TransformOptions): Promise<TransformResult>;
stop(): void;
}
可以看到,
Service
的本質封裝了
build
、
transform
、
stop
函數,隻是不同于單獨調用它們,
Service
底層的實作是一個長期存在可供共享的子程序。
但是,在實際使用上,我們并不是直接使用
Service
建立執行個體,而是通過
startService
來建立一個
Service
執行個體:
const {
startService,
build,
} = require("esbuild")
const service = await startService()
try {
const res = await service.build({
entryPoints: ["./src/main.js"],
write: false
})
console.log(res)
} finally {
service.stop()
}
并且,在使用
stop
的時候需要注意,它會結束這個子程序,這也意味着任何在此時處于
pending
的
Promise
也會被終止。
4 實作一個小而美的 Bundler 打包
在簡單地認識「esbuild」,我們就來實作一個小而美的
Bunder
打包:
1.初始化項目和安裝「esbuild」:
mkdir esbuild-bundler & npm init -y & npm i esbuild
2.目錄結構:
|——— src
|—— main.js #項目入口檔案
|——— index.js #bundler實作核心檔案
3.
index.js
:
(async () => {
const {
startService,
build,
} = require("esbuild")
const service = await startService()
try {
const res = await service.build({
entryPoints: ["./src/main.js"],
outfile: './dist/main.js',
minify: true,
bundle: true,
})
} finally {
service.stop()
}
})()
4.運作一下
node index
即可體驗一下閃電般的
bundler
打包!
寫在最後
想必看完這篇文章,大家對「esbuild」應該建立起一個基礎的認知。并且,文中的源碼隻是基于「Go」實作的底層能力上的,而真正的底層實作還是得看「Go」是如何實作的,由于脫離了大家熟知的前端,是以就不做介紹。那麼,在一下篇文章中,我将會講解在「Vite」的源碼設計中是怎麼使用
esbuild
來實作
.ts
、
jsx
、
.js
文法解析,以及我們如何自定義
plugin
來實作一些代碼轉化。最後,文章中如果存在表述不當的地方,歡迎各位同學提 Issue。
❤️愛心三連擊
通過閱讀,如果你覺得有收獲的話,可以愛心三連擊!!!
前端問路人 —— 五柳(微信公衆号: Code center)