Building Chrome extensions is a powerful way to enhance browser functionality through automation. While my previous guide covered creating extensions with Vite, React, and TypeScript, today I’m focusing on a streamlined development workflow using rollup-plugin-chrome-extension.
This setup eliminates the need to constantly rebuild and refresh your extension during development. Let’s dive in!
Setting Up Your Development Environment
Step 1: Install Node.js with NVM
First, we need a proper Node.js environment. Node Version Manager (NVM) lets you manage multiple Node versions effortlessly:
# Install NVM
brew install nvm
# Create NVM's working directory
mkdir ~/.nvm
Add these lines to your shell profile (~/.zshrc or ~/.bash_profile):
export NVM_DIR="$HOME/.nvm"
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && . "/opt/homebrew/opt/nvm/nvm.sh"
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && . "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm"
Apply the changes immediately:
source ~/.zshrc
Verify NVM installation:
nvm --version
Install the latest Node.js:
nvm install node
Step 2: Set Up Package Management
Enable Corepack for modern package management:
corepack enable
Verify with:
corepack --version
Check if Yarn is installed:
yarn --version
If not, install it:
brew install yarn
Step 3: Initialize Your Project
Create and navigate to your project directory:
mkdir chrome-extension-hot-reload
cd chrome-extension-hot-reload
yarn init -y
Step 4: Install Essential Dependencies
Add the rollup-plugin-chrome-extension and supporting packages:
yarn add -D rollup rollup-plugin-chrome-extension @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript
For TypeScript support, add:
yarn add -D typescript @types/chrome
Step 5: Configure Your Project
Create a rollup.config.mjs
file:
import { chromeExtension, simpleReloader } from 'rollup-plugin-chrome-extension';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/manifest.json',
output: {
dir: 'dist',
format: 'esm',
},
plugins: [
chromeExtension(),
simpleReloader(), // Enables hot reloading
nodeResolve(),
commonjs(),
typescript(),
],
};
Create a minimal tsconfig.json
:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"outDir": "dist",
"rootDir": "src"
}
}
Update your package.json
with these scripts:
{
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w"
},
"type": "module"
}
Step 6: Create Basic Extension Files
Set up your project structure:
mkdir -p src/background src/popup
Create a basic src/manifest.json
:
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"description": "A Chrome extension with hot reloading",
"background": {
"service_worker": "background/index.js"
},
"action": {
"default_popup": "popup/index.html"
},
"permissions": ["storage"]
}
Add a simple background script (src/background/index.ts
):
console.log('Background script running!');
chrome.runtime.onInstalled.addListener(() => {
console.log('Extension installed!');
});
Create a popup interface:
src/popup/index.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<h1>My Extension</h1>
<p>Count: <span id="counter">0</span></p>
<button id="increment">Increment</button>
</div>
<script src="index.js"></script>
</body>
</html>
src/popup/styles.css
:
body {
width: 300px;
padding: 10px;
font-family: system-ui, sans-serif;
}
button {
margin-top: 10px;
padding: 5px 10px;
background: #4285f4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #3367d6;
}
src/popup/index.ts
:
let count = 0;
const counterElement = document.getElementById('counter') as HTMLElement;
const incrementButton = document.getElementById('increment') as HTMLButtonElement;
// Load the stored count
chrome.storage.sync.get(['count'], (result) => {
count = result.count || 0;
updateCounter();
});
// Update the counter display
function updateCounter() {
counterElement.textContent = count.toString();
}
// Handle button click
incrementButton.addEventListener('click', () => {
count += 1;
chrome.storage.sync.set({ count });
updateCounter();
});
Running Your Extension
Start the development server with hot reloading:
yarn dev
Load your extension in Chrome:
- Open Chrome and go to
chrome://extensions/
- Enable “Developer mode”
- Click “Load unpacked” and select the
dist
directory
Now, whenever you make changes to your code, rollup will rebuild the extension and the simpleReloader plugin will refresh it automatically in Chrome!
Troubleshooting Common Issues
Module Format Problems
If you see this error:
[!] RollupError: Node tried to load your configuration file as CommonJS even though it is likely an ES module.
Ensure your rollup config file has the .mjs
extension and your package.json includes "type": "module"
.
Missing Plugins
If rollup complains about missing plugins, install them individually:
yarn add -D @rollup/plugin-node-resolve @rollup/plugin-commonjs
Chrome Manifest Validation Errors
Chrome’s strict validation may reject your extension if the manifest has issues. Check Chrome’s error console in the extensions page for details.
Conclusion
This setup gives you a streamlined Chrome extension development workflow with hot reloading. The rollup-plugin-chrome-extension handles all the complexity of rebuilding your extension and keeping it in sync with Chrome.
By eliminating the manual refresh-rebuild cycle, you can focus on building functionality rather than managing your development environment.
What Chrome extension ideas are you planning to build? The possibilities are endless, from productivity tools to content enhancers and beyond!