下一個時代的打包工具 esbuild

前言

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

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

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

1 什麼是 esbuild

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

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

  • 加載器
  • 壓縮
  • 打包
  • Tree shaking
  • Source map 生成
  • 將 JSX 和較新的 JS 語法移植到 ES6
  • ...
這裏,咱們列出了幾點常關注的,至於其餘,有興趣的同窗能夠移步 官方文檔自行了解。

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

  • Top-level await
  • async await
  • BigInt
  • Hashbang 語法
須要注意的是對於不支持轉化的語法會 原樣輸出

2 對比現有的打包工具

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

對於這些基準測試,esbuild 比我測試的其餘 JavaScript 打包程序 快至少 100 倍。

100 倍,能夠說快到飛起了...而「esbuild」快的緣由,這裏我分兩個層面解釋:json

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 & 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。

❤️愛心三連擊

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

前端問路人 —— 五柳( 微信公衆號: Code center)
相關文章
相關標籤/搜索