esbuild 是一个用 Go 语言编写的极速 JavaScript 和 TypeScript 打包工具。它以比 Webpack、Rollup 或 Parcel 等传统工具快 10-100 倍的速度处理打包、压缩、转译和 Tree-shaking。esbuild 由 Evan Wallace(Figma 联合创始人)创建,通过利用并行性、避免不必要的数据转换以及使用编译语言而非 JavaScript 来实现其性能。无论你需要独立的打包工具还是 Vite 和 Snowpack 等工具的高速基础,esbuild 已成为现代 JavaScript 工具链中不可或缺的一部分。
esbuild 是基于 Go 的 JavaScript/TypeScript 打包工具,速度比 Webpack 或 Rollup 快 10-100 倍。它开箱即用支持 ESM 和 CommonJS、TypeScript 和 JSX、Tree-shaking、压缩、Source Map、CSS 打包、插件 API、监听模式和内置开发服务器。使用 CLI 进行快速任务,使用 JavaScript/Go API 进行编程构建,使用插件进行自定义转换。
- esbuild 用 Go 编写并大量使用并行处理,比 Webpack 等基于 JavaScript 的打包工具快 10-100 倍。
- 开箱即用支持 TypeScript、JSX、ESM、CommonJS、CSS 和 JSON,无需配置。
- 插件 API 让你可以用自定义加载器、解析器和转换来扩展 esbuild。
- Tree-shaking、压缩和代码分割自动工作,生成优化的生产包。
- 监听模式和内置服务模式实现快速开发工作流和即时重建。
- esbuild 驱动 Vite 开发服务器,在现代工具链中被广泛用作快速转译器和打包工具。
为什么 esbuild 如此快速
esbuild 通过四个关键架构决策实现了非凡的速度,使其区别于所有基于 JavaScript 的打包工具。
- 用 Go 编写:Go 编译为原生机器码,无需垃圾回收暂停风暴。JavaScript 打包工具在 V8 中运行,有 JIT 编译开销、GC 暂停和单线程限制。:用 Go 编写:Go 编译为原生机器码,无需垃圾回收暂停风暴。JavaScript 打包工具在 V8 中运行,有 JIT 编译开销、GC 暂停和单线程限制。
- 大规模并行:esbuild 使用所有可用 CPU 核心进行解析、链接和代码生成。JavaScript 打包工具受单线程事件循环的根本限制。:大规模并行:esbuild 使用所有可用 CPU 核心进行解析、链接和代码生成。JavaScript 打包工具受单线程事件循环的根本限制。
- 最少数据转换:esbuild 读取源文件,将其解析为紧凑的 AST 表示一次,然后写入输出。它避免了 Webpack 和 Rollup 中插件链执行的重复字符串到 AST 到字符串转换。:最少数据转换:esbuild 读取源文件,将其解析为紧凑的 AST 表示一次,然后写入输出。它避免了 Webpack 和 Rollup 中插件链执行的重复字符串到 AST 到字符串转换。
- 内存效率:esbuild 使用紧凑的数据结构并以流式方式处理文件,即使对于大型项目也能保持较低的内存使用。:内存效率:esbuild 使用紧凑的数据结构并以流式方式处理文件,即使对于大型项目也能保持较低的内存使用。
# Benchmark: Bundle 1,000 modules (React + TypeScript)
#
# esbuild 0.33s (Go, parallel)
# Rollup + terser 15.4s (JS, single-threaded)
# Webpack 5 19.8s (JS, single-threaded)
# Parcel 2 8.7s (Rust parser + JS plugins)
#
# esbuild is 47-60x faster than Webpack/Rollup
# for equivalent bundling tasks安装
esbuild 可以作为本地 npm 依赖或全局安装。它提供平台特定的原生二进制文件,安装时无需编译步骤。
# Install as a local dev dependency (recommended)
npm install --save-dev esbuild
# Install globally
npm install -g esbuild
# Verify installation
npx esbuild --version
# 0.24.x
# Using with yarn
yarn add --dev esbuild
# Using with pnpm
pnpm add -D esbuildCLI 使用
esbuild CLI 是打包、压缩或转换文件的最快方式。它接受所有主要选项作为命令行标志,非常适合脚本、CI 管道和一次性任务。
基本打包和常用 CLI 标志:
# Bundle a single entry point
npx esbuild src/index.ts --bundle --outfile=dist/bundle.js
# Bundle with ESM output format
npx esbuild src/index.ts --bundle --format=esm --outfile=dist/bundle.mjs
# Bundle for Node.js
npx esbuild src/server.ts --bundle --platform=node --outfile=dist/server.js
# Transpile TypeScript without bundling
npx esbuild src/index.ts --outfile=dist/index.js
# Bundle multiple entry points into an output directory
npx esbuild src/app.ts src/worker.ts --bundle --outdir=dist
# Bundle with JSX for React
npx esbuild src/App.tsx --bundle --jsx=automatic --outfile=dist/app.js用于生产构建的高级 CLI 选项:
# Production build with minification and source maps
npx esbuild src/index.ts \
--bundle \
--minify \
--sourcemap \
--target=es2020 \
--format=esm \
--outfile=dist/bundle.js
# With tree-shaking and external packages
npx esbuild src/index.ts \
--bundle \
--minify \
--tree-shaking=true \
--external:react \
--external:react-dom \
--format=esm \
--outfile=dist/bundle.js
# With define for environment variables
npx esbuild src/index.ts \
--bundle \
--define:process.env.NODE_ENV=\"production\" \
--define:__VERSION__=\"1.2.3\" \
--outfile=dist/bundle.js
# Analyze bundle with metafile
npx esbuild src/index.ts --bundle --metafile=meta.json --outfile=dist/bundle.js
npx esbuild --analyze=verbose < meta.jsonJavaScript API
JavaScript API 提供从 Node.js 脚本、构建工具和自定义开发服务器对 esbuild 的编程访问。它暴露三个主要函数:build、transform 和 context。
build()
build 函数从磁盘读取文件,打包它们并写入输出。它接受所有与 CLI 相同的选项以及额外的 JavaScript 特定选项如插件。
import * as esbuild from "esbuild";
// Basic build
const result = await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outfile: "dist/bundle.js",
format: "esm",
target: "es2020",
minify: true,
sourcemap: true,
metafile: true,
});
// Analyze the bundle
const analysis = await esbuild.analyzeMetafile(result.metafile);
console.log(analysis);
// Library build with multiple entry points
await esbuild.build({
entryPoints: ["src/index.ts", "src/utils.ts"],
bundle: true,
outdir: "dist",
format: "esm",
splitting: true,
platform: "node",
target: "node18",
external: ["express", "pg"],
packages: "external",
});transform()
transform 函数在单个代码字符串上操作,不访问文件系统。适用于开发服务器和编辑器集成中的即时转译。
import * as esbuild from "esbuild";
// Transform TypeScript to JavaScript
const tsCode = "const greet = (name: string): string => `Hello \${name}`;";
const result = await esbuild.transform(tsCode, {
loader: "ts",
target: "es2020",
});
console.log(result.code);
// const greet = (name) => `Hello \${name}`;
// Minify JavaScript
const minified = await esbuild.transform("const x = 1 + 2;", {
minify: true,
});
console.log(minified.code); // "const x=3;"
// Transform JSX
const jsx = await esbuild.transform(
"const App = () => <div>Hello</div>;",
{ loader: "jsx", jsx: "automatic" }
);
console.log(jsx.code);context()
context 函数为监听模式和服务模式创建长期构建上下文。它返回具有 rebuild、watch、serve 和 dispose 方法的对象。
import * as esbuild from "esbuild";
// Create a long-lived build context
const ctx = await esbuild.context({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist",
format: "esm",
sourcemap: true,
});
// Manual rebuild (reuses cached state)
const result = await ctx.rebuild();
// Start watching for file changes
await ctx.watch();
console.log("Watching for changes...");
// Start a dev server on port 8000
const { host, port } = await ctx.serve({
servedir: "public",
port: 8000,
});
console.log("Serving at http://" + host + ":" + port);
// Clean up when done
// await ctx.dispose();内容类型和加载器
esbuild 使用加载器来确定如何处理每种文件类型。加载器根据文件扩展名选择,但可以使用 --loader 标志或 API 中的 loader 选项覆盖。
- js / jsx — JavaScript 和 JSX 文件。配置后 .js 和 .jsx 文件都支持 JSX 语法。
- ts / tsx — TypeScript 和 TSX 文件。esbuild 剥离类型注解但不执行类型检查。
- css — CSS 文件。esbuild 打包 CSS 导入,处理 @import 解析,支持 CSS 模块。
- json — JSON 文件被解析并内联为 JavaScript 对象。Tree-shaking 作用于单个 JSON 属性。
- text — 文件作为包含文件内容的 JavaScript 字符串导入。
- binary — 文件作为文件内容的 Uint8Array 导入。
- base64 — 文件作为 base64 编码字符串导入。
- file — 文件被复制到输出目录,导入解析为输出文件路径。
- dataurl — 文件作为 data URL 字符串内联。
// Configure loaders in the JavaScript API
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist",
loader: {
".png": "file",
".svg": "dataurl",
".txt": "text",
".woff2": "file",
".json": "json",
".csv": "text",
},
});
// CLI loader override
// npx esbuild src/index.ts --bundle --loader:.svg=dataurl --loader:.txt=text打包和输出格式
esbuild 支持三种输出格式,决定打包代码的结构方式。选择正确的格式取决于目标环境和模块系统。
- ESM: "module" 和库包。
- CommonJS:
- IIFE:
// Build a library with both ESM and CJS outputs
import * as esbuild from "esbuild";
const shared = {
entryPoints: ["src/index.ts"],
bundle: true,
minify: true,
sourcemap: true,
target: "es2020",
external: ["react", "react-dom"],
};
// ESM output
await esbuild.build({
...shared,
format: "esm",
outfile: "dist/index.mjs",
});
// CommonJS output
await esbuild.build({
...shared,
format: "cjs",
outfile: "dist/index.cjs",
});
// IIFE output for browsers (global variable)
await esbuild.build({
...shared,
format: "iife",
globalName: "MyLibrary",
outfile: "dist/index.global.js",
});代码分割
esbuild 支持 ESM 输出格式的自动代码分割。启用分割后,多个入口点之间的共享模块被提取到单独的块文件中,动态 import() 表达式创建按需加载的额外块。
// Enable code splitting (ESM format required)
await esbuild.build({
entryPoints: ["src/home.ts", "src/about.ts"],
bundle: true,
splitting: true,
format: "esm",
outdir: "dist",
chunkNames: "chunks/[name]-[hash]",
});
// Dynamic imports also create separate chunks
// src/home.ts
// const module = await import("./heavy-module.ts");
// module.doSomething();
// Output structure:
// dist/
// home.js (entry)
// about.js (entry)
// chunks/shared-A1B2.js (shared code)Tree-Shaking
esbuild 在打包过程中自动执行 Tree-shaking(死代码消除)。它分析 import/export 图并移除从未导入或使用的代码。Tree-shaking 在语句级别工作并尊重 package.json 中的 sideEffects 字段。
Note: 为了最大化 Tree-shaking 效果,使用 ES 模块语法(import/export)而非 CommonJS(require/module.exports)。CommonJS 是动态的,阻止对未使用导出的静态分析。
// package.json — Mark the package as side-effect-free
{
"name": "my-library",
"sideEffects": false
}
// Or specify files with side effects
{
"name": "my-library",
"sideEffects": ["./src/polyfills.js", "*.css"]
}
// esbuild automatically removes unused exports
// src/utils.ts
// export function usedFunction() { ... } <-- kept
// export function unusedFunction() { ... } <-- removed
// Force tree-shaking even without bundle mode
// npx esbuild src/index.ts --tree-shaking=true --outfile=dist/out.js压缩
esbuild 包含内置压缩器,通过移除空白、缩短标识符和应用代码转换来减小包大小。压缩器同时适用于 JavaScript 和 CSS。可以作为单个标志启用或分别为空白、标识符和语法配置。
// Enable all minification
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
minify: true, // Shorthand for all three below
outfile: "dist/bundle.js",
});
// Fine-grained minification control
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
minifyWhitespace: true, // Remove whitespace
minifyIdentifiers: true, // Shorten variable names
minifySyntax: true, // Simplify expressions
outfile: "dist/bundle.js",
});
// CSS minification works the same way
// npx esbuild src/styles.css --minify --outfile=dist/styles.min.css
// Minification transforms examples:
// true && x --> x
// if (a) b(); else c(); --> a ? b() : c()
// "use strict" removed in ESM
// Dead code after return/throw removedSource Maps
Source Map 允许调试器将打包/压缩的代码映射回原始源文件。esbuild 支持多种 Source Map 模式以匹配不同的部署场景。
- linked: 生成单独的 .map 文件并在输出中添加 sourceMappingURL 注释。生产部署的标准做法。
- inline: 将整个 Source Map 作为 base64 data URL 嵌入输出文件。适用于开发和小型脚本。
- external: 生成单独的 .map 文件但不添加 sourceMappingURL 注释。用于上传 Source Map 到错误跟踪服务但不向用户暴露。
- both: 生成单独的 .map 文件并嵌入内联 Source Map。很少需要。
// Source map modes
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
minify: true,
outfile: "dist/bundle.js",
// Option 1: Linked (production — separate .map file)
sourcemap: true, // or sourcemap: "linked"
// Option 2: Inline (development — embedded in output)
// sourcemap: "inline",
// Option 3: External (error tracking — no comment)
// sourcemap: "external",
// Include source content in the map for offline debugging
sourcesContent: true,
});插件 API
esbuild 插件 API 让你拦截和自定义构建过程。插件可以解析导入路径、加载文件内容和转换代码。API 设计简单高效,有两个主要钩子:onResolve 和 onLoad。
- onResolve: 当 esbuild 遇到导入路径时调用。你的回调可以返回自定义路径、命名空间或 external 标志。
- onLoad: 当 esbuild 需要加载文件时调用。你的回调返回文件内容和加载器类型。
以下是加载 .txt 文件为 ES 模块并添加环境变量的示例插件:
import * as esbuild from "esbuild";
import fs from "fs";
// Plugin: Load .txt files as ES modules
const txtPlugin = {
name: "txt-loader",
setup(build) {
build.onLoad({ filter: /\.txt$/ }, async (args) => {
const text = await fs.promises.readFile(args.path, "utf8");
return {
contents: "export default " + JSON.stringify(text) + ";",
loader: "js",
};
});
},
};
// Plugin: Replace environment variables at build time
const envPlugin = {
name: "env-loader",
setup(build) {
build.onResolve({ filter: /^env$/ }, (args) => ({
path: args.path,
namespace: "env-ns",
}));
build.onLoad({ filter: /.*/, namespace: "env-ns" }, () => ({
contents: "export default " + JSON.stringify(process.env) + ";",
loader: "json",
}));
},
};
// Use plugins in build
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outfile: "dist/bundle.js",
plugins: [txtPlugin, envPlugin],
});监听模式
监听模式监控源文件的变化,检测到变化时自动重建。它使用操作系统文件监视器进行即时通知而非轮询,使重建几乎是即时的。
// Watch mode using the context API
import * as esbuild from "esbuild";
const ctx = await esbuild.context({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist",
sourcemap: true,
logLevel: "info",
});
// Start watching — rebuilds on file changes
await ctx.watch();
console.log("Watching for file changes...");
// To stop watching and clean up:
// await ctx.dispose();
// CLI watch mode
// npx esbuild src/index.ts --bundle --outdir=dist --watch服务模式
内置服务模式启动本地 HTTP 服务器来提供构建输出。它与监听模式集成,每个请求在文件发生变化时触发重建。这提供了一个最小化的开发服务器,无需额外工具。
// Serve mode with watch integration
import * as esbuild from "esbuild";
const ctx = await esbuild.context({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "public/dist",
sourcemap: true,
});
// Start the dev server
const { host, port } = await ctx.serve({
servedir: "public",
port: 3000,
fallback: "public/index.html", // SPA fallback
});
console.log("Dev server: http://" + host + ":" + port);
// Every HTTP request triggers a rebuild if files changed
// No need to call ctx.watch() separately
// CLI serve mode
// npx esbuild src/index.ts --bundle --outdir=public/dist --servedir=public --serve=3000目标环境
target 选项告诉 esbuild 输出哪个 JavaScript 版本。esbuild 自动将现代语法向下转换到指定的目标。支持的目标包括 ESNext、ES2015-ES2022、Chrome、Firefox、Safari、Edge、Node 版本号。
// Target a JavaScript version
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
target: "es2020",
outfile: "dist/bundle.js",
});
// Target specific browsers
// target: ["chrome90", "firefox88", "safari14", "edge90"]
// Target Node.js: target: "node18"
// Target latest: target: "esnext"
// Syntax transforms based on target:
// Optional chaining (?.) -> target < es2020
// Nullish coalescing (??) -> target < es2020
// Class fields -> target < es2022
// Async/await -> target < es2017平台配置
platform 选项告诉 esbuild 代码将在浏览器还是 Node.js 中运行。这影响主字段、条件和内置模块的默认设置。
// Browser platform (default) — resolves "browser" field in package.json
await esbuild.build({
entryPoints: ["src/app.ts"],
bundle: true,
platform: "browser",
outfile: "dist/app.js",
});
// Node.js platform — marks fs, path, crypto as external
await esbuild.build({
entryPoints: ["src/server.ts"],
bundle: true,
platform: "node",
target: "node18",
outfile: "dist/server.js",
});
// Neutral platform — no defaults, full control
// platform: "neutral"Define 和环境变量
define 选项让你在构建时用常量表达式替换全局标识符。常用于环境变量、功能标志和条件编译。替换在 AST 级别进行,可与 Tree-shaking 配合消除死代码分支。
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outfile: "dist/bundle.js",
define: {
"process.env.NODE_ENV": '"production"',
"__DEV__": "false",
"__VERSION__": '"1.2.3"',
"process.env.API_URL": '"https://api.example.com"',
},
});
// In source code, this enables dead code elimination:
//
// if (process.env.NODE_ENV === "development") {
// // This entire block is removed in production
// enableDevTools();
// }
//
// if (__DEV__) {
// console.log("debug info"); // Removed
// }
// CLI equivalent:
// npx esbuild src/index.ts --bundle \
// --define:process.env.NODE_ENV=\"production\" \
// --define:__DEV__=falseesbuild vs Webpack vs Rollup vs Vite
了解 esbuild 与其他打包工具的比较有助于为项目选择正确的工具。每个工具都有不同的优势和权衡。
| 特性 | esbuild | Webpack | Rollup | Vite |
|---|---|---|---|---|
| 编写语言 | Go | JavaScript | JavaScript | JS(使用 esbuild + Rollup) |
| 速度(1K 模块) | ~0.3秒 | ~20秒 | ~15秒 | ~0.5秒(开发),~8秒(构建) |
| 代码分割 | 仅 ESM | 完全支持 | 完全支持 | 完全支持 |
| 插件生态 | 小 | 最大 | 大 | 增长中(Rollup 兼容) |
| HMR | 无内置 | 是 | 通过插件 | 是(即时) |
| TypeScript | 仅剥离 | 通过 ts-loader | 通过插件 | 通过 esbuild |
| CSS 打包 | 内置 | 通过加载器 | 通过插件 | 内置 |
| 配置复杂度 | 最小 | 高 | 中等 | 低 |
esbuild 擅长原始构建速度、库打包和作为其他工具内的快速转译器。Webpack 最适合需要完整插件生态的复杂应用。Rollup 适合具有高级 Tree-shaking 的库发布。Vite 将 esbuild 的开发速度与 Rollup 的生产质量结合。
最佳实践
- 使用 esbuild 进行库打包,速度和简单配置最重要。对于复杂 Web 应用,考虑使用底层采用 esbuild 的 Vite。
- 始终单独运行 tsc --noEmit 进行类型检查。esbuild 剥离类型但不验证它们。
- 使用 context API 的监听模式进行开发,而不是重复调用 build。上下文重用内部状态以加快增量重建。
- 设置 target 选项以匹配最低支持环境。
- 使用 metafile 选项分析包组成。
- 使用 ES 模块语法并设置 sideEffects 字段来启用 Tree-shaking。
- 使用 define 进行编译时常量和环境变量,支持死代码消除。
- 生产构建启用压缩、Source Map(linked 模式)并设置适当的 target 和 platform 选项。
// Complete production build script (build.mjs)
import * as esbuild from "esbuild";
const isProduction = process.env.NODE_ENV === "production";
const result = await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist",
format: "esm",
splitting: true,
target: "es2020",
platform: "browser",
minify: isProduction,
sourcemap: isProduction ? "linked" : "inline",
metafile: true,
define: {
"process.env.NODE_ENV": isProduction ? '"production"' : '"development"',
},
loader: {
".png": "file",
".svg": "dataurl",
".woff2": "file",
},
chunkNames: "chunks/[name]-[hash]",
assetNames: "assets/[name]-[hash]",
});
if (isProduction) {
const analysis = await esbuild.analyzeMetafile(result.metafile);
console.log(analysis);
}常见问题
为什么 esbuild 比 Webpack 快这么多?
esbuild 用 Go 编写,编译为原生机器码并在所有 CPU 核心上完全并行运行。Webpack 在 Node.js 中使用 JavaScript 运行,是单线程的,有 JIT 编译开销和垃圾回收暂停。esbuild 还通过每个文件只解析一次为紧凑表示来最小化数据转换。
esbuild 支持 TypeScript 类型检查吗?
不支持。esbuild 在转译时剥离 TypeScript 类型注解但不执行类型检查。这是为了速度而设计的。你应该在 CI 管道中单独运行 tsc --noEmit。
esbuild 能替代 Webpack 用于 React 应用吗?
对于简单到中等的 React 应用,可以。esbuild 处理 JSX、TypeScript、CSS、代码分割和压缩。但对于需要 HMR、模块联邦或广泛插件生态的复杂设置,Webpack 或 Vite 是更好的选择。
esbuild 如何处理 CSS?
esbuild 有内置 CSS 打包器,解析 @import 语句,处理 CSS 模块,执行压缩并生成 Source Map。
esbuild 和 Vite 有什么区别?
esbuild 是专注于原始速度的底层打包器和转译器。Vite 是更高级的构建工具,开发时使用 esbuild 进行依赖预打包和转译,生产时使用 Rollup。Vite 在 esbuild 之上添加了 HMR、开发服务器、框架插件和更丰富的配置系统。
esbuild 支持代码分割吗?
是的,但仅限 ESM 输出格式。启用分割后,共享模块被提取到单独的块文件中。IIFE 和 CommonJS 格式不支持代码分割。
如何使用 esbuild 插件?
插件是具有 name 和 setup 函数的 JavaScript 对象。setup 函数接收具有 onResolve 和 onLoad 钩子的构建对象。插件通过 plugins 选项传递给 build 或 context 函数。
esbuild 适合生产环境吗?
是的。esbuild 自 2021 年以来一直稳定且广泛用于生产环境。它驱动 Vite 的开发体验,被 Vue、React、Svelte 和 SolidJS 框架使用。其 API 在次版本之间保持稳定且无破坏性变更。