Rollup vs Vite 2026: When You Need a Dedicated Bundler
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:
- An ESM output (
dist/index.js) for tree-shaking - A CJS output (
dist/index.cjs) for Node.js compatibility - A UMD output if you need CDN usage via
<script>tags - Type declarations (
dist/index.d.ts) sideEffects: falseinpackage.jsonfor 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-shakingdist/index.cjs— CommonJS for older Node.jsdist/index.umd.js— UMD for CDN usagedist/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
externalcontrol — Rollup'sexternaloption 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
| Package | Weekly Downloads | Size (gzip) | Latest Version | Active |
|---|---|---|---|---|
| rollup | ~15M | ~1.5MB install | 4.x | Yes |
| vite | ~18M | ~4MB install | 6.x | Yes |
| tsup | ~5M | ~2MB install | 8.x | Yes |
| esbuild | ~30M | ~10MB install | 0.24.x | Yes |
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
| Task | Rollup | Vite lib mode | tsup |
|---|---|---|---|
| Single output format | Medium | Low | Minimal |
| ESM + CJS + UMD + types | High | Medium | Minimal |
| Framework app | Not ideal | Low | Not needed |
| Multi-entry library | High | Medium | Low |
| Custom AST transforms | High (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
Related Resources
- Compare Rollup and Vite package health: /compare/rollup-vs-vite
- How Rspack compares to the webpack/Rollup model: /blog/rspack-vs-webpack-2026
- Vite package details and download trends: /packages/vite
See the live comparison
View rollup vs. vite on PkgPulse →