Appearance
esbuild 介绍
什么是 esbuild
esbuild 是一个用 Go 语言编写的 JavaScript 打包器和压缩器,由 Evan Wallace(Figma 联合创始人)开发。它的主要特点是极致的构建速度,比其他打包工具快 10-100 倍。
esbuild 的核心特点
极致的构建速度
- 使用 Go 语言编写
- 多线程并行处理
- 内存高效使用
- 无需缓存即可快速构建
原生支持
- JavaScript
- TypeScript
- JSX
- CSS
简洁的 API
- 命令行接口
- JavaScript API
- Go API
esbuild 为什么这么快
语言选择
- Go 语言:编译型语言,执行效率高
- JavaScript:解释型语言,性能受限
并行处理
- Go 的 goroutine 实现轻量级线程
- 充分利用多核 CPU
- 并行解析、生成代码
内存优化
- 避免数据复制
- 高效的内存管理
- 减少垃圾回收
实现方式
- 从零开始实现,没有历史包袱
- 不使用第三方库
- 算法优化
esbuild 的工作原理
解析阶段
源代码
↓
词法分析(Lexer)
↓
语法分析(Parser)
↓
AST(抽象语法树)esbuild 使用自定义的解析器:
javascript
const source = 'const x = 1 + 2;';
// esbuild 解析
const result = esbuild.transformSync(source, {
loader: 'js'
});链接阶段
多个模块 AST
↓
符号解析
↓
依赖图构建
↓
模块合并生成阶段
合并后的 AST
↓
代码生成
↓
Source Map 生成
↓
输出文件esbuild 的使用
命令行使用
bash
# 打包文件
esbuild src/index.js --bundle --outfile=dist/bundle.js
# 压缩代码
esbuild src/index.js --bundle --minify --outfile=dist/bundle.js
# 指定格式
esbuild src/index.js --bundle --format=esm --outfile=dist/bundle.js
# 开发服务器
esbuild src/index.js --bundle --servedir=dist
# 监听模式
esbuild src/index.js --bundle --watch --outfile=dist/bundle.jsJavaScript API
构建 API
javascript
const esbuild = require('esbuild');
// 一次性构建
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
minify: true,
sourcemap: true,
format: 'esm',
target: ['es2020']
}).then(() => {
console.log('构建完成');
}).catch(() => process.exit(1));持续监听
javascript
const esbuild = require('esbuild');
async function watch() {
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js'
});
await ctx.watch();
}
watch();开发服务器
javascript
const esbuild = require('esbuild');
async function serve() {
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js'
});
const { host, port } = await ctx.serve({
servedir: 'dist'
});
console.log(`Server running at http://${host}:${port}`);
}
serve();Transform API
javascript
const esbuild = require('esbuild');
// 转换代码
const result = esbuild.transformSync(
'const x = 1 + 2;',
{ loader: 'js', minify: true }
);
console.log(result.code);TypeScript 支持
esbuild 原生支持 TypeScript,无需额外配置:
javascript
esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
outfile: 'dist/bundle.js'
});注意
esbuild 不进行类型检查,只编译代码。如需类型检查,请使用 tsc。
JSX 支持
javascript
esbuild.build({
entryPoints: ['src/app.jsx'],
bundle: true,
outfile: 'dist/bundle.js',
loader: {
'.jsx': 'jsx'
},
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment'
});CSS 支持
javascript
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
loader: {
'.css': 'css'
}
});esbuild 的配置选项
入口配置
javascript
esbuild.build({
entryPoints: ['src/index.js'],
entryPoints: ['src/a.js', 'src/b.js'],
entryPoints: {
main: 'src/index.js',
admin: 'src/admin.js'
}
});输出配置
javascript
esbuild.build({
entryPoints: ['src/index.js'],
outfile: 'dist/bundle.js',
outdir: 'dist',
outbase: 'src',
splitting: true,
format: 'esm',
platform: 'browser',
target: ['es2020', 'chrome80', 'firefox72']
});模块解析
javascript
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
external: ['react', 'react-dom'],
mainFields: ['module', 'main'],
conditions: ['import', 'require'],
alias: {
'@': './src'
}
});代码转换
javascript
esbuild.build({
entryPoints: ['src/index.js'],
define: {
'process.env.NODE_ENV': '"production"',
'DEBUG': 'false'
},
inject: ['./process-shim.js'],
banner: '// Build with esbuild',
footer: '// End of bundle'
});插件系统
javascript
const httpPlugin = {
name: 'http',
setup(build) {
build.onResolve({ filter: /^https?:\/\// }, args => ({
path: args.path,
namespace: 'http-url'
}));
build.onLoad({ filter: /.*/, namespace: 'http-url' }, async args => {
const response = await fetch(args.path);
return { contents: await response.text(), loader: 'js' };
});
}
};
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
plugins: [httpPlugin]
});esbuild 的优点
极速构建
- 比 Webpack 快 10-100 倍
- 无需缓存优化
- 大型项目优势明显
开箱即用
- 原生支持 TypeScript
- 原生支持 JSX
- 原生支持 CSS
- 无需配置 loader
简单配置
- 配置项少
- API 简洁
- 易于上手
内存高效
- 内存占用低
- 适合大型项目
API 灵活
- 命令行接口
- JavaScript API
- Go API
- 插件系统
esbuild 的缺点
功能相对简单
- 不如 Webpack 功能全面
- 缺少一些高级特性
- 插件生态较小
类型检查
- 不进行 TypeScript 类型检查
- 需要配合 tsc 使用
开发体验
- HMR 支持有限
- 开发服务器功能简单
- 不如 Vite 开发体验好
生态规模
- 插件数量较少
- 社区相对较小
- 某些场景支持不完善
代码分割
- 代码分割功能相对简单
- 不如 Webpack 灵活
与其他工具对比
构建速度对比
| 工具 | 相对速度 |
|---|---|
| esbuild | 1x(最快) |
| Vite | ~2-3x |
| Rollup | ~10x |
| Webpack | ~100x |
功能对比
| 功能 | esbuild | Webpack | Rollup | Vite |
|---|---|---|---|---|
| 构建速度 | 极快 | 慢 | 中等 | 快 |
| TypeScript | 原生 | loader | 插件 | 原生 |
| CSS 处理 | 原生 | loader | 插件 | 原生 |
| 代码分割 | 基础 | 强大 | 好 | 好 |
| HMR | 基础 | 好 | 插件 | 极好 |
| 插件生态 | 小 | 大 | 中 | 中 |
esbuild 的应用场景
Vite 底层
- Vite 使用 esbuild 进行预构建
- 开发环境依赖预构建
- 生产环境使用 Rollup
库开发
- 快速构建库
- TypeScript 库编译
简单项目
- 小型应用构建
- 脚本打包
工具链集成
- 作为其他工具的一部分
- Snowpack 底层
- 其他构建工具的加速器
插件开发
插件结构
javascript
const myPlugin = {
name: 'my-plugin',
setup(build) {
// onResolve - 解析模块路径
build.onResolve({ filter: /^env$/ }, args => ({
path: args.path,
namespace: 'env-ns'
}));
// onLoad - 加载模块内容
build.onLoad({ filter: /.*/, namespace: 'env-ns' }, args => ({
contents: JSON.stringify(process.env),
loader: 'json'
}));
// onStart - 构建开始
build.onStart(() => {
console.log('构建开始');
});
// onEnd - 构建结束
build.onEnd(result => {
console.log('构建结束');
});
}
};常用钩子
| 钩子 | 用途 |
|---|---|
| onResolve | 自定义模块解析 |
| onLoad | 自定义模块加载 |
| onStart | 构建开始时触发 |
| onEnd | 构建结束时触发 |
| onDispose | 清理资源 |
文件类型插件
javascript
const yamlPlugin = {
name: 'yaml',
setup(build) {
build.onLoad({ filter: /\.ya?ml$/ }, async args => {
const yaml = require('js-yaml');
const fs = require('fs');
const content = fs.readFileSync(args.path, 'utf8');
const data = yaml.load(content);
return {
contents: JSON.stringify(data),
loader: 'json'
};
});
}
};最佳实践
与 TypeScript 配合
json
// tsconfig.json
{
"compilerOptions": {
"noEmit": true,
"emitDeclarationOnly": true,
"declaration": true
}
}javascript
// 构建脚本
const esbuild = require('esbuild');
const { exec } = require('child_process');
async function build() {
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
outfile: 'dist/bundle.js',
minify: true,
sourcemap: true
});
exec('tsc --emitDeclarationOnly');
}
build();多入口构建
javascript
esbuild.build({
entryPoints: {
main: 'src/main.js',
admin: 'src/admin.js',
shared: 'src/shared.js'
},
bundle: true,
outdir: 'dist',
splitting: true,
format: 'esm'
});开发环境配置
javascript
const esbuild = require('esbuild');
async function dev() {
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
sourcemap: true,
banner: {
js: 'new EventSource("/esbuild").addEventListener("change", () => location.reload());'
}
});
await ctx.watch();
const { host, port } = await ctx.serve({
servedir: 'dist'
});
console.log(`http://${host}:${port}`);
}
dev();命令行选项
bash
# 基本选项
--bundle # 打包依赖
--minify # 压缩代码
--sourcemap # 生成 source map
--watch # 监听模式
--format=esm # 输出格式
--platform=browser # 目标平台
--target=es2020 # 目标环境
# 输出选项
--outfile=dist/bundle.js # 输出文件
--outdir=dist # 输出目录
--outbase=src # 输出基础路径
# 模块选项
--external:react # 外部依赖
--main-fields=module,main # 主字段
# 定义选项
--define:DEBUG=false # 定义变量