Skip to content

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.js

JavaScript 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 灵活

与其他工具对比

构建速度对比

工具相对速度
esbuild1x(最快)
Vite~2-3x
Rollup~10x
Webpack~100x

功能对比

功能esbuildWebpackRollupVite
构建速度极快中等
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      # 定义变量

基于 VitePress 的本地知识库