Rollup Configuration for Chrome Extensions: Complete Guide
Building Chrome extensions in 2025 requires modern tooling to handle the complexity of Manifest V3 extensions. Rollup has emerged as one of the most powerful and efficient bundlers for Chrome extensions, offering tree-shaking, code splitting, and a plugin ecosystem that makes managing extension builds straightforward. This comprehensive guide walks you through setting up Rollup for your Chrome extension projects, from basic configuration to advanced optimizations that will make your extension faster and smaller.
Whether you are migrating from Webpack or starting fresh, this guide covers everything you need to know about configuring Rollup specifically for Chrome extension development with Manifest V3.
Why Use Rollup for Chrome Extensions?
Chrome extensions have unique requirements that make Rollup an excellent choice for bundling. Unlike traditional web applications, extensions must manage multiple entry points including background service workers, content scripts, popup pages, options pages, and side panels. Rollup’s modular architecture handles these complex build scenarios elegantly.
The Case for Rollup in Extension Development
Rollup offers several compelling advantages for Chrome extension development. First, its native ES module support means you can write modern JavaScript without worrying about compatibility issues. Second, Rollup’s tree-shaking capability removes unused code, resulting in smaller bundle sizes which directly impact extension performance and loading times. Third, the plugin ecosystem provides solutions for every aspect of extension bundling, from handling multiple entry points to processing different file types.
Many developers initially choose Webpack for their extension projects, but often find the configuration complexity overwhelming. Rollup’s simpler configuration model makes it easier to understand and maintain, while still providing all the features needed for production-ready extensions. The transition from Webpack to Rollup typically results in faster build times and smaller output files.
Understanding the Chrome Extension Build Requirements
Chrome extensions built with Manifest V3 have specific build requirements that influence your Rollup configuration. Each extension component runs in a different context: background service workers run in a special extension context, content scripts run in the context of web pages, and popup or options pages run as standard web pages within the extension. Your bundler must handle these contexts correctly while ensuring all dependencies are properly bundled.
The background service worker in Manifest V3 presents unique challenges. Unlike the old background pages, service workers have a different lifecycle and cannot access the DOM. Your Rollup configuration must ensure that code intended for the service worker is properly isolated from code that requires DOM access. Additionally, service workers must be single files in Manifest V3, meaning all dependencies must be bundled into a single JavaScript file.
Setting Up Rollup for Your Chrome Extension
Getting started with Rollup for Chrome extensions requires installing the necessary packages and creating a configuration file. This section walks you through the complete setup process.
Installing Required Dependencies
Begin by installing Rollup and the plugins you will need for your extension build. The core packages include Rollup itself, the Node resolve plugin for finding modules, and the CommonJS plugin for handling CommonJS dependencies. You will also need plugins for processing different file types and minifying the output.
npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-terser @rollup/plugin-json @rollup/plugin-replace
The Node resolve plugin allows Rollup to find modules in your node_modules folder and resolve import statements. The CommonJS plugin converts CommonJS modules (the format used by most npm packages) to ES modules that Rollup can bundle. The Terser plugin minifies the JavaScript output, reducing file size significantly. The JSON plugin enables importing JSON files directly, and the Replace plugin allows environment-specific variable substitution.
For Chrome extensions, you will also want to consider additional plugins for specific tasks. The Babel plugin can transpile modern JavaScript for broader browser compatibility if needed. The TypeScript plugin enables TypeScript support. The copy plugin helps with copying static assets like icons and HTML files to the build output.
Creating Your Rollup Configuration File
Create a rollup.config.js file in your project root to configure Rollup for your Chrome extension. The configuration defines input entry points, output formats, and plugins to use during the build process.
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import json from '@rollup/plugin-json';
import replace from '@rollup/plugin-replace';
const production = !process.env.ROLLUP_WATCH;
export default {
input: {
background: 'src/background/background.js',
popup: 'src/popup/popup.js',
options: 'src/options/options.js',
content: 'src/content/content.js'
},
output: {
dir: 'dist',
format: 'es',
sourcemap: !production,
entryFileNames: 'js/[name].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]'
},
plugins: [
resolve({
browser: true,
preferBuiltins: false
}),
commonjs(),
json(),
replace({
preventAssignment: true,
'process.env.NODE_ENV': JSON.stringify(production ? 'production' : 'development')
}),
production && terser()
]
};
This configuration defines multiple entry points for different extension components. The output is set to ES modules format, which works well for modern browsers and extension environments. The file naming scheme includes hashes for cache busting in production builds, while keeping development builds more readable.
Configuring Rollup for Manifest V3 Specifics
Manifest V3 introduced several changes that require specific consideration in your Rollup configuration. Understanding these requirements ensures your extension builds correctly and passes Chrome Web Store validation.
Handling Service Workers
The background service worker in Manifest V3 must be a single JavaScript file. This means your Rollup configuration must bundle all dependencies into one file for the background entry point. The configuration shown above achieves this by specifying background.js as a separate entry point with all dependencies inlined.
Service workers cannot access the DOM, so any code that requires document or window objects must be handled carefully. One common pattern is to create separate files for service worker logic and UI logic, then ensure the background entry only includes the appropriate code. You can use the @rollup/plugin-alias plugin to create aliases that point to the correct implementations for each context.
import alias from '@rollup/plugin-alias';
export default {
// ... other configuration
plugins: [
alias({
entries: [
{ find: '@background', replacement: './src/background/background-core.js' },
{ find: '@shared', replacement: './src/shared/shared-utils.js' }
]
}),
// ... other plugins
]
};
Managing Content Scripts
Content scripts run in the context of web pages, which creates unique challenges for bundling. Unlike service workers or popup pages, content scripts share the page’s global scope, meaning your bundled code must not pollute the global namespace unintentionally. Rollup’s module format options help control how code is wrapped and exposed.
For content scripts, you typically want to bundle all dependencies into a single file to avoid loading issues. The configuration should ensure that any third-party libraries are properly bundled and do not create global variables that might conflict with the host page.
// Separate configuration for content scripts
export default {
input: 'src/content/content.js',
output: {
file: 'dist/js/content.js',
format: 'iife', // Immediately Invoked Function Expression
name: 'contentScript',
sourcemap: !production
},
plugins: [
resolve({
browser: true,
dedupe: ['react', 'react-dom']
}),
commonjs(),
production && terser()
]
};
The IIFE format wraps the bundled code in an immediately invoked function, preventing variables from leaking into the global scope while ensuring the code executes immediately when loaded as a content script.
Optimizing Your Extension Build
Optimization is crucial for Chrome extensions, where load time directly impacts user experience and Chrome’s extension performance metrics. This section covers techniques to make your extension faster and smaller.
Tree Shaking and Code Splitting
Rollup’s tree-shaking capability automatically removes unused code from your final bundles. To maximize tree-shaking effectiveness, write your code using ES modules with named exports rather than default exports. Named exports allow Rollup to analyze which parts of a module are actually used.
// Good: Named exports enable effective tree-shaking
export function calculateTabMemory(tabs) {
return tabs.reduce((total, tab) => total + (tab.incognito ? 0 : tab.memory), 0);
}
export function suspendTab(tabId) {
return chrome.tabs.discard(tabId);
}
// Bad: Default exports prevent tree-shaking
export default {
calculateTabMemory,
suspendTab
};
Code splitting divides your code into multiple chunks that can be loaded on demand. For Chrome extensions, this is particularly useful for large libraries that are only needed in specific contexts. For example, a charting library might only be needed in your options page, so it should not be bundled with the background script.
// Dynamic imports enable code splitting
async function loadCharts() {
const { ChartManager } = await import('./chart-manager.js');
return new ChartManager();
}
// Only load when needed
if (isOptionsPage) {
loadCharts().then(charts => charts.render());
}
Minification and Compression
The Terser plugin provides extensive minification options that can significantly reduce your bundle size. Beyond basic minification, Terser can perform advanced optimizations like shortening variable names, removing whitespace and comments, and inlining functions.
import terser from '@rollup/plugin-terser';
const productionConfig = terser({
compress: {
passes: 2,
pure_getters: true,
unsafe: true,
unsafe_comps: true,
unsafe_math: true,
unsafe_methods: true
},
mangle: {
properties: {
regex: /^_/
}
},
format: {
comments: false
}
});
These options provide aggressive minification, but some options like unsafe transformations may cause issues with certain code patterns. Always test your extension thoroughly after enabling aggressive minification.
Handling External Dependencies
Some dependencies should not be bundled but instead marked as external. Chrome’s built-in APIs, accessed through the chrome global object, should always be marked external so Rollup does not try to bundle them.
export default {
// ... other configuration
external: ['chrome', 'chrome-runtime'],
plugins: [
resolve({
browser: true
}),
commonjs(),
// ... other plugins
]
};
For dependencies that are available globally in Chrome extensions or that you want to load from a CDN, mark them as external to exclude them from the bundle. This reduces bundle size but requires the dependency to be available at runtime.
Handling Static Assets and HTML Files
Chrome extensions require various static assets including HTML files, images, icons, and CSS files. While Rollup primarily processes JavaScript, you can configure it to handle these assets or use additional plugins.
Using the Copy Plugin
The @rollup/plugin-copy plugin allows you to copy static files to your distribution directory during the build process. This is essential for including HTML files, icons, and other non-JavaScript assets.
import copy from '@rollup/plugin-copy';
export default {
// ... other configuration
plugins: [
copy({
targets: [
{ src: 'src/popup/popup.html', dest: 'dist' },
{ src: 'src/options/options.html', dest: 'dist' },
{ src: 'src/icons/*', dest: 'dist/icons' },
{ src: 'src/styles/*', dest: 'dist/styles' },
{ src: 'manifest.json', dest: 'dist' }
],
hook: 'writeBundle'
})
]
};
Processing CSS with Rollup
For CSS processing, you have several options. The simplest approach is to copy CSS files using the copy plugin. For more advanced CSS processing including PostCSS support, use the postcss plugin.
import postcss from '@rollup/plugin-postcss';
export default {
// ... other configuration
plugins: [
postcss({
extract: true,
minimize: production,
sourceMap: !production
}),
// ... other plugins
]
};
The extract option writes CSS to a separate file, while minimize enables CSS minification in production builds. This approach works well for popup and options pages that need styled interfaces.
Advanced Rollup Configurations
As your extension grows in complexity, you may need more sophisticated Rollup configurations. This section covers advanced patterns for handling complex extension architectures.
Multi-Page Extension Build
Large extensions often have multiple pages beyond just popup and options. Side panels, new tab pages, and devtools pages each require their own build configuration. You can create separate Rollup configurations for each page type or use a single configuration with multiple entry points.
// rollup.config.js
import { defineConfig } from 'rollup';
const baseConfig = defineConfig({
plugins: [
// Shared plugins
]
});
const pageConfig = defineConfig({
input: {
'popup/popup': 'src/popup/popup.js',
'options/options': 'src/options/options.js',
'sidepanel/sidepanel': 'src/sidepanel/sidepanel.js',
'newtab/newtab': 'src/newtab/newtab.js'
},
output: {
dir: 'dist',
format: 'es',
entryFileNames: 'js/[name].js',
chunkFileNames: 'js/[name]-[hash].js'
},
// Page-specific plugins
});
export default [baseConfig, pageConfig];
TypeScript Support
Adding TypeScript support to your Rollup configuration enables type checking and improved developer experience. The @rollup/plugin-typescript plugin handles TypeScript compilation and integrates with your tsconfig.json.
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/background/background.ts',
output: {
file: 'dist/js/background.js',
format: 'es'
},
plugins: [
typescript({
tsconfig: './tsconfig.json',
sourceMap: !production,
inlineSources: !production
})
]
};
TypeScript provides compile-time type checking that catches errors before runtime, making your extension more reliable. The integration with Rollup ensures TypeScript is compiled during the build process without requiring a separate build step.
Environment-Specific Builds
Different build environments require different configurations. Development builds should include source maps for debugging, while production builds should be minified and optimized. Environment variables help control these differences.
const production = process.env.NODE_ENV === 'production';
export default {
input: 'src/background/background.js',
output: {
file: 'dist/js/background.js',
format: 'es',
sourcemap: !production
},
plugins: [
replace({
preventAssignment: true,
'process.env.DEBUG': JSON.stringify(!production)
}),
production && terser()
]
};
You can then use these environment variables in your code to enable or disable debug features:
if (process.env.DEBUG) {
console.log('Debug mode enabled');
}
Common Issues and Solutions
Even with careful configuration, you may encounter issues when building Chrome extensions with Rollup. This section addresses common problems and their solutions.
Module Resolution Issues
One of the most common issues is module resolution failures, where Rollup cannot find imported modules. This often happens with packages that have complex export configurations or that use non-standard file extensions.
import resolve from '@rollup/plugin-node-resolve';
export default {
plugins: [
resolve({
browser: true,
preferBuiltins: false,
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json']
})
]
};
Adding all possible extensions to the resolve plugin often resolves these issues. If problems persist, you may need to explicitly configure specific packages as external or use the alias plugin to point to correct implementations.
Chrome API Compatibility
The chrome global object is not available during the build process, which can cause issues if code tries to access Chrome APIs at module load time. Ensure all Chrome API calls happen inside functions that are only called at runtime in the extension context.
// Bad: Chrome API accessed at module load time
import { getStorage } from './storage';
const settings = chrome.storage.local.get(); // Will fail in Node.js
// Good: Chrome API accessed in functions
export async function getSettings() {
return chrome.storage.local.get();
}
Circular Dependencies
Circular dependencies can cause issues in bundled code. Rollup handles some circular dependencies correctly, but complex circular reference patterns may cause unexpected behavior. Restructure your code to minimize circular dependencies, or use the output.inlineDynamicImports option for problematic entry points.
Conclusion
Rollup provides a powerful and flexible solution for building Chrome extensions in 2025. Its simple configuration model, excellent tree-shaking capabilities, and comprehensive plugin ecosystem make it ideal for extension projects of any size. By following the patterns and configurations in this guide, you can create efficient, optimized builds that result in fast-loading extensions that perform well and pass Chrome Web Store review.
Remember to test your extension thoroughly at each stage of configuration development. Build tooling is foundational to your extension’s quality, so investing time in getting it right pays dividends throughout your development process. With Rollup properly configured, you can focus on building great extension features rather than fighting with your build system.
For more information on Chrome extension development, explore our other guides covering Manifest V3, service workers, content scripts, and extension publishing.