rollup
Rollup is the module bundler that made ES-module-first builds and tree shaking mainstream. You point it at an entry file and it emits a bundle in ESM, CommonJS, UMD, or IIFE format, keeping only the code that is actually imported. Its core niche today is building JavaScript libraries for npm, and it is also the production bundler inside Vite. Since v4 the parser is native Rust code shipped as per-platform binaries, which made it faster and made installation touchier in some environments.
Still the best tool for bundling libraries and the engine under Vite production builds. For apps use Vite, for raw speed use esbuild, and keep an eye on Rolldown, which is being built to take over this niche.
Use it if
- You are publishing a library to npm and want clean ESM plus CJS output with the most mature tree shaking available
- You need precise control over output: multiple formats, preserved module structure, banners, manual chunks
- You already use Vite and need to tune the production build, since Vite build options are Rollup options underneath
- You want an official plugin suite (@rollup/*) covering TypeScript, CommonJS interop, node resolution, JSON, and minification
- You are building an application, not a library: Vite gives you a dev server, HMR, and sane defaults on top of Rollup and you will finish faster
- Raw build speed is your bottleneck: esbuild does comparable bundling far faster because Rollup does much more per-module analysis
- Your dependency tree is heavy CommonJS: everything goes through @rollup/plugin-commonjs, and its interop edge cases can eat an afternoon
- You are picking a long-term default in 2026: the Vite team is building Rolldown, a Rust rewrite aimed at exactly this niche, so check its status before committing a new toolchain
Setup reality
npm install rollup lists 27 optional platform packages and the right native binary is picked at install time. The classic failure is npm's optional-dependency bug: move a lockfile or node_modules across OS, libc, or CPU arch (macOS to Alpine Docker, x64 to arm64) and you get "Cannot find module @rollup/rollup-linux-x64-gnu" until you delete the lockfile and reinstall. Config lives in rollup.config.mjs (a .js config inside a CJS package needs --bundleConfigAsCjs), and TypeScript, CommonJS deps, JSON imports, and minification each need a separate @rollup/* plugin, so a real library build ends up with four to six plugins. Requires Node 18+.
Patterns
Write a basic config fileconfig-file
// rollup.config.mjs
export default {
input: 'src/index.js',
output: { file: 'dist/bundle.js', format: 'esm' }
};Use the .mjs extension; a .js config inside a CommonJS package needs the --bundleConfigAsCjs flag.
Emit ESM and CJS from one entrydual-esm-cjs
export default {
input: 'src/index.js',
output: [
{ file: 'dist/index.mjs', format: 'esm' },
{ file: 'dist/index.cjs', format: 'cjs', exports: 'named' }
]
};Set exports: "named" explicitly for CJS output, or Rollup warns when you mix default and named exports.
Bundle from the command linecli-build
npx rollup src/main.js --format iife --name myLib --file dist/bundle.js
npx rollup -c # use rollup.config.mjs
npx rollup -c -w # rebuild on changeIIFE and UMD formats require --name because the bundle gets assigned to a global variable.
Bundle npm dependenciesresolve-node-modules
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
export default {
input: 'src/index.js',
output: { file: 'dist/bundle.js', format: 'esm' },
plugins: [resolve(), commonjs()]
};Rollup resolves nothing from node_modules on its own; without these two plugins every bare import is treated as external.
Compile TypeScript inputtypescript
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/index.ts',
output: { dir: 'dist', format: 'esm' },
plugins: [typescript()]
};The plugin transpiles only; many libraries run a separate tsc pass with emitDeclarationOnly for the .d.ts files.
Keep dependencies out of a library bundleexternal-deps
export default {
input: 'src/index.js',
external: ['react', 'react-dom', /^node:/],
output: { file: 'dist/index.mjs', format: 'esm' }
};Mark every dependency and peerDependency external when publishing a library, or you will inline React into your package.
Minify one output with terserminify
import terser from '@rollup/plugin-terser';
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.min.js',
format: 'iife',
name: 'myLib',
plugins: [terser()]
}
};Put terser in output.plugins, not top-level plugins, so only that specific output gets minified.
Inline environment values at build timereplace-env
import replace from '@rollup/plugin-replace';
export default {
input: 'src/index.js',
output: { file: 'dist/bundle.js', format: 'esm' },
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
preventAssignment: true
})
]
};Always pass preventAssignment: true; without it the plugin also rewrites assignment targets and prints a warning.
Bundle programmaticallyjs-api
import { rollup } from 'rollup';
const bundle = await rollup({ input: 'src/index.js' });
const { output } = await bundle.generate({ format: 'esm' });
await bundle.write({ file: 'dist/out.js', format: 'esm' });
await bundle.close();Call bundle.close() when done; it releases plugin resources and pending work, and skipping it can hang scripts.
Split output with multiple entriescode-splitting
export default {
input: { main: 'src/main.js', worker: 'src/worker.js' },
output: {
dir: 'dist',
format: 'esm',
chunkFileNames: 'chunks/[name]-[hash].js'
}
};Multiple entries force output.dir instead of output.file; modules shared between entries become automatic chunks.
Tighten tree shakingtree-shake-control
export default {
input: 'src/index.js',
treeshake: { moduleSideEffects: false },
output: { file: 'dist/index.mjs', format: 'esm' }
};moduleSideEffects: false drops imports whose exports go unused; if a dependency relies on import side effects (polyfills, CSS) this silently breaks it.
Import JSON filesjson-import
import json from '@rollup/plugin-json';
export default {
input: 'src/index.js',
output: { file: 'dist/bundle.js', format: 'cjs' },
plugins: [json()]
};Plain JSON imports fail without this plugin even though Node itself can import JSON.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| vite | npm | You are building an app and want a dev server and defaults; it uses Rollup for production builds anyway |
| esbuild | npm | Build speed matters more than fine-grained output control or maximum tree shaking |
| rolldown | npm | You want the Rust successor the Vite team is building for this exact role; newer and still stabilizing |
| webpack | npm | You need its ecosystem-specific features like module federation or deeply custom loader chains |