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

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 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 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 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), enabling import.meta.env usage and on-the-fly resolution. The 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 to ensure unique, Chrome-compliant output names. The mixed-format sub-plugin in 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 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 that uses the factory function and runtime helpers:

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 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 with a static manifest path:

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, 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:

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:

Rollup Plugin Core Files:

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

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. You must declare all web_accessible_resources statically in your manifest.json file before building.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →