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@latestfor 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-formatsub-plugin. - Dynamic resources: Only the Vite plugin offers
defineDynamicResourcefor runtime asset registration. - Testing utilities: The Vite plugin provides
allFilesReadyandfileReadyhelpers 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:
packages/vite-plugin/src/node/index.ts— Factory function returning the plugin arraypackages/vite-plugin/src/node/plugin-manifest.ts— Virtual module generationpackages/vite-plugin/src/node/plugin-hmr.ts— Hot module replacement implementationpackages/vite-plugin/src/node/plugin-optionsProvider.ts— Config injection and API exposurepackages/vite-plugin/src/node/defineManifest.ts— Runtime manifest definition helper
Rollup Plugin Core Files:
packages/rollup-plugin/src/index.ts— Core plugin forwarding hooks to sub-pluginspackages/rollup-plugin/src/manifest-input/index.ts— MV2/MV3 parsing and validationpackages/rollup-plugin/src/validate-names/index.ts— Chrome naming convention enforcementpackages/rollup-plugin/src/mixed-format/index.ts— Dual manifest version supportpackages/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.tsand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →