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.

Rollup
1
Define the entry point

Start by specifying the input property. This is the main file of your library that contains all the logic you want to bundle. For a standard library, this is often src/index.js or src/main.js.

JavaScript
JavaScript
export default {
  input: 'src/index.js',
  // ... other config
};
Rollup
2
Configure the output format

Define the output property to specify where the bundle goes and in what format. For libraries, you typically want to generate both ES Module (.mjs) and CommonJS (.cjs) formats to support modern bundlers and Node.js environments.

JavaScript
JavaScript
export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/index.mjs',
      format: 'es',
      sourcemap: true
    },
    {
      file: 'dist/index.cjs',
      format: 'cjs',
      sourcemap: true
    }
  ]
};
Rollup
3
Add essential plugins

Raw JavaScript won't handle modern syntax like TypeScript, JSX, or JSON imports. Use the @rollup/plugin-node-resolve to find modules in node_modules, and @rollup/plugin-commonjs to convert CommonJS packages to ES modules.

JavaScript
JavaScript
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
  input: 'src/index.js',
  output: [ /* ... */ ],
  plugins: [
    resolve(),
    commonjs()
  ]
};
4
Exclude external dependencies

By default, Rollup bundles everything. For libraries, you usually want to mark dependencies listed in package.json as external. This keeps your bundle size small by letting the consumer's environment provide those modules.

JavaScript
JavaScript
export default {
  input: 'src/index.js',
  output: [ /* ... */ ],
  external: ['lodash', 'react'], // Add your deps here
  plugins: [ /* ... */ ]
};

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.

Shell
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.

JavaScript
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.

FeatureESM OutputCJS Output
Syntaximport / exportrequire() / module.exports
Tree ShakingSupportedNot supported
Async LoadingNative import()Callback-based require()
Default ExportNamed default propertyDirect 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.

JavaScript
// ✅ 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.

JavaScript
// 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.

JavaScript
// vite.config.js
export default {
  build: {
    lib: {
      entry: 'src/index.js',
      formats: ['es', 'cjs']
    }
  }
};

Comparison

FeatureRollupVite
Primary UseLibrary BundlingDev Server + Library Build
HMR SupportNoYes
Config ComplexityModerateLow
Output ControlHighStandard

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.

JavaScript
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.

Rollup 2026 build: what to check next