# CRXJS Vite Plugin vs Rollup Plugin: Key Architectural and Functional Differences

> Explore CRXJS Vite plugin vs Rollup plugin differences. vite-plugin offers HMR & virtual modules for development, while rollup-plugin is build-time focused for manifest generation.

- Repository: [crxjs/chrome-extension-tools](https://github.com/crxjs/chrome-extension-tools)
- Tags: deep-dive
- Published: 2026-02-28

---

**The CRXJS Vite plugin delivers a development-focused experience with HMR and virtual modules through multiple small plugins, while the Rollup plugin provides a single, build-time focused tool for manifest generation and validation without runtime helpers.**

The `crxjs/chrome-extension-tools` repository maintains two distinct packages for building Chrome extensions. Understanding the **vite-plugin vs rollup-plugin CRXJS** architectural differences ensures you select the appropriate toolchain for your development workflow. While both produce valid Chrome extension manifests and compiled assets, they differ fundamentally in composition models, lifecycle hooks, and runtime capabilities.

## Architectural Differences

### Plugin Composition Model

The **Vite plugin** exposes a factory function (`crx`) in [`packages/vite-plugin/src/node/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/index.ts) that returns an **array of small, focused Vite plugins**—each handling specific concerns like options provision, background scripts, content scripts, file writing, HMR, and manifest generation. This fine-grained approach allows individual plugins to be disabled or swapped as needed, utilizing Vite’s native Plugin API (`config`, `buildStart`, `transform`, `handleHotUpdate`) alongside virtual modules.

In contrast, the **Rollup plugin** implements a **single monolithic plugin** in [`packages/rollup-plugin/src/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/index.ts) that internally instantiates sub-plugins (`manifestInput`, `htmlInputs`, `validateNames`, `browserPolyfill`, `mixedFormat`) and forwards Rollup hooks to them. This coarse-grained architecture keeps internal sub-plugins hidden from users, relying on Rollup’s Hook API (`options`, `buildStart`, `resolveId`, `load`, `generateBundle`) without exposing granular control.

### Development-Time Features

The Vite plugin provides **Hot Module Replacement (HMR)** through [`packages/vite-plugin/src/node/plugin-hmr.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-hmr.ts) and a client-side loader that communicates with background and content scripts during development. It creates **virtual modules** for manifest, background, and content-script entry points (defined in [`virtualFileIds.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/virtualFileIds.ts)), enabling `import.meta.env` usage and on-the-fly resolution. The [`plugin-optionsProvider.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/plugin-optionsProvider.ts) injects CRX options into Vite’s config for zero-config project scaffolding.

The Rollup plugin focuses strictly on build-time operations without HMR or virtual modules. It specializes in **manifest validation** via [`packages/rollup-plugin/src/validate-names/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/validate-names/index.ts) to ensure unique, Chrome-compliant output names. The `mixed-format` sub-plugin in [`packages/rollup-plugin/src/mixed-format/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/mixed-format/index.ts) handles both Manifest V2 and V3 output paths, while `browser-polyfill` generates namespace polyfills for environments lacking the `chrome` object.

### Build-Time vs Runtime Responsibilities

| Responsibility | Vite Plugin | Rollup Plugin |
|----------------|-------------|---------------|
| **Manifest generation** | [`plugin-manifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/plugin-manifest.ts) emits virtual modules; file-writer persists to disk | `manifest-input` and `mixed-format` emit final JSON during `generateBundle` |
| **Asset handling** | `plugin-fileWriter*` manages scripts, CSS, and static assets during dev and build | `html-inputs` parses HTML files to discover and bundle script tags |
| **Runtime API** | Exports `defineManifest`, `defineDynamicResource`, `allFilesReady`, and `fileReady` for extension code | No runtime exports; build-time only |
| **Module resolution** | Uses Vite’s ES module resolver with esbuild | Uses Rollup’s resolver with standard plugins |

## Functional Differences for Developers

When comparing **CRXJS vite-plugin vs rollup-plugin** capabilities, consider these workflow impacts:

- **Development server with HMR**: The Vite plugin provides full hot reloading for content scripts, background pages, and popups. The Rollup plugin supports only static builds.
- **Zero-config scaffolding**: The Vite plugin works with `npm create crxjs@latest` for immediate project setup, while the Rollup plugin requires manual configuration.
- **Manifest version support**: The Vite plugin targets Manifest V3 exclusively by design. The Rollup plugin supports both MV2 and MV3 through the `mixed-format` sub-plugin.
- **Dynamic resources**: Only the Vite plugin offers `defineDynamicResource` for runtime asset registration.
- **Testing utilities**: The Vite plugin provides `allFilesReady` and `fileReady` helpers to await build completion in test suites. The Rollup plugin offers no equivalent testing hooks.

## Implementation Examples

### Configuring the Vite Plugin

