|$ curl https://forge-ai.dev/api/markdown?path=docs/build-tools/rollup
$cat docs/build-tools-—-rollup.md
updated Recently·12 min read·published

Build Tools — Rollup

Build ToolsIntermediate
Introduction

Rollup is a module bundler for JavaScript that compiles small pieces of code into something larger and more complex, such as a library or application. Created by Rich Harris in 2015, Rollup pioneered tree-shaking and the ESM-first approach that has since influenced virtually every bundler in the ecosystem.

While Webpack dominates application bundling, Rollup is the preferred tool for publishing JavaScript libraries. Its focus on producing clean, efficient output makes it ideal for npm packages, component libraries, and any project where bundle size and ES module compatibility matter most.

Why Rollup?

Rollup excels in scenarios where Webpack and Vite (which uses Rollup under the hood in production) are overkill or produce suboptimal output. Its key advantages include:

FeatureRollupWebpackesbuild
Tree-shakingPioneer, most effectiveGoodGood
ESM outputFirst-class supportSupportedLimited
Plugin ecosystemMatureLargestGrowing
Library bundlingBest in classOverkillGood
Code splittingSupportedMatureLimited

info

Rollup is the default production bundler for Vite. When you build a Vite project for production, Vite delegates to Rollup for the actual bundling. This means Rollup's tree-shaking and output quality power most modern frontend builds.
Core Concepts

Rollup is built around ES modules (ESM). Unlike CommonJS, ESM has a static module structure — imports and exports are known at parse time, not runtime. This static structure is what enables Rollup's powerful tree-shaking.

rollup-concepts.mjs
JavaScript
1// ESM — static imports enable tree-shaking
2import { map, filter } from "lodash-es";
3
4// Rollup can statically analyze this import
5// and only include 'map' and 'filter' in the output,
6// eliminating the rest of lodash-es
7
8// Tree-shaking removes "dead code" — exports that
9// are imported but never used
10import { useEffect, useState, useCallback } from "react";
11
12// If useCallback is never referenced in the bundle,
13// Rollup will omit it from the output entirely.
14
15// Side-effect detection matters:
16import "./styles.css"; // side-effect — always included
17import "some-polyfill"; // side-effect — Rollup may warn
18import { knownExport } from "./utils"; // clean — tree-shakable

warning

Tree-shaking only works with ESM syntax. If you import from a CommonJS package, Rollup cannot statically analyze the exports and may include the entire module. Use @rollup/plugin-commonjs to convert CJS to ESM, but tree-shaking will be less effective.
Rollup Configuration

Rollup is configured via a rollup.config.mjs file that exports an array of configuration objects. Each object represents a build target with its own input, output, and plugins.

rollup.config.mjs
JavaScript
1// rollup.config.mjs
2import resolve from "@rollup/plugin-node-resolve";
3import commonjs from "@rollup/plugin-commonjs";
4import typescript from "@rollup/plugin-typescript";
5import terser from "@rollup/plugin-terser";
6import dts from "rollup-plugin-dts";
7
8const config = [
9 // ESM build
10 {
11 input: "src/index.ts",
12 output: {
13 dir: "dist",
14 format: "esm",
15 sourcemap: true,
16 entryFileNames: "[name].mjs",
17 },
18 plugins: [
19 resolve({ extensions: [".ts", ".js"] }),
20 commonjs(),
21 typescript({ tsconfig: "./tsconfig.json" }),
22 ],
23 external: ["react", "react-dom"], // peer dependencies
24 },
25 // CommonJS build
26 {
27 input: "src/index.ts",
28 output: {
29 dir: "dist",
30 format: "cjs",
31 sourcemap: true,
32 entryFileNames: "[name].cjs",
33 },
34 plugins: [
35 resolve({ extensions: [".ts", ".js"] }),
36 commonjs(),
37 typescript({ tsconfig: "./tsconfig.json" }),
38 ],
39 external: ["react", "react-dom"],
40 },
41 // TypeScript declarations
42 {
43 input: "src/index.ts",
44 output: [{ dir: "dist", format: "esm" }],
45 plugins: [dts()],
46 },
47];
48
49export default config;

info

For library authors, always build both ESM and CJS output. The package.json ""exports"'} field should point to the ESM build as the primary entry, with CJS as a fallback for older bundlers.
Key Plugins

Rollup's plugin ecosystem is mature and well-maintained. These are the essential plugins for most projects:

@rollup/plugin-node-resolve

Resolves third-party modules from node_modules using Node.js resolution algorithm. Rollup does not resolve external modules by default — this plugin is essential for any project with dependencies.

