mrkeyoor.com_
Sat 19 Sept 15:51 UTC
npmCLI & Toolingupdated 19 Sept 2026

esbuild review

esbuild 0.28.2 is a native Go program exposed through a command line, a JavaScript API, and a Go API. It bundles ESM and CommonJS, strips TypeScript and JSX syntax, processes CSS, lowers syntax for selected runtimes, minifies output, writes source maps, watches files, and serves development assets. This patch repairs wrong output involving top-level await cycles, logical assignments, CSS nesting, and out-of-gamut colors. It also restores input overwrite protection and adds Visual Studio diagnostics.

251.7Mdownloads / wk
Verdict

esbuild 0.28.2 installed in 1.2 seconds and used 12 MB in our sandbox, with 0 audit findings and working CommonJS and ESM loading, so it is an efficient build primitive for Node-based pipelines. Skip it when you need built-in type checking, framework HMR, or AST-level plugins.

We installed it

Lab card: what happened when we installed esbuildScreenshot of esbuild documentation
Install✓ · 1.2s2 packages on disk · 12 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does esbuild install cleanly?

Yes. In a fresh container with an empty cache, npm install esbuild finished in 1 seconds, leaving 2 packages and 12 MB on disk. npm audit reported no known vulnerabilities.

Can esbuild run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does esbuild work with both ESM and CommonJS?

Yes. Both import 'esbuild' and require('esbuild') worked in Node 22 in our run. The package is published as CommonJS.

Does esbuild include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

esbuild or rollup: which should you use?

rollup: Choose it for library packaging that needs fine output control and a broad transformation plugin catalog. esbuild 0.28.2 installed in 1.2 seconds and used 12 MB in our sandbox, with 0 audit findings and working CommonJS and ESM loading, so it is an efficient build primitive for Node-based pipelines.

When should you not use esbuild?

Do not use esbuild as your TypeScript checker. It erases type syntax and can emit JavaScript from code that tsc rejects.

API stability3/5The `build`, `transform`, and `context` entry points in esbuild 0.28.2 retain the familiar option model, and command-line flags closely match JavaScript properties. The package is still below 1.0, so minor releases may break behavior. Watch and serve moved into contexts in an earlier change, and correctness patches regularly alter generated JavaScript or CSS, making exact version pinning sensible.
Docs5/5esbuild's official site documents options in command-line, JavaScript, and Go forms, with separate material for loaders, CSS, plugins, watch mode, the server, and known limits. It plainly says that TypeScript type checking is absent and explains cross-platform native-package failures. Release 0.28.2 notes include minimal inputs and incorrect outputs for each code-generation fix, which makes upgrade review practical.
Maintenance4/5The evanw/esbuild repository is active and unarchived, with a push recorded on August 9, 2026. GitHub reports 40,018 stars and 608 open issues and pull requests. Release 0.28.2 arrived on August 8 and fixed multiple JavaScript and CSS correctness defects, input overwrite behavior, child-process accounting, target collisions, and editor log formatting. Maintenance is current, although project stewardship is concentrated.
Ecosystem5/5npm counted 271,867,617 esbuild downloads from August 19 through August 25, 2026, and GitHub reports 40,018 stars. Vite, tsup, and many framework toolchains use it directly or transitively. Our package check found bundled TypeScript declarations plus working `require()` and ESM import. Its plugin surface remains intentionally narrower than Rollup or webpack because parsed syntax trees are private.

Discussed on

  1. hnEsbuild – An extremely fast JavaScript bundler679 points
  2. hnESbuild – A fast JavaScript bundler and minifier in Go355 points
  3. hnWhy Is Esbuild Fast?320 points
  4. hnEsbuild 0.9254 points
  5. hnSome notes on using esbuild246 points

Use it if

  • A Node service, command-line program, worker, or small library needs bundling and syntax lowering without a large configuration layer.
  • You want TypeScript or JSX transformed quickly while `tsc` runs separately for type errors and declaration files.
  • A build tool needs a programmable transform, bundle, watch, serve, or module-loading primitive.
  • Multiple browser entry points need shared ESM chunks and the project does not need a framework-specific development server.
Skip it if

Setup reality

We installed esbuild 0.28.2 in a clean Node 22 Bookworm container in 1.2 seconds. The result was 2 packages and 12 MB on disk, with 0 findings from npm audit. The package declares 0 direct and 0 peer dependencies and is 11,320 KB unpacked. It requires Node 18+, bundles TypeScript declarations, uses CommonJS without an exports map, and loaded through both require() and ESM import in our test.

