Skip to main content

Rollup vs Vite 2026: When You Need a Dedicated Bundler

·PkgPulse Team
0

TL;DR

Vite for apps, Rollup for libraries — and Vite uses Rollup internally anyway. Vite (~18M weekly downloads) is the default for web applications in 2026. Rollup (~15M downloads) remains the best choice for building JavaScript/TypeScript libraries that will be published to npm. The key distinction: Vite wraps Rollup with a development server, HMR, and framework plugins that you simply do not need when building a library destined for npm. For app development, reach for Vite. For library authoring with precise output control, reach for Rollup.

Key Takeaways

  • Vite uses Rollup for production builds — they share the same output quality
  • Rollup produces the cleanest library bundles — best tree-shaking in the ecosystem
  • Vite is better for applications — dev server, HMR, plugin ecosystem for frameworks
  • Vite also has a library mode (build.lib) — but bare Rollup gives more output control
  • Rollup plugin ecosystem is fully available in Vite (backward compatible)
  • tsup is the modern shortcut — wraps esbuild but produces Rollup-quality output for 90% of library use cases

The Relationship Between Rollup and Vite

Vite is not a competitor to Rollup. It is built on top of Rollup:

Vite architecture:
  ┌─────────────────────────────────────┐
  │ Vite                                │
  │  ├── Dev server (native ESM)        │
  │  ├── esbuild (transpile + dep prep) │
  │  └── Rollup (production builds) ←──┼── This IS Rollup
  └─────────────────────────────────────┘

When you run vite build, you are running Rollup with Vite's plugin transform layer on top. The output quality is identical to running Rollup directly. This means choosing between them is not about output quality — it is about what else you need around the build step.

For an application, you want Vite because you get hot module replacement during development, CSS handling, image optimization, and a plugin ecosystem tuned for React, Vue, Svelte, and other frameworks.

For a library, you want Rollup (or a Rollup-based tool) because you only care about the production artifact. There is no dev server, no browser, no HMR — just an input file and a set of output formats.


Why Use Rollup Directly for Library Authoring

When you publish a package to npm, your consumers will use Vite, webpack, or some other bundler to consume your package. What matters to them:

  1. An ESM output (dist/index.js) for tree-shaking
  2. A CJS output (dist/index.cjs) for Node.js compatibility
  3. A UMD output if you need CDN usage via <script> tags
  4. Type declarations (dist/index.d.ts)
  5. sideEffects: false in package.json for aggressive tree-shaking

Rollup handles all of these with full control:

// rollup.config.js — for a npm library
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { dts } from 'rollup-plugin-dts';

export default [
  // Build the JavaScript
  {
    input: 'src/index.ts',
    output: [
      {
        file: 'dist/index.cjs',
        format: 'cjs',          // CommonJS for Node.js
        exports: 'named',
      },
      {
        file: 'dist/index.js',
        format: 'esm',          // ES modules for modern bundlers
        exports: 'named',
        sourcemap: true,
      },
      {
        file: 'dist/index.umd.js',
        format: 'umd',          // UMD for CDN / <script> usage
        name: 'MyLibrary',
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    ],
    plugins: [
      resolve(),
      commonjs(),
      typescript({ tsconfig: './tsconfig.build.json' }),
    ],
    external: ['react', 'react-dom'], // Never bundle peer deps
  },
  // Build the type definitions
  {
    input: 'src/index.ts',
    output: { file: 'dist/index.d.ts', format: 'esm' },
    plugins: [dts()],
  },
];

This produces exactly:

  • dist/index.js — ES module for Vite/webpack tree-shaking
  • dist/index.cjs — CommonJS for older Node.js
  • dist/index.umd.js — UMD for CDN usage
  • dist/index.d.ts — TypeScript types

Pair this with a proper package.json exports field:

{
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  },
  "sideEffects": false
}

Vite's Library Mode

Vite does have a library mode via build.lib that handles many of the same cases:

// vite.config.ts — library mode
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';

export default defineConfig({
  plugins: [
    react(),
    dts({ insertTypesEntry: true }),
  ],
  build: {
    lib: {
      entry: 'src/index.ts',
      name: 'MyLibrary',
      formats: ['es', 'cjs', 'umd'],
      fileName: (format) => `index.${format}.js`,
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
  },
});

So when should you choose bare Rollup over Vite's library mode? The main scenarios:

  • Complex multi-entry libraries — Rollup handles multiple entry points and chunk splitting with more precision
  • Custom code transformations — Rollup's plugin API is more granular for AST-level transforms
  • Avoiding framework dependencies — Vite's library mode still pulls in Vite's own dependencies; bare Rollup keeps your build toolchain lean
  • Fine-grained external control — Rollup's external option accepts functions, RegExp patterns, and per-output-format configuration

Bundle Analysis

Both ecosystems have visualization tools to understand what is in your bundle:

# Rollup: rollup-plugin-visualizer
npm install --save-dev rollup-plugin-visualizer
// rollup.config.js
import { visualizer } from 'rollup-plugin-visualizer';

export default {
  plugins: [
    visualizer({
      open: true,        // Opens browser automatically
      filename: 'stats.html',
      gzipSize: true,
      brotliSize: true,
    }),
  ],
};
# Vite: rollup-plugin-visualizer works here too
npm install --save-dev rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    visualizer({
      open: true,
      filename: 'dist/stats.html',
    }),
  ],
});

Because Vite uses Rollup internally, the same rollup-plugin-visualizer works in both. There is also vite-bundle-visualizer (a thin wrapper) and rollup-plugin-bundle-stats for CI integration.


