關注「Vite」底層實現的同窗,我想應該清楚它使用「esbuild」來實現對 .ts
、jsx
、.js
代碼的轉化。固然,在「Vite」以前更早使用「esbuild」的就是「Snowpack」。不過,相比較「Vite」擁有的巨大社區,顯然「Snowpack」的關注度較小。javascript
「Vite」的核心是基於瀏覽器原生的 ES Module
。可是,相比較傳統的打包工具和開發工具而言,它作出了不少改變,採用「esbuild」來支持 .ts
、jsx
、.js
代碼的轉化就是其中之一。css
那麼,接下來咱們就步入今天的正題,What is esbuild, and how to use it?前端
「esbuild」官方的介紹:它是一個「JavaScript」Bundler
打包和壓縮工具,它能夠將「JavaScript」和「TypeScript」代碼打包分發在網頁上運行。java
目前「esbuild」支持的功能:node
這裏,咱們列出了幾點常關注的,至於其餘,有興趣的同窗能夠移步 官方文檔自行了解。
目前對於「JavaScript」語法轉化不支持的特性有:typescript
須要注意的是對於不支持轉化的語法會 原樣輸出。
「esbuild」的做者對比目前現階段相似的工具作了基準測試。最後的結果是:npm
對於這些基準測試,esbuild 比我測試的其餘 JavaScript 打包程序 快至少 100 倍。
100 倍,能夠說快到飛起了...而「esbuild」快的緣由,這裏我分兩個層面解釋:json
固然,語言層面僅僅是官方解釋中的一點的展開,其餘解釋有時間等後續分析其源碼實現後講解。
雖然,「esbuild」早已開源和使用,可是官方文檔只是簡單介紹瞭如何使用,而對於 API 介紹部分是欠缺的,建議讀者本身去閱讀源碼中的定義。瀏覽器
「esbuild」總共提供了四個函數:transform
、build
、buildSync
、Service
。下面,咱們從源碼定義的角度來認識一下它們。微信
transform
能夠用於轉化 .js
、.tsx
、ts
等文件,而後輸出爲舊的語法的 .js
文件,它提供了兩個參數:
sourcefile
、須要加載的 loader
,其中 loader
的定義:type Loader = 'js' | 'jsx' | 'ts' | 'tsx' | 'css' | 'json' | 'text' | 'base64' | 'file' | 'dataurl' | 'binary';
transform
會返回一個 Promise
,對應的 TransformResult
爲一個對象,它會包含轉化後的舊的 js
代碼、sourceMap
映射、警告信息:
interface TransformResult { js: string; jsSourceMap: string; warnings: Message[]; }
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
。
buidSync
顧名思義,相比較 build
而言,它是同步的構建方式,即若是使用 build
咱們須要藉助 async await
來實現同步調用,而使用 buildSync
能夠直接實現同步調用。
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
也會被終止。
在簡單地認識「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)