esbuild — Rules and Conventions
1. Philosophy
- Speed first — Written in Go, parallelized, no AST overhead. 10-100x faster than JS bundlers.
- Library, not framework — Use as a library (
build()API) or CLI. No dev server, no HMR. - Transpile only — No type checking. Pair with
tsc --noEmitfor types. - ESM-native — Outputs ESM/CJS/IIFE. Tree-shaking by default.
- Minimal config — Sensible defaults. Configure only what you need.
2. Minimum Versions
| Technology | Minimum Version |
|---|---|
| esbuild | 0.23+ |
| Node.js | 22+ |
| pnpm | 11+ |
3. Build API — build()
Basic usage
// build.ts
import { build } from "esbuild";
await build({
entryPoints: ["src/index.ts"],
bundle: true,
outfile: "dist/index.js",
format: "esm",
platform: "browser",
target: "es2022",
sourcemap: true,
minify: true,
splitting: true,
external: ["react", "react-dom"],
});
Essential options
| Option | Description | Typical Value |
|---|---|---|
entryPoints |
Input files | ['src/index.ts'] |
bundle |
Bundle deps | true |
outfile / outdir |
Single file vs dir | outfile for lib, outdir for app |
format |
Output format | 'esm' | 'cjs' | 'iife' |
platform |
Runtime target | 'browser' | 'node' | 'neutral' |
target |
JS version | 'es2022' (Baseline 2023+) |
sourcemap |
Source maps | true | 'inline' | 'external' |
minify |
Minify output | true (esbuild minifier) |
splitting |
Code splitting | true (requires outdir) |
external |
Exclude from bundle | ['react', 'react-dom'] |
loader |
File type handling | { '.ts': 'ts', '.png': 'dataurl' } |
define |
Global replacements | { 'process.env.NODE_ENV': '"production"' } |
banner / footer |
Inject code | '/* license */' |
treeShaking |
DCE | true (default) |
keepNames |
Preserve names | true for debugging |
4. Formats and Platforms — Defaults Matrix
| Format | Platform | Use Case | Global |
|---|---|---|---|
esm |
browser |
Modern apps, modules | window |
esm |
node |
Node ESM packages | globalThis |
cjs |
node |
Node CommonJS, legacy | global |
iife |
browser |
Script tag, no bundler | window |
Dual ESM/CJS (library)
// Build both formats
await Promise.all([
build({ ...common, format: "esm", outfile: "dist/index.mjs" }),
build({ ...common, format: "cjs", outfile: "dist/index.cjs" }),
]);
Package.json
exportsmap handles resolution — seepackage-managerskill.
5. Loaders and Assets
Built-in loaders
loader: {
'.ts': 'ts', // TypeScript (transpile only)
'.tsx': 'tsx', // TSX
'.js': 'js', // JavaScript
'.jsx': 'jsx', // JSX
'.json': 'json', // JSON as module
'.css': 'css', // CSS as string
'.png': 'dataurl', // Base64 inline
'.jpg': 'file', // Copy to outdir
'.svg': 'text', // Raw string
'.woff2': 'file', // Fonts
'.wasm': 'file' // WASM
}
Rules
ts/tsx= transpile only (no type check)css= returns CSS string, use withinject: trueor extractdataurlfor small assets (<10kb typical)filefor production assets (copies to outdir)- Custom loader only when built-in insufficient
6. Code Splitting
Entry points (automatic splitting)
await build({
entryPoints: ["src/app.ts", "src/admin.ts"],
outdir: "dist",
format: "esm",
splitting: true,
// Creates: dist/app.js, dist/admin.js, dist/chunk-*.js
});
Dynamic import (manual splitting)
// app.ts
const HeavyModule = await import("./heavy-module");
Rules Code Splitting
splitting: truerequiresoutdir(notoutfile)- Entry points = automatic split points
- Dynamic
import()= lazy-loaded chunks externaldeps not bundled — loaded at runtime
7. TypeScript — Transpile Only
await build({
entryPoints: ["src/index.ts"],
loader: { ".ts": "ts", ".tsx": "tsx" },
tsconfig: "tsconfig.json", // Uses compilerOptions from tsconfig
// esbuild ignores: types, interfaces, enums (use 'transform' for enums)
});
What esbuild handles
| Feature | Support |
|---|---|
import/export |
✅ |
type imports/exports |
✅ (stripped) |
| JSX / TSX | ✅ |
enum |
⚠️ transform option (default: preserve as object) |
namespace |
❌ |
| Decorators | ⚠️ experimental |
| Type checking | ❌ (use tsc --noEmit) |
Full TypeScript rules: see
typescriptskill. esbuild = transpiler only.
8. Watch Mode — context API
// Dev build with watch
const ctx = await build({
entryPoints: ["src/index.ts"],
outfile: "dist/index.js",
format: "esm",
watch: {
onRebuild(error, result) {
if (error) console.error("Rebuild failed:", error);
else console.log("Rebuilt:", new Date().toLocaleTimeString());
},
},
});
// Later: stop watching
await ctx.dispose();
Context API (advanced)
const ctx = await esbuild.context({
entryPoints: ["src/index.ts"],
outfile: "dist/index.js",
format: "esm",
});
await ctx.watch();
await ctx.rebuild(); // Trigger manual rebuild
await ctx.dispose();
9. Plugins
Core philosophy
- Minimal API —
setup(build)withonLoad,onResolve,onStart,onEnd - Namespace —
file://for virtual modules,http://for remote - Order — plugins run in array order
Essential patterns
// shim plugin
const shimPlugin = {
name: "shim",
setup(build) {
build.onResolve({ filter: /^buffer$/ }, () => ({
path: "buffer",
external: true,
}));
build.onLoad({ filter: /.*/, namespace: "file" }, async (args) => ({
contents: await fs.readFile(args.path, "utf8"),
loader: "js",
}));
},
};
// alias plugin
const aliasPlugin = {
name: "alias",
setup(build) {
build.onResolve({ filter: /^@\// }, (args) => ({
path: resolve(__dirname, "src", args.path.slice(2)),
namespace: "file",
}));
},
};
// env replacement
const envPlugin = {
name: "env",
setup(build) {
build.onResolve({ filter: /^env$/ }, () => ({
path: "env",
namespace: "env-ns",
}));
build.onLoad({ filter: /.*/, namespace: "env-ns" }, () => ({
contents: `export default ${JSON.stringify(process.env)}`,
loader: "js",
}));
},
};
Rules plugin
- Prefer built-in options (
define,external,aliasviaresolve.aliasin Vite) - Custom plugins only for: shims, virtual modules, custom loaders
- Keep plugins small — single responsibility
10. Bundle Analysis
Metafile
const result = await build({
entryPoints: ["src/index.ts"],
outfile: "dist/index.js",
format: "esm",
metafile: true,
});
// result.metafile = { inputs, outputs, inputs: { bytes, imports } }
await fs.writeFile("metafile.json", JSON.stringify(result.metafile, null, 2));
Visualize
# esbuild built-in
npx esbuild --analyze metafile.json
# Or use third-party
npx esbuild-visualizer metafile.json
Key metrics
- Bundle size (gzipped)
- Duplicate modules (should be 0)
- Large deps (candidates for
externalor code splitting) - Unused code (tree-shaking verification)
11. Methodology
Before using ANY esbuild config/plugin/pattern not documented in this skill:
- MCP Context7 (priority):
context7_resolve-library-id+context7_query-docsfor esbuild. - Official docs: esbuild.github.io — verify current API + options.
- Project config:
build.ts,esbuild.config.js,package.jsonscripts — verify against actual setup. - HARD RULE: If not in this skill AND cannot be verified against 2 authoritative sources → DO NOT USE IT. Document as assumption or risk in report to orchestrator.
12. Prohibitions
- ❌ Do not use esbuild for type checking — run
tsc --noEmitseparately - ❌ Do not use
enumwithouttransformoption — outputs verbose objects - ❌ Do not bundle
externaldeps (React, etc.) — bloats bundle - ❌ Do not use
outfilewithsplitting: true— requiresoutdir - ❌ Do not use experimental decorators without testing
- ❌ Do not skip
metafile: truein CI — no bundle visibility - ❌ Do not use as dev server — Vite owns that (see
viteskill)
13. References
Note: For TypeScript rules, see TypeScript Note: For package manager conventions and library publishing, see Package Manager Note: For Vite (dev server + build), see Vite
Last updated: 2026-08