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