Tree-Shaking

Rollup pioneered tree-shaking in the JavaScript bundler ecosystem. Its tree-shaking is still considered the gold standard:

// Your library exports three things
export function add(a: number, b: number) { return a + b; }
export function multiply(a: number, b: number) { return a * b; }
export const VERSION = '1.0.0';

// Consumer imports only add
import { add } from 'your-library';

// Rollup output — multiply and VERSION are completely eliminated
// webpack output — depends on sideEffects field; can include unreferenced exports
// esbuild output — similar quality to Rollup for simple cases

For libraries where consumer bundle size matters, Rollup's tree-shaking is worth using directly or through tsup. The difference is most visible with larger libraries where eliminating unused exports measurably reduces the consumer's bundle.


tsup: The Modern Library Build Tool

For most library authors in 2026, tsup handles the 90% case without requiring a hand-crafted Rollup config:

// tsup.config.ts — replaces custom Rollup config for most libraries
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['cjs', 'esm'],       // Both output formats
  dts: true,                     // Generate .d.ts files
  splitting: false,
  sourcemap: true,
  clean: true,
  minify: false,                 // Libraries usually skip minification
  external: ['react'],           // Peer dependencies
  treeshake: true,
});

tsup uses esbuild internally (not Rollup), but produces comparable output. The tradeoff: faster build times, slightly less control. For complex multi-output libraries or those needing Rollup-specific plugins, bare Rollup is still the better tool.


Package Health

PackageWeekly DownloadsSize (gzip)Latest VersionActive
rollup~15M~1.5MB install4.xYes
vite~18M~4MB install6.xYes
tsup~5M~2MB install8.xYes
esbuild~30M~10MB install0.24.xYes

Both Rollup and Vite are actively maintained. Vite is backed by the Vue team and has the higher download count due to application usage. Rollup is the more foundational tool and sees steady growth as library authoring continues to grow. Neither is at risk of abandonment.


Configuration Complexity

TaskRollupVite lib modetsup
Single output formatMediumLowMinimal
ESM + CJS + UMD + typesHighMediumMinimal
Framework appNot idealLowNot needed
Multi-entry libraryHighMediumLow
Custom AST transformsHigh (plugins)Medium (plugins)Low

When to Choose Each

Use Vite when:

  • Building a web application (React, Vue, Svelte, vanilla)
  • You need a dev server with HMR during development
  • You want framework-specific plugins (React Fast Refresh, Vue SFC, Svelte HMR)
  • You are setting up a new project and want sensible defaults

Use Rollup directly when:

  • Building a library with unusual multi-format output requirements
  • You need precise control over what gets bundled and what stays external
  • You are writing custom build plugins that need deep Rollup API access
  • You need UMD output with specific global variable names for CDN usage

Use tsup when:

  • Building a TypeScript npm library (the modern default for most cases)
  • You want Rollup-quality tree-shaking without the configuration overhead
  • You are building a CLI tool or Node.js package

The relationship between Rollup and Vite is evolving. Vite uses Rollup for production builds today, but the Vite team has been exploring a Rust-based bundler (Rolldown) that would replace the Rollup production build path while maintaining Rollup plugin API compatibility. When Rolldown becomes Vite's default bundler, the performance gap for production builds will narrow significantly — Vite will gain faster production builds while Rollup retains its plugin ecosystem dominance and direct API access.

For library authors currently on Rollup directly, tsup has become the practical default for TypeScript packages: it wraps esbuild for the actual compilation, delegates tree-shaking to esbuild's Rollup-compatible algorithm, and generates CJS/ESM dual output with TypeScript declarations in one configuration file. The only reason to use Rollup directly over tsup is if you need Rollup-specific plugin behavior or unusual output formats that tsup doesn't expose in its configuration surface. For the large majority of TypeScript library authors, tsup provides the right tradeoff: Rollup-quality output with a fraction of the configuration effort required to set up Rollup from scratch, and a maintained preset that handles the CJS/ESM dual-output problem without manual entry point configuration.

The preserveModules option in Rollup is worth knowing for library authors who need to maintain file-per-file ESM output rather than a single bundled file. When set to true, Rollup outputs the same directory structure as the source, preserving individual module boundaries. This is the correct approach for component libraries (like shadcn/ui itself, if it were a traditional package) where consumers want to import individual components without dragging in the entire library: import { Button } from 'my-ui/button' instead of import { Button } from 'my-ui'. Vite's library mode does not expose preserveModules directly in its build.lib configuration — you must pass it through build.rollupOptions.output.preserveModules. This is another reason library authors sometimes prefer bare Rollup: the full output configuration API is directly accessible without searching for which Vite options proxy down to which Rollup options.

One configuration detail that trips up many library authors switching from Vite's library mode to bare Rollup is the external option. Vite's rollupOptions.external only applies to the Rollup production build path; some Vite plugins handle peer dependencies differently during development versus production, leading to subtle bundling differences between vite serve and vite build. Bare Rollup's external option is authoritative across the entire build — it accepts strings, RegExp, or a function (id) => boolean — giving you precise control over what ends up in the bundle. For libraries with complex peer dependency trees (a React component library that also optionally depends on react-query and zustand), Rollup's external function form lets you exclude all peer packages with (id) => !id.startsWith('.') while including only your vendored utilities. This level of control is the primary reason experienced library authors reach for bare Rollup over Vite's library mode when output composition matters.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.