Create a [`vite.config.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/vite.config.ts) that uses the factory function and runtime helpers:

```typescript
import { defineConfig } from 'vite'
import { chromeExtension, defineManifest } from '@crxjs/vite-plugin'

export const manifest = defineManifest({
  name: 'My Extension',
  version: '1.0.0',
  manifest_version: 3,
  action: { default_popup: 'popup.html' },
  background: { service_worker: 'src/background.ts' },
  content_scripts: [
    {
      matches: ['<all_urls>'],
      js: ['src/content.ts'],
    },
  ],
})

export default defineConfig({
  plugins: [chromeExtension({ manifest })],
})

```

The `chromeExtension` export from [`packages/vite-plugin/src/node/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/index.ts) creates virtual modules for the manifest and bundles background and content scripts with HMR enabled.

### Configuring the Rollup Plugin

For classic Rollup setups, configure [`rollup.config.js`](https://github.com/crxjs/chrome-extension-tools/blob/main/rollup.config.js) with a static manifest path:

```javascript
import { chromeExtension } from 'rollup-plugin-chrome-extension'

export default {
  input: 'src/background.ts',
  output: {
    dir: 'dist',
    format: 'esm',
  },
  plugins: [
    chromeExtension({
      manifest: './manifest.json',
    }),
  ],
}

```

During the `generateBundle` hook in [`packages/rollup-plugin/src/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/index.ts), the plugin validates the manifest, injects `web_accessible_resources` entries, and writes output to the `dist` directory.

### Using Runtime Helpers (Vite Only)

Dynamically register assets at runtime using Vite-specific client code:

```typescript
import { defineDynamicResource } from '@crxjs/vite-plugin/client'

// Inside a content script
defineDynamicResource('myImage', new URL('assets/logo.png', import.meta.url))

```

This inserts the resource into the manifest at runtime. No equivalent functionality exists in the Rollup plugin.

## Key Source Files and Implementation Details

Understanding the internal structure helps debug build issues:

**Vite Plugin Core Files:**
- [`packages/vite-plugin/src/node/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/index.ts) — Factory function returning the plugin array
- [`packages/vite-plugin/src/node/plugin-manifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-manifest.ts) — Virtual module generation
- [`packages/vite-plugin/src/node/plugin-hmr.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-hmr.ts) — Hot module replacement implementation
- [`packages/vite-plugin/src/node/plugin-optionsProvider.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-optionsProvider.ts) — Config injection and API exposure
- [`packages/vite-plugin/src/node/defineManifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/defineManifest.ts) — Runtime manifest definition helper

**Rollup Plugin Core Files:**
- [`packages/rollup-plugin/src/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/index.ts) — Core plugin forwarding hooks to sub-plugins
- [`packages/rollup-plugin/src/manifest-input/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/manifest-input/index.ts) — MV2/MV3 parsing and validation
- [`packages/rollup-plugin/src/validate-names/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/validate-names/index.ts) — Chrome naming convention enforcement
- [`packages/rollup-plugin/src/mixed-format/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/mixed-format/index.ts) — Dual manifest version support
- [`packages/rollup-plugin/src/browser-polyfill/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/browser-polyfill/index.ts) — Chrome namespace polyfill generation

## Summary

- The **Vite plugin** uses a factory pattern returning multiple small plugins with granular control, targeting Vite 3–8 with Manifest V3 only.
- The **Rollup plugin** operates as a single plugin with internal sub-plugins, supporting both Manifest V2 and V3 but lacking development server features.
- **HMR, virtual modules, and runtime helpers** (`defineManifest`, `defineDynamicResource`) are exclusive to the Vite architecture.
- **Build-time validation and polyfills** are core strengths of the Rollup implementation in [`packages/rollup-plugin/src/validate-names/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/validate-names/index.ts) and related modules.
- Choose the Vite plugin for interactive development workflows; select the Rollup plugin for minimal build pipelines or legacy MV2 compatibility.

## Frequently Asked Questions

### Can I use the CRXJS Rollup plugin inside a Vite project?

While possible in limited compatibility mode, the Rollup plugin is not designed for Vite’s dev server and lacks HMR support. According to the source warning in [`packages/rollup-plugin/src/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/index.ts), Vite compatibility is limited to specific versions and use cases. For full Vite integration, use `@crxjs/vite-plugin` instead.

### Does the Vite plugin support Manifest V2?

No. The Vite plugin deliberately supports only Manifest V3 as the modern Chrome extension standard. If you require Manifest V2 support for legacy browsers, you must use the Rollup plugin, which handles both versions through the `mixed-format` sub-plugin in [`packages/rollup-plugin/src/mixed-format/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/mixed-format/index.ts).

### Which plugin provides better performance for production builds?

Both plugins produce optimized production bundles, but the Rollup plugin has a smaller footprint since it excludes HMR client code and development utilities. The Vite plugin’s production build ultimately uses Rollup internally (Vite’s build mode), but includes additional runtime helpers that may marginally increase bundle size if used.

### How do I dynamically add web accessible resources in the Rollup plugin?

The Rollup plugin does not support dynamic resource registration at runtime. Unlike the Vite plugin’s `defineDynamicResource` helper, the Rollup plugin processes the manifest only during the build phase in [`packages/rollup-plugin/src/manifest-input/index.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/rollup-plugin/src/manifest-input/index.ts). You must declare all `web_accessible_resources` statically in your manifest.json file before building.