repo
← all guides

Bundler

esbuild

Update from CommonJS to ES Module output in esbuild

esbuild is a JavaScript bundler built in Go. It handles ESM natively and bundles for the browser out of the box. Most of the work here is removing the CommonJS output you no longer need.

1. Mark the package as ESM

Add type: "module" to your package.json. Every .js file in the package is now treated as an ES module.

{
	"name": "my-package",
	"type": "module"
}

Any file that genuinely still has to be CommonJS (e.g. a config file for an older tool) can keep the .cjs extension.

2. Emit a single ESM bundle

Since the default for esbuild is ESM, you can omit the format parameter if you've previously defined cjs as its value.

import * as esbuild from 'esbuild';

await esbuild.build({
	entryPoints: ['src/index.js'],
	bundle: true,
	outfile: 'dist/index.js'
});

Alternatively, if you are using the CLI:

esbuild "src/index.js" --bundle --outfile="dist/index.js"

If you were previously building both CJS and ESM formats, remove the separate build step or output for CommonJS.

"platform" flag

If you are using the platform parameter with the value of node and bundle is set to true, this will default to the cjs format. In this case, you need to explicitly define the format to esm:

import * as esbuild from 'esbuild';

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

and in the CLI:

esbuild "src/index.js" --platform=node --bundle --format=esm --outfile="dist/index.js"

3. Point exports at the bundle

Replace main with an exports field. This is what lets Node.js and bundlers resolve your package, and it stops deep imports into your dist folder from becoming part of your public API by accident.

{
	"exports": {
		".": "./dist/index.js"
	},
	"files": ["dist"]
}

4. Check the result

Run publint to catch problems with leftover CommonJS references, publishing config, etc.

npx publint