rollup-plugin-resolve.js
JavaScript
1import resolve from "@rollup/plugin-node-resolve";
2
3// Basic usage — resolves all node_modules imports
4resolve();
5
6// With options — control what gets resolved
7resolve({
8 extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json"],
9 // Only resolve modules that match these conditions
10 resolveOnly: ["lodash-es", "date-fns"],
11 // Prevent bundling certain dependencies
12 modulesOnly: true,
13 // Prefer ESM over CJS when both are available
14 preferBuiltins: true,
15});

@rollup/plugin-commonjs

Converts CommonJS modules to ESM so Rollup can process them. This is necessary because the majority of npm packages still use CommonJS, and Rollup only understands ESM natively.

rollup-plugin-commonjs.js
JavaScript
1import commonjs from "@rollup/plugin-commonjs";
2
3commonjs({
4 // Include specific packages
5 include: "node_modules/**",
6 // Try to detect named exports from CJS modules
7 namedExports: {
8 "react": ["useEffect", "useState", "useCallback"],
9 "react-dom": ["createPortal", "flushSync"],
10 },
11 // Require all requires to be in the module scope
12 strictRequires: true,
13 // Ignore dynamic requires (they break tree-shaking)
14 ignoreDynamicRequires: true,
15});

@rollup/plugin-typescript

Integrates the TypeScript compiler with Rollup, handling type checking and transpilation. For faster builds, consider using @rollup/plugin-esbuild instead, which uses esbuild for transpilation.

rollup-plugin-typescript.js
JavaScript
1import typescript from "@rollup/plugin-typescript";
2
3typescript({
4 tsconfig: "./tsconfig.json",
5 // Output declaration files separately
6 declaration: true,
7 declarationDir: "./dist/types",
8 // Exclude test files from build
9 exclude: ["**/*.test.ts", "**/*.spec.ts"],
10 // Use ES2022 target for library output
11 target: "ES2022",
12 // Preserve JSX for library consumers
13 jsx: "react-jsx",
14});
Output Formats

Rollup supports multiple output formats through the format option. Choosing the right format depends on your target audience and consumption method.

FormatUse CaseExample
esmModern bundlers, import/exportimport { foo } from "./lib"
cjsNode.js require(), legacy bundlersconst foo = require("./lib")
umdBrowser script tags, AMD, CJSwindow.MyLib = {...}
iifeSelf-executing browser bundle(function() {...})()
systemSystemJS loader compatibilitySystem.register(...)
rollup-multi-format.mjs
JavaScript
1// Multi-format build — library authors should provide ESM + CJS
2const config = [
3 // ESM — for modern bundlers (Vite, Webpack, Rollup itself)
4 {
5 input: "src/index.js",
6 output: {
7 file: "dist/my-lib.mjs",
8 format: "esm",
9 },
10 },
11 // CJS — for Node.js require()
12 {
13 input: "src/index.js",
14 output: {
15 file: "dist/my-lib.cjs",
16 format: "cjs",
17 },
18 },
19 // UMD — for direct browser usage
20 {
21 input: "src/index.js",
22 output: {
23 file: "dist/my-lib.umd.js",
24 format: "umd",
25 name: "MyLib", // global variable name
26 },
27 },
28];

info

Name your ESM output files with .mjs extension. This signals to Node.js and bundlers that the file uses ES module syntax, regardless of the nearest package.json ""type"'} field.
Code Splitting

Rollup supports code splitting via dynamic import(). When it encounters a dynamic import, Rollup creates a separate chunk that is loaded on demand. This is controlled by the output.dir option (required for code splitting) and output.entryFileNames.

rollup-code-splitting.mjs
JavaScript
1// rollup.config.mjs — code splitting config
2export default {
3 input: {
4 main: "src/main.js",
5 admin: "src/admin.js",
6 },
7 output: {
8 dir: "dist",
9 format: "esm",
10 // Chunk naming patterns
11 entryFileNames: "[name]-[hash].mjs",
12 chunkFileNames: "chunks/[name]-[hash].mjs",
13 // Preserve modules for tree-shaking at module level
14 preserveModules: false,
15 },
16 plugins: [resolve(), commonjs()],
17};
18
19// Dynamic import creates automatic split points
20// src/main.js
21const module = await import("./heavy-feature.js");
22// Rollup creates: dist/chunks/heavy-feature-abc123.mjs

warning

Code splitting requires output.dir instead of output.file. If you use output.file, Rollup will produce a single bundle and cannot create separate chunks.
Library Bundling Best Practices

Publishing a library with Rollup requires careful consideration of what to bundle, what to externalize, and how to structure your output. These best practices ensure your library works across bundlers and runtimes.

