esbuild 最佳实践 #
项目结构 #
推荐目录结构 #
text
project/
├── src/
│ ├── index.js # 入口文件
│ ├── components/ # 组件目录
│ ├── utils/ # 工具函数
│ ├── styles/ # 样式文件
│ └── assets/ # 静态资源
├── public/
│ └── index.html # HTML 模板
├── dist/ # 构建输出
├── scripts/
│ └── build.js # 构建脚本
├── esbuild.config.js # esbuild 配置
├── package.json
└── tsconfig.json # TypeScript 配置(可选)
配置文件组织 #
javascript
// esbuild.config.js
const esbuild = require('esbuild');
const { createBuildConfig } = require('./scripts/build-config');
const isDev = process.env.NODE_ENV !== 'production';
async function build() {
const config = createBuildConfig({
isDev,
entryPoints: ['src/index.js'],
outdir: 'dist',
});
if (isDev) {
const ctx = await esbuild.context(config);
await ctx.watch();
await ctx.serve({ servedir: 'dist', port: 3000 });
} else {
await esbuild.build(config);
}
}
build().catch(() => process.exit(1));
javascript
// scripts/build-config.js
function createBuildConfig(options) {
const { isDev, entryPoints, outdir } = options;
return {
entryPoints,
outdir,
bundle: true,
minify: !isDev,
sourcemap: isDev,
target: ['es2020', 'chrome80', 'firefox78', 'safari14'],
define: {
'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
},
loader: {
'.js': 'js',
'.jsx': 'jsx',
'.ts': 'ts',
'.tsx': 'tsx',
'.css': 'css',
'.png': 'dataurl',
'.svg': 'file',
},
};
}
module.exports = { createBuildConfig };
性能优化 #
1. 减少打包体积 #
排除不必要的依赖 #
javascript
esbuild.build({
external: [
'react',
'react-dom',
'lodash-es',
// 其他 CDN 加载的库
],
});
使用 ES 模块版本 #
javascript
// ✅ 好 - 使用 ES 模块,支持 Tree-shaking
import { debounce, throttle } from 'lodash-es';
// ❌ 差 - CommonJS 不支持 Tree-shaking
import _ from 'lodash';
const { debounce } = _;
按需导入 #
javascript
// ✅ 好 - 按需导入
import { Button } from 'antd';
// ❌ 差 - 导入全部
import antd from 'antd';
2. 代码分割优化 #
javascript
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outdir: 'dist',
format: 'esm',
splitting: true,
chunkNames: 'chunks/[name]-[hash]',
});
3. 缓存策略 #
javascript
esbuild.build({
entryNames: '[name]-[hash]',
chunkNames: 'chunks/[name]-[hash]',
assetNames: 'assets/[name]-[hash]',
});
4. 压缩优化 #
javascript
esbuild.build({
minify: true,
minifyWhitespace: true,
minifyIdentifiers: true,
minifySyntax: true,
drop: ['console', 'debugger'],
legalComments: 'none',
});
5. 目标环境优化 #
javascript
// 根据用户群体设置目标
const targets = {
modern: ['es2022', 'chrome100', 'firefox100', 'safari15'],
standard: ['es2020', 'chrome80', 'firefox78', 'safari14'],
compatible: ['es2015', 'chrome60', 'firefox60', 'safari11'],
};
esbuild.build({
target: targets.standard,
});
开发环境配置 #
完整开发配置 #
javascript
const esbuild = require('esbuild');
const { createServer } = require('http');
const { spawn } = require('child_process');
async function startDev() {
const ctx = await esbuild.context({
entryPoints: ['src/index.jsx'],
bundle: true,
outdir: 'dist',
format: 'esm',
sourcemap: 'linked',
target: 'es2020',
loader: {
'.jsx': 'jsx',
'.css': 'css',
'.png': 'dataurl',
'.svg': 'file',
},
define: {
'process.env.NODE_ENV': '"development"',
},
banner: {
js: 'new EventSource("/esbuild").addEventListener("change", () => location.reload());',
},
});
await ctx.watch();
const { host, port } = await ctx.serve({
servedir: 'dist',
port: 3000,
});
console.log(`🚀 Dev server: http://${host}:${port}`);
}
startDev().catch(console.error);
热更新实现 #
html
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Dev</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/index.js"></script>
<script>
new EventSource('/esbuild').addEventListener('change', () => {
location.reload();
});
</script>
</body>
</html>
TypeScript 开发配置 #
javascript
const esbuild = require('esbuild');
const { exec } = require('child_process');
async function devWithTypescript() {
// 类型检查(并行运行)
const tsc = exec('tsc --noEmit --watch');
tsc.stdout?.pipe(process.stdout);
tsc.stderr?.pipe(process.stderr);
// esbuild 构建
const ctx = await esbuild.context({
entryPoints: ['src/index.tsx'],
bundle: true,
outdir: 'dist',
sourcemap: true,
loader: { '.tsx': 'tsx', '.ts': 'ts' },
tsconfig: 'tsconfig.json',
});
await ctx.watch();
await ctx.serve({ servedir: 'dist' });
}
devWithTypescript();
生产环境配置 #
完整生产配置 #
javascript
const esbuild = require('esbuild');
const fs = require('fs').promises;
const path = require('path');
async function buildProduction() {
const startTime = Date.now();
const result = await esbuild.build({
entryPoints: ['src/index.jsx'],
bundle: true,
outdir: 'dist',
format: 'esm',
minify: true,
target: ['es2020', 'chrome80', 'firefox78', 'safari14'],
sourcemap: false,
metafile: true,
drop: ['console', 'debugger'],
legalComments: 'none',
loader: {
'.jsx': 'jsx',
'.css': 'css',
'.png': 'dataurl',
'.svg': 'file',
'.woff': 'file',
'.woff2': 'file',
},
define: {
'process.env.NODE_ENV': '"production"',
},
entryNames: '[name]-[hash]',
chunkNames: 'chunks/[name]-[hash]',
assetNames: 'assets/[name]-[hash]',
});
const duration = Date.now() - startTime;
console.log(`✅ Build completed in ${duration}ms`);
// 分析构建结果
const analysis = await esbuild.analyzeMetafile(result.metafile);
console.log('\n📦 Bundle Analysis:');
console.log(analysis);
return result;
}
buildProduction().catch((err) => {
console.error('❌ Build failed:', err);
process.exit(1);
});
多环境配置 #
javascript
const configs = {
development: {
minify: false,
sourcemap: true,
drop: [],
define: {
'process.env.NODE_ENV': '"development"',
'process.env.API_URL': '"http://localhost:3001"',
},
},
staging: {
minify: true,
sourcemap: 'linked',
drop: ['console'],
define: {
'process.env.NODE_ENV': '"staging"',
'process.env.API_URL': '"https://staging.api.example.com"',
},
},
production: {
minify: true,
sourcemap: false,
drop: ['console', 'debugger'],
define: {
'process.env.NODE_ENV': '"production"',
'process.env.API_URL': '"https://api.example.com"',
},
},
};
const env = process.env.NODE_ENV || 'development';
const envConfig = configs[env];
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outdir: 'dist',
...envConfig,
});
库开发配置 #
NPM 库配置 #
javascript
const esbuild = require('esbuild');
const pkg = require('./package.json');
const external = [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
];
const baseConfig = {
entryPoints: ['src/index.ts'],
bundle: true,
external,
sourcemap: true,
loader: { '.ts': 'ts' },
};
async function buildLibrary() {
// ESM
await esbuild.build({
...baseConfig,
format: 'esm',
outfile: pkg.module,
});
// CommonJS
await esbuild.build({
...baseConfig,
format: 'cjs',
outfile: pkg.main,
});
// UMD(可选)
await esbuild.build({
...baseConfig,
format: 'umd',
globalName: 'MyLibrary',
outfile: pkg.browser,
});
console.log('✅ Library build complete');
}
buildLibrary();
package.json 配置 #
json
{
"name": "my-library",
"version": "1.0.0",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"browser": "dist/index.umd.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js",
"browser": "./dist/index.umd.js",
"types": "./dist/index.d.ts"
},
"./styles.css": "./dist/styles.css"
},
"files": ["dist"],
"sideEffects": false,
"peerDependencies": {
"react": ">=17.0.0"
},
"devDependencies": {
"esbuild": "^0.20.0",
"typescript": "^5.0.0"
},
"scripts": {
"build": "node scripts/build.js",
"prepublishOnly": "npm run build"
}
}
框架集成 #
React 项目 #
javascript
const esbuild = require('esbuild');
const isDev = process.env.NODE_ENV !== 'production';
esbuild.build({
entryPoints: ['src/index.jsx'],
bundle: true,
outdir: 'dist',
format: 'esm',
jsx: 'automatic',
jsxImportSource: 'react',
loader: {
'.jsx': 'jsx',
'.css': 'css',
},
define: {
'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
},
external: isDev ? [] : ['react', 'react-dom'],
});
Vue 项目 #
javascript
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/main.js'],
bundle: true,
outdir: 'dist',
loader: {
'.vue': 'js',
'.css': 'css',
},
external: ['vue'],
define: {
'process.env.NODE_ENV': '"production"',
},
});
Next.js 集成 #
javascript
// next.config.js
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
path: false,
};
}
return config;
},
};
Vite 集成 #
javascript
// vite.config.js
import { defineConfig } from 'vite';
import esbuild from 'rollup-plugin-esbuild';
export default defineConfig({
plugins: [
esbuild({
target: 'es2020',
loaders: {
'.js': 'js',
'.ts': 'ts',
},
}),
],
});
安全配置 #
移除敏感信息 #
javascript
esbuild.build({
define: {
'process.env.SECRET_KEY': 'undefined',
'process.env.API_SECRET': 'undefined',
},
drop: ['console'],
});
内容安全策略 #
javascript
const cspPlugin = {
name: 'csp',
setup(build) {
build.onEnd(() => {
console.log('Remember to set CSP headers:');
console.log("Content-Security-Policy: default-src 'self'; script-src 'self'");
});
},
};
外部资源验证 #
javascript
const validateExternalPlugin = {
name: 'validate-external',
setup(build) {
const allowedExternals = ['react', 'react-dom', 'lodash-es'];
build.onResolve({ filter: /.*/ }, (args) => {
if (args.path.startsWith('.') || args.path.startsWith('/')) {
return null;
}
if (!allowedExternals.includes(args.path)) {
console.warn(`Warning: Unrecognized external module: ${args.path}`);
}
return { path: args.path, external: true };
});
},
};
CI/CD 集成 #
GitHub Actions #
yaml
name: Build
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Type check
run: npm run typecheck
- name: Build
run: npm run build
env:
NODE_ENV: production
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
Docker 集成 #
dockerfile
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
构建脚本 #
json
{
"scripts": {
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"build:analyze": "node scripts/build.js --analyze",
"typecheck": "tsc --noEmit",
"lint": "eslint src/",
"test": "vitest",
"ci": "npm run lint && npm run typecheck && npm run test && npm run build"
}
}
常见问题解决 #
1. 模块解析错误 #
javascript
// 问题:Cannot find module 'xxx'
// 解决:添加路径别名或检查导入路径
esbuild.build({
alias: {
'@': './src',
'@components': './src/components',
},
nodePaths: ['./src'],
});
2. CommonJS 兼容性 #
javascript
// 问题:CommonJS 模块导入问题
// 解决:使用 default 导入或配置
esbuild.build({
mainFields: ['module', 'main'],
banner: {
js: `
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
`,
},
});
3. Node.js polyfills #
javascript
// 问题:Node.js 内置模块在浏览器中不可用
// 解决:添加 polyfills
esbuild.build({
define: {
global: 'globalThis',
'process.env.NODE_ENV': '"production"',
},
inject: ['./scripts/node-polyfills.js'],
});
javascript
// scripts/node-polyfills.js
export const process = {
env: {},
version: 'v18.0.0',
};
export const Buffer = {
from: (str) => str,
isBuffer: () => false,
};
export const __dirname = '/';
export const __filename = '/index.js';
4. CSS 问题 #
javascript
// 问题:CSS 导入顺序或模块化问题
// 解决:使用 CSS Modules 或单独处理
esbuild.build({
loader: {
'.module.css': 'local-css',
'.css': 'css',
},
cssModules: {
pattern: '[name]-[hash]',
},
});
5. 大型项目优化 #
javascript
// 问题:大型项目构建慢
// 解决:增量构建和缓存
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outdir: 'dist',
incremental: true,
});
// 首次构建
await ctx.rebuild();
// 后续增量构建
await ctx.rebuild();
调试技巧 #
构建分析 #
javascript
const result = await esbuild.build({
metafile: true,
});
// 详细分析
const analysis = await esbuild.analyzeMetafile(result.metafile, {
verbose: true,
});
console.log(analysis);
调试插件 #
javascript
const debugPlugin = {
name: 'debug',
setup(build) {
build.onResolve({ filter: /.*/ }, (args) => {
console.log('[Resolve]', args.path, 'from', args.importer);
return null;
});
build.onLoad({ filter: /.*/ }, (args) => {
console.log('[Load]', args.path);
return null;
});
},
};
错误追踪 #
javascript
esbuild.build({
logLevel: 'debug',
logLimit: 100,
}).catch((err) => {
console.error('Build error:', err);
if (err.errors) {
err.errors.forEach((e) => {
console.error(` ${e.text}`);
if (e.location) {
console.error(` at ${e.location.file}:${e.location.line}`);
}
});
}
});
总结 #
esbuild 最佳实践清单:
text
┌─────────────────────────────────────────────────────────────┐
│ esbuild 最佳实践清单 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ 项目结构 │
│ ├── 合理的目录组织 │
│ ├── 配置文件分离 │
│ └── 环境变量管理 │
│ │
│ ✅ 性能优化 │
│ ├── 代码分割 │
│ ├── Tree-shaking │
│ ├── 缓存策略 │
│ └── 压缩优化 │
│ │
│ ✅ 开发体验 │
│ ├── 热更新 │
│ ├── Source Map │
│ ├── 类型检查 │
│ └── 错误提示 │
│ │
│ ✅ 生产部署 │
│ ├── 多环境配置 │
│ ├── 安全检查 │
│ ├── CI/CD 集成 │
│ └── 构建分析 │
│ │
└─────────────────────────────────────────────────────────────┘
恭喜你完成了 esbuild 文档的学习!现在你已经掌握了从入门到专家级别的 esbuild 知识,可以自信地在项目中使用 esbuild 了!
最后更新:2026-03-28