The wrapper depends on a matching optional package for its native executable. Install dependencies on the operating system and CPU that will execute the build. Copying node_modules between macOS and Linux or between arm64 and x64 commonly leaves the wrapper with the wrong binary. Private mirrors must carry the required @esbuild/* package, and disabling optional dependencies prevents the usual binary selection.

No config file is mandatory in version 0.28.2. CLI flags and JavaScript options cover the same build concepts. The context() API owns watch and serve state, so shutdown code should call dispose() to close file watchers and the long-lived child process. This release fixes a deadlock report reached after certain invalid API calls, but callers still need to await each operation and print esbuild's structured errors.

TypeScript conversion is syntax-only: it does not check assignments, resolve type semantics, or emit .d.ts files. Run tsc --noEmit beside application builds and use tsc or another declaration tool for packages. Set platform, format, target, and package externalization rather than inheriting context-sensitive defaults. Version 0.28.2 again blocks an output path that equals an input unless allowOverwrite is explicitly enabled. Our attempt to bundle esbuild itself for a browser failed, so keep its API in Node tooling.

Patterns

Bundle one Node command bundle-node-cli

npx esbuild src/cli.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/cli.js

`--bundle` follows imports; without it the output keeps them. Set the target to the oldest Node release in production.

Run a build from JavaScript build-with-api

import { build } from 'esbuild';

await build({
  entryPoints: ['src/index.ts'],
  bundle: true,
  platform: 'node',
  format: 'esm',
  target: 'node22',
  outfile: 'dist/index.js',
  sourcemap: true,
});

The promise rejects with structured diagnostics. The JavaScript wrapper sends work to a native child process.

Keep a watch context alive watch-build

import { context } from 'esbuild';

const ctx = await context({
  entryPoints: ['src/app.ts'],
  bundle: true,
  outdir: 'dist',
});

await ctx.watch();
process.on('SIGTERM', () => ctx.dispose());

Watch state belongs to the context. Call `dispose()` during shutdown so the watcher and native service do not remain open.

Serve rebuilt browser files serve-static-build

const ctx = await context({
  entryPoints: ['src/browser.tsx'],
  bundle: true,
  outdir: 'public/assets',
});

const server = await ctx.serve({ servedir: 'public' });
console.log(server.hosts, server.port);

The built-in server rebuilds and serves a directory. It does not supply framework-specific hot updates or HTML transforms.

Minify and write a separate map minify-production-output

await build({
  entryPoints: ['src/app.ts'],
  bundle: true,
  minify: true,
  sourcemap: 'external',
  outfile: 'dist/app.min.js',
});

The single `minify` switch enables syntax, identifier, and whitespace passes. Test pinned upgrades because minifier fixes can change emitted code.

Replace a build constant safely replace-build-constant

await build({
  entryPoints: ['src/app.ts'],
  bundle: true,
  define: {
    __BUILD_MODE__: JSON.stringify('production'),
  },
  outfile: 'dist/app.js',
});

`define` accepts JavaScript expressions, so string values need quotes. `JSON.stringify` creates a valid string literal for this case.

Keep Node packages external externalize-node-packages

await build({
  entryPoints: ['src/server.ts'],
  bundle: true,
  platform: 'node',
  packages: 'external',
  outfile: 'dist/server.cjs',
  format: 'cjs',
});

`packages: external` leaves every bare package import for Node. Use named `external` entries if only selected dependencies should stay out.

Transform a TypeScript string transform-typescript-string

import { transform } from 'esbuild';

const result = await transform('const count: number = 2', {
  loader: 'ts',
  target: 'es2022',
  sourcemap: 'inline',
});

console.log(result.code);

`transform` handles one source string and never resolves imports or verifies that a type annotation is correct.

Split shared ESM chunks split-esm-chunks

await build({
  entryPoints: ['src/home.ts', 'src/admin.ts'],
  bundle: true,
  splitting: true,
  format: 'esm',
  outdir: 'dist',
});

Splitting requires ESM output and an output directory. Entry points and dynamic imports can each create chunks.

Supply a virtual module load-virtual-module

const buildInfo = {
  name: 'build-info',
  setup(api) {
    api.onResolve({ filter: /^virtual:build$/ }, () => ({
      path: 'build', namespace: 'virtual',
    }));
    api.onLoad({ filter: /.*/, namespace: 'virtual' }, () => ({
      contents: `export default ${JSON.stringify({ sha: process.env.GIT_SHA })}`,
      loader: 'js',
    }));
  },
};

Plugins control resolution and loading, not esbuild's internal syntax tree. Never put a secret into generated browser source.

Run type checks before bundling typecheck-before-build

{
  "scripts": {
    "check": "tsc --noEmit",
    "build": "npm run check && esbuild src/index.ts --bundle --outfile=dist/index.js"
  }
}

esbuild removes types even when an assignment is invalid. Keep `tsc` responsible for semantic errors and declaration output.

Alternatives

PackageRegistryPick it when
rollupnpmChoose it for library packaging that needs fine output control and a broad transformation plugin catalog.
webpacknpmChoose it when a mature loader and plugin system must cover a large, unusual browser application.
@swc/corenpmChoose it for fast syntax transformation inside an existing test runner or bundling pipeline.

More cli & tooling guides

commander · chalk · typescript · yargs · click · vite · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.