# Building a JavaScript Library for Distribution: Rollup vs Vite Library Mode

> Compare Rollup vs Vite library mode for efficient JavaScript distribution. Vite simplifies ESM/CJS/UMD outputs and asset handling, while Rollup needs manual plugins. Optimize your builds.

- Repository: [Vite/vite](https://github.com/vitejs/vite)
- Tags: comparison
- Published: 2026-02-16

---

**Vite library mode abstracts Rollup-compatible bundling through Rolldown to provide automatic ESM/CJS/UMD outputs, built-in CSS extraction, and optimized asset handling, whereas pure Rollup requires manual plugin configuration for each build step.**

When **building a JavaScript library for distribution** in the `vitejs/vite` ecosystem, developers face a choice between configuring a pure Rollup pipeline or leveraging Vite's opinionated library mode. Both approaches generate the module formats required for modern npm packages, but Vite adds a layer of performance optimizations and sensible defaults that significantly reduce configuration boilerplate. This analysis examines the architectural differences, build processes, and output characteristics to help you optimize for modern bundler practices.

## Core Architectural Differences

### Bundler Engine and Performance

**Rollup** (stand-alone) runs on Node.js with single-threaded execution or limited parallelism. **Vite library mode** uses **Rolldown**, a Rust-based bundler that maintains Rollup compatibility while leveraging multi-threaded scanning and cache-aware incremental builds. According to the source code in [`packages/vite/src/node/build.ts`](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts), Vite integrates Rolldown at the core of its build pipeline, resulting in faster cold starts and rebuilds compared to traditional Rollup configurations.

### Configuration Surface and Defaults

Pure Rollup requires an explicit [`rollup.config.js`](https://github.com/vitejs/vite/blob/main/rollup.config.js) where you manually define the `input` mapping, `output` array for each format, and plugin chain. In contrast, Vite’s [`vite.config.ts`](https://github.com/vitejs/vite/blob/main/vite.config.ts) exposes a **`build.lib`** option that merges sensible defaults automatically. When `build.lib` is enabled, Vite generates **ESM**, **CommonJS**, and **UMD** outputs without requiring separate output configurations, as implemented in the `LibraryOptions` interface within [`build.ts`](https://github.com/vitejs/vite/blob/main/build.ts).

### CSS and Asset Handling

Rollup requires external plugins like `rollup-plugin-postcss` to extract CSS into a distributable file. Vite handles CSS bundling natively: when `build.lib` is active, the pipeline automatically emits a single CSS file (`dist/<name>.css`) and allows customization via the **`build.lib.cssFileName`** option. For assets, Vite inlines files smaller than **4 KB** by default and hashes larger assets into the `assetsDir`, whereas Rollup requires manual `url` plugin configuration for similar behavior.

### Environment Variable Replacement

Vite statically replaces all **`import.meta.env.*`** references during production builds, leaving **`process.env.*`** dynamic for library consumers to define at runtime. This differs from Rollup, where developers must manually install and configure `@rollup/plugin-replace` to achieve equivalent environment variable substitution.

## How Vite Implements Library Mode

When the `build.lib` flag is set in [`packages/vite/src/node/build.ts`](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts), Vite’s internal build pipeline executes the following steps:

1. **Merges user `rolldownOptions` with internal defaults** — the `rolldownOptions` property (which replaces the historic `rollupOptions` alias) allows fine-tuning while preserving Vite’s conventions.
2. **Configures three output targets** — automatically generates `es`, `cjs`, and `umd` formats with sensible filenames (`.js`, `.cjs`, `.umd.cjs`).
3. **Bundles CSS imports** — collects all CSS dependencies into a single stylesheet using the `cssFileName` option specified in `LibraryOptions`.
4. **Generates package.json exports** — writes a minimal conditional exports map compatible with modern Node.js and bundler resolution.
5. **Strips HMR plugins** — removes module preload polyfills and hot-module replacement code that do not apply to library builds.

This implementation leverages **esbuild-based transforms**, **Rolldown’s parallel graph processing**, and Vite’s internal caching layer to optimize the build graph without additional setup.

## Practical Configuration Examples

### Vite Library Mode Setup

```typescript
// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.ts',
      name: 'MyLib',
      fileName: (format) => `my-lib.${format}.js`,
      cssFileName: 'my-lib.css',
    },
    rolldownOptions: {
      external: ['vue', 'lodash'],
    },
    minify: 'oxc',
    sourcemap: true,
    assetsInlineLimit: 4096,
  },
})

```

This configuration emits [`dist/my-lib.es.js`](https://github.com/vitejs/vite/blob/main/dist/my-lib.es.js), `dist/my-lib.cjs`, `dist/my-lib.umd.cjs`, and [`dist/my-lib.css`](https://github.com/vitejs/vite/blob/main/dist/my-lib.css) (if CSS is imported), with assets under 4 KB inlined automatically.

### Equivalent Rollup Configuration

```javascript
// rollup.config.js
import typescript from '@rollup/plugin-typescript'
import postcss from 'rollup-plugin-postcss'
import { terser } from 'rollup-plugin-terser'

export default {
  input: 'src/index.ts',
  external: ['vue', 'lodash'],
  plugins: [
    typescript(),
    postcss({ extract: true, minimize: true }),
    terser(),
  ],
  output: [
    { file: 'dist/my-lib.es.js', format: 'es', sourcemap: true },
    { file: 'dist/my-lib.cjs', format: 'cjs', sourcemap: true },
    { file: 'dist/my-lib.umd.js', format: 'umd', name: 'MyLib', sourcemap: true },
  ],
}

```

This manual approach requires explicit plugin installation for TypeScript, CSS extraction, and minification, with no built-in handling for `import.meta.env` or automatic asset hashing.

### Direct Rolldown Configuration

```typescript
// rolldown.config.ts
import { defineConfig } from 'rolldown'

export default defineConfig({
  input: 'src/index.ts',
  external: ['vue'],
  output: [
    { file: 'dist/my-lib.es.js', format: 'esm' },
    { file: 'dist/my-lib.cjs', format: 'cjs' },
    { file: 'dist/my-lib.umd.js', format: 'umd', name: 'MyLib' },
  ],
})

```

Using Rolldown directly mirrors Vite’s internal behavior but removes Vite-specific conventions like automatic CSS handling and environment replacement, suitable when you need maximum control over the bundler graph.

## When to Prefer Pure Rollup Over Vite

Despite Vite’s optimizations, a pure Rollup configuration remains preferable in specific scenarios:

- **Monorepo consistency**: When your organization already maintains complex Rollup configurations across multiple packages, introducing Vite adds an additional abstraction layer.
- **Plugin compatibility**: If you rely on Rollup plugins that have not been verified against Rolldown’s specific hook timing or behavior.
- **Custom hook logic**: When you need absolute control over every Rollup lifecycle hook, such as bespoke `output.assetFileNames` functions or custom code-splitting algorithms that override Vite’s defaults.

## Summary

- **Vite library mode** uses **Rolldown** (Rust-based, Rollup-compatible) for faster builds compared to Node.js-based Rollup.
- **Automatic output generation** produces ESM, CJS, and UMD formats without manual `output` array configuration.
- **Built-in CSS extraction** bundles stylesheets automatically via the `cssFileName` option, eliminating the need for `rollup-plugin-postcss`.
- **Asset optimization** inlines files under 4 KB and hashes larger assets by default.
- **Environment handling** statically replaces `import.meta.env.*` while preserving `process.env.*` for consumer-defined variables.
- **Configuration escape hatch** via `rolldownOptions` allows dropping down to raw Rollup-compatible settings when Vite’s opinions conflict with specific requirements.

## Frequently Asked Questions

### Is Vite library mode faster than pure Rollup for building libraries?

Yes. Vite leverages **Rolldown**, a Rust-based bundler compatible with the Rollup plugin API, which executes graph processing and module bundling in parallel. According to the implementation in [`packages/vite/src/node/build.ts`](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts) and the optimizer integration in [`rolldownDepPlugin.ts`](https://github.com/vitejs/vite/blob/main/rolldownDepPlugin.ts), this architecture provides significantly faster cold starts and incremental rebuilds compared to single-threaded Rollup executions.

### How does CSS handling differ between Vite and Rollup when bundling a library?

Vite automatically extracts all imported CSS into a single file (defaulting to `dist/<name>.css`) when `build.lib` is enabled, configurable via the **`cssFileName`** option in `LibraryOptions`. Rollup requires manual integration of `rollup-plugin-postcss` or similar plugins to achieve CSS extraction, with separate configuration needed for minification and output naming.

### Can I use existing Rollup plugins with Vite library mode?

Yes. Vite supports the vast majority of Rollup plugins through the **`rolldownOptions.plugins`** array (formerly `rollupOptions.plugins`). However, plugins that rely on specific Node.js-only APIs or untested hook behaviors may require verification against Rolldown’s implementation, as noted in the Vite source code configuration handling.

### When should I choose pure Rollup over Vite for library distribution?

Choose pure Rollup when you require **bundler independence** from the Vite toolchain (such as in monorepos already standardized on Rollup), need **unverified Rollup plugins** that may conflict with Rolldown, or demand **absolute control** over every bundler hook and output option. For most modern libraries, Vite’s library mode provides equivalent output guarantees with less configuration overhead.