esbuild Reference: CLI, JavaScript API, Loaders, Plugins & Build Scripts
esbuild is a JavaScript/TypeScript bundler written in Go. It’s 10–100x faster than webpack or Rollup for the same job. It doesn’t do everything — no TypeScript type-checking, no CSS modules, no HMR — but for bundling and transpiling it’s the fastest option by far. Vite uses esbuild for dependency pre-bundling in dev and for transpiling TypeScript. esbuild is best used directly when you need a fast, predictable build pipeline without a framework layer.
1. CLI Basics
Bundle, transform, target, format, and common CLI flags
# Install: npm install -D esbuild # Bundle entry point to output file: npx esbuild src/index.ts --bundle --outfile=dist/out.js # Common flags: npx esbuild src/index.ts \ --bundle \ # resolve + inline all imports --outfile=dist/bundle.js \ --platform=browser \ # browser (default) | node | neutral --target=es2020 \ # output syntax target (es2015, es2020, node18, chrome96...) --format=esm \ # esm | cjs | iife --minify \ # minify whitespace + identifiers + syntax --sourcemap \ # generate .js.map --define:process.env.NODE_ENV='"production"' # replace global identifiers # Output directory (multiple entry points): npx esbuild src/app.ts src/worker.ts \ --bundle \ --outdir=dist \ --splitting \ # code splitting (esm only) --format=esm # Watch mode (rebuilds on file change): npx esbuild src/index.ts --bundle --outfile=dist/out.js --watch # Transform only (no bundling — transpile one file): npx esbuild src/component.tsx --loader=tsx --target=es2019 # Named exports for library builds: npx esbuild src/index.ts \ --bundle \ --format=esm \ --external:react \ # don't bundle react (peer dep) --external:react-dom \ --outfile=dist/index.esm.js
2. JavaScript API
build(), transform(), context() for watch/serve, and options reference
import * as esbuild from "esbuild";
// One-shot build:
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outfile: "dist/out.js",
platform: "browser",
target: ["es2020", "chrome90", "firefox88"],
format: "esm",
minify: true,
sourcemap: true,
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
"__VERSION__": JSON.stringify("1.0.0"),
},
loader: {
".png": "dataurl", // inline images as base64
".svg": "text", // inline SVGs as strings
".wasm": "binary", // inline WASM as Uint8Array
},
external: ["react", "react-dom"],
splitting: true, // code splitting (esm only)
});
// Transform a code string (no filesystem):
const result = await esbuild.transform(`
const x: number = 42;
export default x;
`, {
loader: "ts",
target: "es2018",
});
console.log(result.code); // const x = 42; export default x;
// Watch + serve (dev server):
const ctx = await esbuild.context({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist",
format: "esm",
});
await ctx.watch();
await ctx.serve({ port: 3000, servedir: "." });
// Dispose when done:
await ctx.dispose();
3. Loaders & CSS
Loaders for different file types, CSS bundling, CSS modules limitation
// Built-in loaders (set per extension):
// js, jsx, ts, tsx — JavaScript/TypeScript (auto-detected by extension)
// json — imports as JS object
// text — imports as UTF-8 string
// base64 — imports as base64-encoded string
// dataurl — imports as "data:..." URI (inline)
// binary — imports as Uint8Array
// file — copies to outdir, imports as URL string
// css — CSS files (bundled with @import resolution)
// local-css — CSS Modules (scoped class names)
// empty — treats import as empty module (stub)
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist",
loader: {
".png": "file", // copied to dist/, imported as URL string
".svg": "dataurl", // inlined as base64 data URI
".txt": "text", // inlined as string
".wasm": "binary", // inlined as Uint8Array
".css": "css", // bundled CSS
".module.css": "local-css", // CSS Modules (generates scoped names)
},
});
// CSS bundling — esbuild resolves @import and bundles:
// src/styles.css: @import "./variables.css"; @import "./reset.css";
// → dist/out.css with all @imports inlined
// CSS limitations:
// - No CSS-in-JS (styled-components, emotion) — needs plugin
// - CSS Modules supported via "local-css" loader but no :composes
// - No PostCSS processing (no autoprefixer) — needs plugin
// - For full CSS pipeline, pair with Vite or use esbuild as transpiler only
4. Plugins
Plugin API — onResolve, onLoad, onEnd hooks for custom loaders and transforms
import * as esbuild from "esbuild";
// Plugin structure:
const myPlugin: esbuild.Plugin = {
name: "my-plugin",
setup(build) {
// Intercept import resolution:
build.onResolve({ filter: /^env$/ }, (args) => ({
path: args.path,
namespace: "env-ns",
}));
// Load a custom namespace as a virtual module:
build.onLoad({ filter: /.*/, namespace: "env-ns" }, () => ({
contents: JSON.stringify(process.env),
loader: "json",
}));
// Transform .md files to JS:
build.onLoad({ filter: /\.md$/ }, async (args) => {
const text = await fs.promises.readFile(args.path, "utf8");
return {
contents: `export default ${JSON.stringify(text)}`,
loader: "js",
};
});
// Run after build completes:
build.onEnd((result) => {
if (result.errors.length) console.error("Build failed");
else console.log("Build succeeded");
});
},
};
// Use plugin:
await esbuild.build({ entryPoints: ["src/index.ts"], plugins: [myPlugin] });
// Popular community plugins:
// esbuild-plugin-alias — path aliases without tsconfig
// esbuild-plugin-postcss — PostCSS (autoprefixer, etc.)
// esbuild-plugin-svgr — SVG to React components
// @fal-works/esbuild-plugin-global-externals — global window.React externals
5. Build Scripts & CI Patterns
Node.js build scripts, multi-format library builds, and type checking separately
// build.mjs — typical library build script:
import * as esbuild from "esbuild";
const sharedConfig = {
entryPoints: ["src/index.ts"],
bundle: true,
external: ["react", "react-dom"], // peer deps
sourcemap: true,
};
await Promise.all([
// ESM for bundlers (tree-shakeable):
esbuild.build({ ...sharedConfig, format: "esm", outfile: "dist/index.esm.js" }),
// CJS for Node.js / older tooling:
esbuild.build({ ...sharedConfig, format: "cjs", outfile: "dist/index.cjs.js" }),
]);
// package.json:
// "main": "dist/index.cjs.js",
// "module": "dist/index.esm.js",
// "exports": { ".": { "import": "./dist/index.esm.js", "require": "./dist/index.cjs.js" } }
// IMPORTANT: esbuild does NOT type-check. Run tsc separately:
// package.json scripts:
// "build": "node build.mjs",
// "type-check": "tsc --noEmit",
// "ci": "npm run type-check && npm run build"
// GitHub Actions:
// - run: node build.mjs
// - run: npx tsc --noEmit # fails if type errors
// Metadata analysis:
const result = await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist",
metafile: true, // generate metadata
});
const text = await esbuild.analyzeMetafile(result.metafile!);
console.log(text); // shows which files contribute to bundle size
Track Node.js and build tooling releases at ReleaseRun. Related: Vite 6 Reference | Turborepo Reference | TypeScript Reference
🔍 Free tool: npm Package Health Checker — check esbuild and related build tool packages for known CVEs and active maintenance.
Founded
2023 in London, UK
Contact
hello@releaserun.com