Skip to content
⚠️ This article was written in 2021. Some content may be outdated.

esbuild:用 Go 重寫的 JS 打包器

esbuild 是 Evan Wallace 在 2020 年發佈的 JavaScript/TypeScript 打包器,用 Go 語言編寫。它不是最快的 JS 打包器,它是快到不真實的那種——比 Webpack 快 100 倍,比 Parcel 快 100 倍,比 Rollup 快數倍。Vite 用它做依賴預構建,Snowpack 也集成了它。

為什麼這麼快

esbuild 速度快有三個原因:

  1. Go 語言:編譯型語言,天然比 JS 快。Go 的併發模型(goroutine)也讓並行處理更高效
  2. 從零實現:不用任何現有的 JS 庫(如 acorn、babel),全部自己實現 parser 和 bundler
  3. 內存優化:數據結構儘量緊湊,減少內存分配和 GC 壓力
bash
# 實際測試:打包 10 個 Three.js 拷貝
# esbuild:  ~47ms
# Webpack:  ~4,250ms
# Rollup:   ~1,780ms
# Parcel:   ~4,500ms

基本使用

bash
# 全局安裝
npm install -g esbuild

# 打包單文件
esbuild app.ts --bundle --outfile=out.js

# 打包為生產環境
esbuild app.ts --bundle --minify --outfile=out.js

# 多入口
esbuild src/home.ts src/about.ts --bundle --outdir=dist

API 使用

esbuild 有 CLI 和 API 兩種使用方式,API 更靈活:

javascript
const esbuild = require('esbuild')

// 構建
async function build() {
  const result = await esbuild.build({
    entryPoints: ['src/index.tsx'],
    bundle: true,
    minify: true,
    sourcemap: true,
    target: ['es2020'],
    outdir: 'dist',
    format: 'esm',
    splitting: true, // 代碼分割(ESM 格式下可用)
    define: {
      'process.env.NODE_ENV': '"production"'
    },
    loader: {
      '.tsx': 'tsx',
      '.css': 'css',
      '.png': 'dataurl',
      '.svg': 'text'
    },
    external: ['react', 'react-dom'], // 不打包這些
    plugins: [
      // 插件示例
      {
        name: 'env-plugin',
        setup(build) {
          build.onResolve({ filter: /^env$/ }, () => ({
            path: 'env',
            namespace: 'env-ns'
          }))
          build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
            contents: JSON.stringify(process.env),
            loader: 'json'
          }))
        }
      }
    ]
  })

  console.log(`構建完成,輸出 ${result.outputFiles.length} 個文件`)
}

build()

Watch 模式

javascript
const ctx = await esbuild.context({
  entryPoints: ['src/index.ts'],
  bundle: true,
  outdir: 'dist',
  sourcemap: true
})

// 監聽文件變化
await ctx.watch()

// 啓動開發服務器(esbuild 0.17+)
await ctx.serve({
  port: 3000,
  servedir: 'dist'
})

esbuild 在 Vite 中的角色

Vite 在兩個關鍵位置使用了 esbuild:

  1. 依賴預構建:開發階段,將 CommonJS 依賴轉為 ESM,將許多內部模塊合併為一個文件
  2. 生產構建的代碼壓縮:Vite 2.7+ 支持用 esbuild 做 minify,比 Terser 快很多
javascript
// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    // esbuild 預構建配置
    esbuildOptions: {
      target: 'es2020',
      define: {
        global: 'globalThis'
      }
    }
  },
  build: {
    // Vite 2.7+ 使用 esbuild 壓縮
    minify: 'esbuild', // 'terser' | 'esbuild'
    target: 'es2015'
  }
})

目前的侷限

esbuild 不是萬能的,有幾個明顯限制:

  1. 沒有代碼分割的 IIFE 格式:ESM 格式支持 splitting,但傳統格式不支持
  2. 不支持轉換裝飾器語法:Angular 項目無法直接用
  3. 不支持 HMR:自己不會實現 HMR,需要上層框架處理
  4. 插件生態很小:Webpack 插件不能直接遷移
  5. CSS 處理有限:不支持 CSS Modules、PostCSS 等高級功能
javascript
// ❌ 不支持裝飾器
@Component({ template: '<div/>' })
class MyComponent { }

// ❌ 不支持 CSS Modules(需要自行處理)
import styles from './app.module.css'

和 Webpack 的定位差異

esbuild 不是 Webpack 的替代品,至少目前不是。它的定位更像是:

  • 構建加速器:在現有工具鏈中替代最慢的環節(編譯 TS、打包、壓縮)
  • 原型工具:小型項目和庫的打包,配置簡單
  • 底層基礎設施:Vite、Snowpack 等工具的底層引擎
javascript
// Webpack 用户的漸進式採用方式
// 1. 用 esbuild-loader 替代 babel-loader + ts-loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'esbuild-loader',
        options: {
          loader: 'tsx',    // tsx 文件用 tsx loader
          target: 'es2015'
        }
      }
    ]
  },
  plugins: [
    // 2. 用 EsbuildPlugin 替代 TerserPlugin 做壓縮
    new EsbuildPlugin({
      target: 'es2015',
      css: true
    })
  ]
}

小結

  • esbuild 用 Go 編寫,速度比傳統工具快 100 倍,不是營銷話術是實測數據
  • 適合用作底層加速工具(Vite、Snowpack 已集成),不適合完全替代 Webpack
  • Webpack 用户可以用 esbuild-loader 做漸進式提速
  • 插件生態、CSS 處理、HMR 還不成熟,關注但不要盲目全面切換
  • 它代表了一個趨勢:前端工具鏈正在從 JS 遷移到編譯型語言

MIT Licensed