下一個時代的打包工具 esbuild

前言

關注「Vite」底層實現的同窗,我想應該清楚它使用「esbuild」來實現對 .tsjsx.js 代碼的轉化。固然,在「Vite」以前更早使用「esbuild」的就是「Snowpack」。不過,相比較「Vite」擁有的巨大社區,顯然「Snowpack」的關注度較小。css

「Vite」的核心是基於瀏覽器原生的 ES Module。可是,相比較傳統的打包工具和開發工具而言,它作出了不少改變,採用「esbuild」來支持 .tsjsx.js 代碼的轉化就是其中之一。前端

那麼,接下來咱們就步入今天的正題,What is esbuild, and how to use it?node

1 什麼是 esbuild

「esbuild」官方的介紹:它是一個「JavaScript」Bundler 打包和壓縮工具,它能夠將「JavaScript」和「TypeScript」代碼打包分發在網頁上運行。npm

目前「esbuild」支持的功能:json

  • 加載器
  • 壓縮
  • 打包
  • Tree shaking
  • Source map 生成
  • 將 JSX 和較新的 JS 語法移植到 ES6
  • ...

這裏,咱們列出了幾點常關注的,至於其餘,有興趣的同窗能夠移步官方文檔自行了解。瀏覽器

目前對於「JavaScript」語法轉化不支持的特性有:微信

  • Top-level await
  • async await
  • BigInt
  • Hashbang 語法

須要注意的是對於不支持轉化的語法會原樣輸出markdown

2 對比現有的打包工具

「esbuild」的做者對比目前現階段相似的工具作了基準測試。最後的結果是:async

對於這些基準測試,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」總共提供了四個函數:transformbuildbuildSyncService。下面,咱們從源碼定義的角度來認識一下它們。

3.1 transform

transform 能夠用於轉化 .js.tsxts 等文件,而後輸出爲舊的語法的 .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[]; } 複製代碼

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 只有在 writefalse 的狀況下才會輸出,它是一個 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 的本質封裝了 buildtransformstop 函數,只是不一樣於單獨調用它們,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 的時候須要注意,它會結束這個子進程,這也意味着任何在此時處於 pendingPromise 也會被終止。

4 實現一個小而美的 Bundler 打包

在簡單地認識「esbuild」,咱們就來實現一個小而美的 Bunder 打包:

1.初始化項目和安裝「esbuild」:

mkdir esbuild-bundler; cd 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 來實現 .tsjsx.js 語法解析,以及咱們如何自定義 plugin 來實現一些代碼轉化。最後,文章中若是存在表述不當的地方,歡迎各位同窗提 Issue。

往期文章回顧

深度解讀 Vue3 源碼 | 組件建立過程

深度解讀 Vue3 源碼 | 內置組件 teleport 是什麼「來頭」?

深度解讀 Vue3 源碼 | compile 和 runtime 結合的 patch 過程

❤️愛心三連擊

經過閱讀,若是你以爲有收穫的話,能夠愛心三連擊!!!

前端問路人 —— 五柳(微信公衆號: Code center)

相關文章
相關標籤/搜索