Claude Code for esbuild: Ultrafast JavaScript Bundler and Transformer — Claude Skills 360 Blog
Blog / Tooling / Claude Code for esbuild: Ultrafast JavaScript Bundler and Transformer
Tooling

Claude Code for esbuild: Ultrafast JavaScript Bundler and Transformer

Published: February 25, 2027
Read time: 7 min read
By: Claude Skills 360

esbuild bundles JavaScript 10–100x faster than webpack or rollup — written in Go, it processes code in parallel without a plugin dependency graph. The JavaScript API build({ entryPoints, bundle, outdir }) bundles from entry points. transform({ loader: "ts", code }) transpiles a single file without bundling. Custom plugins use onResolve to intercept import resolution and onLoad to return synthetic module contents. Code splitting with splitting: true and format: "esm" emits shared chunks. metafile: true generates a dependency map for esbuild.analyzeMetafile. Watch mode calls ctx.watch() for incremental re-builds. external marks packages that shouldn’t be bundled — critical for library authors. CSS is bundled natively alongside JS. Claude Code generates esbuild build scripts, plugin implementations, library bundling configurations, and the Node.js build pipelines that replace webpack or rollup for fast CI builds.

CLAUDE.md for esbuild

## esbuild Stack
- Version: esbuild >= 0.24
- Build: esbuild.build({ entryPoints, bundle: true, outdir, format, platform })
- Transform: esbuild.transform(code, { loader: "ts|tsx|jsx" }) — single file
- Plugins: { name, setup(build) { build.onResolve({ filter }, fn); build.onLoad({ filter }, fn) } }
- Splitting: splitting: true + format: "esm" + outdir (not outfile) — shared chunks
- Watch: ctx = await context({...}); ctx.watch() — incremental rebuild
- Library: external: [...peerDeps], packages: "external" — don't bundle deps
- Meta: metafile: true → esbuild.analyzeMetafile(result.metafile)

Build Scripts

// scripts/build.ts — application bundle
import * as esbuild from "esbuild"

const isProduction = process.env.NODE_ENV === "production"

// Application build — bundled for browser
await esbuild.build({
  entryPoints: ["src/main.tsx"],
  bundle: true,
  outdir: "dist",
  format: "esm",
  splitting: true,         // Code splitting — shared chunks extracted
  chunkNames: "chunks/[name]-[hash]",
  assetNames: "assets/[name]-[hash]",

  // Target environments
  target: ["chrome100", "firefox100", "safari15"],
  platform: "browser",

  // Loaders
  loader: {
    ".png": "file",
    ".jpg": "file",
    ".svg": "dataurl",
    ".woff2": "file",
  },

  // Aliases (like Vite/webpack resolve.alias)
  alias: {
    "@": "./src",
  },

  // Production optimizations
  minify: isProduction,
  sourcemap: isProduction ? "external" : "inline",
  treeShaking: true,

  // Inject process.env replacements
  define: {
    "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV ?? "development"),
    "process.env.API_URL": JSON.stringify(process.env.API_URL ?? "http://localhost:3000"),
  },

  // Log
  logLevel: "info",
  metafile: true,
})
// scripts/build-server.ts — Node.js server bundle
import * as esbuild from "esbuild"
import { nodeExternalsPlugin } from "esbuild-plugin-node-externals"

await esbuild.build({
  entryPoints: ["src/server/index.ts"],
  bundle: true,
  outfile: "dist/server.js",
  format: "esm",
  platform: "node",
  target: "node20",

  // External: all node_modules (don't bundle server deps)
  plugins: [nodeExternalsPlugin()],
  // Or manually: packages: "external"

  sourcemap: true,
  minify: false,  // Keep server code readable for debugging
})

Library Build

// scripts/build-lib.ts — library with dual CJS/ESM output
import * as esbuild from "esbuild"
import { readFile } from "fs/promises"

const pkg = JSON.parse(await readFile("./package.json", "utf-8"))

// All peer and regular dependencies are external
const external = [
  ...Object.keys(pkg.peerDependencies ?? {}),
  ...Object.keys(pkg.dependencies ?? {}),
]

const shared = {
  entryPoints: {
    index: "src/index.ts",
    // Named entry points for tree-shaking consumers
    "components/Button": "src/components/Button.tsx",
    "components/Form": "src/components/Form.tsx",
    "utils/format": "src/utils/format.ts",
  },
  bundle: true,
  external,
  sourcemap: true,
  minify: false,
  treeShaking: true,
  target: "es2020",
}

