Why rollup fits library builds
Rollup is built for a different job than Vite or Webpack. While those tools are designed to bundle complex applications with dev servers, hot module replacement, and heavy plugin ecosystems, Rollup focuses on producing clean, optimized output for libraries and small utilities. It treats your code as a static distribution artifact rather than a live development environment.
The core difference lies in the output format. Rollup excels at generating ES modules, CommonJS, UMD, and IIFE bundles that are tree-shakeable by downstream consumers. When you publish a package to npm, you want the smallest possible footprint with unused code removed. Rollup’s static analysis engine is specifically tuned for this, often producing smaller and faster-loading bundles than general-purpose bundlers.
Consider the configuration simplicity. A typical Rollup setup for a library requires minimal boilerplate. You define entry points, output formats, and external dependencies. There is no need to configure a dev server, asset pipeline for images, or complex code-splitting strategies unless your library specifically demands it. This lean approach reduces maintenance overhead and keeps the build process predictable.
For example, a simple utility library might only need a single entry file and a config that outputs both ES and CommonJS formats. Rollup handles the deduplication and module resolution efficiently, ensuring that consumers of your library only import what they use. This makes it the preferred choice for frameworks, UI component libraries, and backend utilities where bundle size and load time are critical.
Set up a rollup config file
To begin bundling your library, create a rollup.config.js file in your project root. This file exports a configuration object that tells Rollup where to find your entry point, how to structure the output, and which plugins to use during the build process.
Once your config is structured, you can run npx rollup -c to verify the build. This setup provides a solid foundation for building and distributing your JavaScript library.
Add plugins for typescript
Integrating TypeScript into a Rollup project requires two distinct steps: installing the necessary dependencies and configuring the Rollup pipeline to handle the compilation. Because Rollup is a bundler and not a compiler, it relies on plugins to transform TypeScript source code into JavaScript before bundling.
1. Install Dependencies
First, install @rollup/plugin-typescript and typescript as development dependencies. This ensures your build environment has the compiler and the Rollup integration layer.
npm install -D typescript @rollup/plugin-typescript
2. Configure Rollup
Import the plugin in your rollup.config.js and add it to the plugins array. The plugin accepts options that map directly to the tsconfig.json settings, allowing you to control strictness, target environments, and declaration file generation.
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/index.ts',
output: {
file: 'dist/bundle.js',
format: 'esm',
},
plugins: [
typescript({
tsconfig: './tsconfig.json',
declaration: true, // Generates .d.ts files
declarationDir: './dist/types',
}),
],
};
With this configuration, Rollup will read your tsconfig.json, compile the TypeScript files, and output clean JavaScript along with type definitions if enabled.
Bundle for esm and cjs
Modern JavaScript libraries need to work everywhere. Some environments prefer ES modules, while others still rely on CommonJS. Rollup handles this by generating separate output files for each format.
The standard approach is to use the --bundleConfigAsCjs flag when running Rollup from the command line, or to set export default in your config file. This ensures the bundler reads your configuration correctly regardless of your project's module type.
To output multiple formats, define an array in your output configuration. Rollup will process your source code once and emit distinct files for each target format. This keeps your distribution clean and compatible with both Node.js and modern browsers.
| Feature | ESM Output | CJS Output |
|---|---|---|
| Syntax | import / export | require() / module.exports |
| Tree Shaking | Supported | Not supported |
| Async Loading | Native import() | Callback-based require() |
| Default Export | Named default property | Direct export value |
The table above highlights the technical differences. ESM allows for static analysis and better tree shaking. CJS remains the standard for legacy Node.js environments and older tooling. By providing both, you ensure your library works in the widest range of projects.
Configure your rollup.config.js to specify the file names for each format. Use the name option for UMD bundles if you need global variable support, but stick to ESM and CJS for most library distributions. This dual-output strategy is the industry standard for maximum compatibility.
Tree shake unused code
Rollup eliminates dead code by default, making it the standard for library development. Unlike bundlers that require complex configuration to remove unused exports, Rollup analyzes your static import graph and prunes anything that isn't reachable. This results in smaller bundles without manual intervention.
However, tree shaking relies on strict ES module syntax. If your code uses CommonJS (module.exports) or modifies the global scope, Rollup cannot safely determine what is unused. You must ensure your source code is written in pure ES modules.
// ✅ Tree shaking works
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// ❌ Tree shaking fails
module.exports = { add, subtract };
To guarantee effective pruning, configure your build tool to preserve ES module structure. In Rollup, this means setting output.format to es or cjs while keeping the input as ES modules. Avoid bundling your library into a single IIFE, as this often prevents external consumers from tree shaking your exports.
Rollup vs Vite for libraries
Choosing between Rollup and Vite for library development depends on your output requirements. Rollup is the dedicated bundler for static libraries, while Vite is an interactive development server that uses Rollup under the hood for production builds.
When to use Rollup
Use Rollup when you need fine-grained control over the final bundle. It excels at tree-shaking and producing multiple output formats (ESM, CJS, UMD) from a single configuration. It is the standard for publishing npm packages where the consumer handles the build process.
// rollup.config.js
export default {
input: 'src/index.js',
output: [
{ file: 'dist/bundle.esm.js', format: 'esm' },
{ file: 'dist/bundle.cjs.js', format: 'cjs' }
]
};
When to use Vite
Use Vite when you are building a library that requires a fast, interactive development experience. Vite’s HMR (Hot Module Replacement) allows you to test changes instantly without full rebuilds. It uses Rollup for the final production build, ensuring compatibility, but adds a dev server layer.
// vite.config.js
export default {
build: {
lib: {
entry: 'src/index.js',
formats: ['es', 'cjs']
}
}
};
Comparison
| Feature | Rollup | Vite |
|---|---|---|
| Primary Use | Library Bundling | Dev Server + Library Build |
| HMR Support | No | Yes |
| Config Complexity | Moderate | Low |
| Output Control | High | Standard |
Quick checklist
-
Need HMR for rapid iteration? Choose Vite.
-
Need custom output formats (UMD/iife)? Choose Rollup.
-
Building a simple npm package? Choose Rollup.
-
Building a React/Vue component library? Choose Vite.
Common rollup build errors
Even with a solid configuration, Rollup can throw errors that halt the build. These usually stem from how the bundler handles external dependencies or dynamic code splitting. Understanding these patterns helps you fix builds faster.
External dependencies
Rollup treats everything as internal by default. If your library imports a package like lodash or react but doesn't list it as external, Rollup bundles that code into your output file. This bloats the bundle and causes duplicate dependency issues for consumers.
Fix this by adding the package to the external array in your config. This tells Rollup to leave the import statement alone, expecting the consumer to provide the dependency at runtime.
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'esm'
},
external: ['lodash', 'react']
};
Dynamic imports
Dynamic imports (import()) are essential for code splitting, but they can fail if Rollup doesn't know how to handle the target. If you dynamically import a file that isn't matched by your input or preserveModules settings, Rollup may not generate the correct chunk.
Ensure your dynamic paths are resolvable. If you are using preserveModules, make sure the directory structure in dist matches your source. Misaligned paths often result in Error: Could not resolve entry module during the build phase.


No comments yet. Be the first to share your thoughts!