rollup-library.js
JavaScript
1// rollup.config.mjs — library-focused configuration
2import resolve from "@rollup/plugin-node-resolve";
3import commonjs from "@rollup/plugin-commonjs";
4import typescript from "@rollup/plugin-typescript";
5import terser from "@rollup/plugin-terser";
6
7export default {
8 input: "src/index.ts",
9 output: [
10 { dir: "dist", format: "esm", entryFileNames: "[name].mjs", sourcemap: true },
11 { dir: "dist", format: "cjs", entryFileNames: "[name].cjs", sourcemap: true },
12 ],
13 // Externalize peer dependencies — don't bundle React!
14 external: [
15 "react",
16 "react-dom",
17 /^@namespace\//,
18 ],
19 plugins: [
20 resolve({ extensions: [".ts", ".tsx", ".js"] }),
21 commonjs(),
22 typescript({ tsconfig: "./tsconfig.build.json" }),
23 // Minify only the UMD/IIFE builds, not ESM
24 terser({ compress: { drop_console: true } }),
25 ],
26};
27
28// package.json
29{
30 "type": "module",
31 "main": "./dist/index.cjs",
32 "module": "./dist/index.mjs",
33 "types": "./dist/index.d.ts",
34 "exports": {
35 ".": {
36 "import": "./dist/index.mjs",
37 "require": "./dist/index.cjs",
38 "types": "./dist/index.d.ts"
39 }
40 }
41}

info

Use tsconfig.build.json (separate from your main tsconfig.json) for library builds. Exclude test files, set ""declaration": true'}, and use ""declarationDir": "./dist/types"'}.
Rollup vs Webpack vs Vite

Choosing the right bundler depends on your specific use case. Here is a comparison to help you decide.

CriterionRollupWebpackVite
Primary useLibrariesApplicationsApplications
Dev serverNo (use Vite)Built-inBuilt-in (esbuild)
Production buildRollupWebpackRollup
Tree-shakingExcellentGoodExcellent
HMR speedN/AModerateInstant
Plugin count~1,000~8,000~500

info

If you are building an application, use Vite for development and let it use Rollup for production builds. If you are publishing a library to npm, use Rollup directly for maximum control over output format and tree-shaking.
Best Practices
  • Externalize peer dependencies — React, Vue, and other framework dependencies should never be bundled into your library. List them in external and mark them as peerDependencies in package.json.
  • Provide ESM + CJS — Ship both formats using separate output configs. ESM for modern bundlers, CJS for Node.js and legacy tooling.
  • Use source maps — Always enable sourcemap: true in development and library builds to aid debugging.
  • Optimize for tree-shaking — Write modules with explicit named exports to maximize Rollup's tree-shaking effectiveness. Avoid side effects in module scope.
  • Declare side effects — Add ""sideEffects": false'} to your package.json if your library has no side effects, enabling deeper tree-shaking.
  • Version your plugin dependencies — Pin major versions of Rollup plugins to avoid breaking changes. Rollup's plugin API evolves slowly but breaking changes do occur.
  • Use the .mjs extension for ESM output to make the module format unambiguous regardless of the project's ""type"'} field.
package.json
JSON
1// package.json — optimal library configuration
2{
3 "name": "my-awesome-lib",
4 "type": "module",
5 "sideEffects": false,
6 "main": "./dist/index.cjs",
7 "module": "./dist/index.mjs",
8 "types": "./dist/index.d.ts",
9 "exports": {
10 ".": {
11 "import": "./dist/index.mjs",
12 "require": "./dist/index.cjs",
13 "types": "./dist/index.d.ts"
14 },
15 "./styles.css": "./dist/styles.css"
16 },
17 "files": ["dist"]
18}
CLI Usage

Rollup provides a comprehensive CLI for building without a config file, though the configuration file approach is recommended for complex projects.

CLI
Bash
1# Install Rollup globally or as a dev dependency
2npm install --save-dev rollup
3
4# Basic build with config file
5rollup -c rollup.config.mjs
6
7# Build for production (minified)
8rollup -c --environment BUILD:production
9
10# Watch mode — rebuild on file changes
11rollup -c --watch
12
13# Build without config file
14rollup src/index.js --file dist/bundle.mjs --format esm
15
16# Build with multiple formats
17rollup src/index.js --file dist/bundle.cjs --format cjs
18
19# Generate source maps
20rollup -c --sourcemap
21
22# Tree-shake with verbose logging
23rollup -c --bundleConfigAsCjs --timing

warning

The --bundleConfigAsCjs flag may be needed if your config file uses CommonJS. Rollup v4 defaults to ESM config files. Use the .mjs extension for config files to avoid issues.
$Blueprint — Engineering Documentation·Section ID: BT-ROLLUP·Revision: 1.0