// ESM build
await esbuild.build({
  ...shared,
  format: "esm",
  outdir: "dist/esm",
  outExtension: { ".js": ".mjs" },
})

// CJS build (for CommonJS consumers)
await esbuild.build({
  ...shared,
  format: "cjs",
  outdir: "dist/cjs",
  outExtension: { ".js": ".cjs" },
})

console.log("Library built — ESM + CJS")

Plugins

// scripts/plugins/env-plugin.ts — inject .env variables
import * as esbuild from "esbuild"
import { config } from "dotenv"

export function envPlugin(envFile = ".env"): esbuild.Plugin {
  return {
    name: "env",
    setup(build) {
      // Intercept "virtual:env" import
      build.onResolve({ filter: /^virtual:env$/ }, () => ({
        path: "virtual:env",
        namespace: "env-ns",
      }))

      build.onLoad({ filter: /.*/, namespace: "env-ns" }, () => {
        const { parsed } = config({ path: envFile })
        const entries = Object.entries(parsed ?? {})
          .map(([k, v]) => `export const ${k} = ${JSON.stringify(v)};`)
          .join("\n")

        return { contents: entries, loader: "js" }
      })
    },
  }
}
// scripts/plugins/raw-plugin.ts — import files as strings
import * as esbuild from "esbuild"
import { readFile } from "fs/promises"

export function rawPlugin(): esbuild.Plugin {
  return {
    name: "raw",
    setup(build) {
      // Match imports like: import sql from "./query.sql?raw"
      build.onResolve({ filter: /\?raw$/ }, args => ({
        path: args.path,
        namespace: "raw-ns",
        pluginData: { importer: args.importer, dir: args.resolveDir },
      }))

      build.onLoad({ filter: /.*/, namespace: "raw-ns" }, async (args) => {
        const realPath = args.path.replace(/\?raw$/, "")
        const contents = await readFile(
          require("path").resolve(args.pluginData.dir, realPath),
          "utf-8"
        )
        return {
          contents: `export default ${JSON.stringify(contents)}`,
          loader: "js",
        }
      })
    },
  }
}

Watch Mode

// scripts/dev.ts — watch mode with live reload
import * as esbuild from "esbuild"
import { createServer } from "http"
import { readFileSync } from "fs"

// WebSocket-based live reload
const clients: Set<ReturnType<typeof createSSEResponse>> = new Set()

function createSSEResponse(res: any) {
  res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  })
  return res
}

// esbuild context for incremental rebuilds
const ctx = await esbuild.context({
  entryPoints: ["src/main.tsx"],
  bundle: true,
  outdir: "dist",
  format: "esm",
  splitting: true,
  sourcemap: "inline",
  define: { "process.env.NODE_ENV": '"development"' },

  // On rebuild: notify connected clients
  plugins: [
    {
      name: "live-reload",
      setup(build) {
        build.onEnd((result) => {
          if (result.errors.length === 0) {
            clients.forEach(client => client.write("data: reload\n\n"))
            console.log(`[${new Date().toLocaleTimeString()}] Rebuilt`)
          }
        })
      },
    },
  ],
})

await ctx.watch()
console.log("Watching for changes...")

Metafile Analysis

// scripts/analyze.ts — analyze bundle size
import * as esbuild from "esbuild"

const result = await esbuild.build({
  entryPoints: ["src/main.tsx"],
  bundle: true,
  outdir: "dist/analyze",
  format: "esm",
  splitting: true,
  metafile: true,
  minify: true,
})

if (result.metafile) {
  // Text summary
  const text = await esbuild.analyzeMetafile(result.metafile, { verbose: false })
  console.log(text)

  // JSON for bundle visualization tools
  const { writeFile } = await import("fs/promises")
  await writeFile("dist/metafile.json", JSON.stringify(result.metafile))
  // Upload to https://esbuild.github.io/analyze/ for visual treemap
}

For the Vite bundler alternative that wraps Rollup for production builds but adds the dev server HMR experience, plugin ecosystem, and first-class framework support for React, Vue, and Svelte that esbuild alone doesn’t provide, see the Vite plugins guide for custom plugin development. For the Rollup bundler alternative when fine-grained output control for library builds with named chunks, manual chunks, and output plugins beyond esbuild’s simpler API is needed, the TypeScript library patterns cover the rollup configuration. The Claude Skills 360 bundle includes esbuild skill sets covering build scripts, plugins, and library bundling. Start with the free tier to try esbuild configuration generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free