Building Dual ESM and CJS Bundles with Rollup's Multi‑Output
Learn how Rollup's multi‑output lets you build ES module and CommonJS bundles from a single input, with a worked example, trade‑offs, and verification steps.
15 May 2026, 16:21 UTC

Problem
You maintain a JavaScript library that must be consumable both as an ES module (for modern bundlers and browsers) and as a CommonJS bundle (for Node.js projects that still rely on require). Building two separate bundles usually means running Rollup twice, which duplicates work and can lead to inconsistent outputs.
Thesis
Rollup’s multi‑output feature lets you define a single input and shared plugin pipeline while emitting multiple files—one for es format and one for cjs—in one pass, reducing build time and ensuring the same source transformations apply to both targets.
Worked Example
- Create a Rollup configuration file
rollup.config.jsin your project root. - Use an array for the
outputproperty, each object describing a target format. - Add a shared plugin (e.g., a banner plugin) that runs once for the whole build.
// rollup.config.js
import { banner } from 'rollup-plugin-banner';
export default {
input: 'src/index.js',
output: [
{ file: 'dist/my-lib.esm.js', format: 'es', sourcemap: true },
{ file: 'dist/my-lib.cjs.js', format: 'cjs', sourcemap: true, name: 'MyLib' }
],
plugins: [banner({ banner: '// My Library v1.0' })]
};
Run the build from the command line:
# Run in the project root; you need read access to src/ and write access to dist/
npx rollup -c
After the command finishes, inspect the generated files:
- The ES module build (
my-lib.esm.js) should containexportstatements, e.g.,export function foo() { … }. - The CommonJS build (
my-lib.cjs.js) should wrap the code in a UMD‑style factory or plain CommonJS, usingmodule.exportsandrequirecalls.
To verify each bundle works:
- Test the CJS bundle in Node:
node dist/my-lib.cjs.js(orrequire('./dist/my-lib.cjs.js')from another script). - Test the ES bundle in a browser‑compatible loader (e.g., using
type='module'in an HTML script tag) or with a tool likeesmin Node:node -r esm dist/my-lib.esm.js.
Trade‑off / Limitation
Plugins that mutate the module graph—such as those that call this.emitFile, resolve dynamic imports conditionally, or inject side‑effects—may produce different results for the ES and CJS outputs because Rollup treats the two formats as separate output phases. If a plugin’s behavior depends on the target format, you could end up with divergent bundles.
Practical check: after building, compare the import and require statements in both files. If you notice missing exports or extra polyfills in only one format, inspect the plugin’s documentation for format‑specific options or consider splitting the build.
Actionable Closing
Adopt the multi‑output configuration when you need to ship both ESM and CJS versions of a library and want a single source of truth for transformations. Keep your plugin set format‑agnostic, test each output in its target runtime, and treat the dist/ folder as disposable—you can roll back by deleting the generated files:
rm dist/my-lib.esm.js dist/my-lib.cjs.js
With this approach you gain faster builds and consistent code while retaining the flexibility to publish to both module systems.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.