Contents
After publishing my first article on building a Chrome Extension using React, TypeScript, TailWind, and Webpack, a handful of people commented that Webpack was an antique best left on the shelf. In this article, I will walk you through creating the a Chrome Extension that uses the same tech stack but with Vite as the build tool.
For the full code, check out the GitHub repository.
Introduction
In this tutorial, we’ll build a Chrome extension using React, TypeScript, Tailwind CSS, and Vite. Vite is a modern build tool that offers a fast and lean development experience. By the end of this guide, you’ll have a fully functional Chrome extension with a popup and side panel, all powered by React components styled with Tailwind CSS.
Prerequisites
• Node.js and npm installed
• Basic understanding of React and TypeScript
• Familiarity with Chrome extensions is helpful but not required
Creating a Popup
1. Create the React App with Vite
Use Vite to scaffold a new React project with TypeScript support:
npm create vite@latest react-chrome-ext -- --template react-ts
cd react-chrome-ext
This will create a new project named react-chrome-ext using the React TypeScript template.
2. Install Dependencies
Navigate to the project directory and install the dependencies:
npm install
npm install --save-dev @types/node
npm install --save-dev vite-plugin-static-copy
3. Update App.tsx
Open src/App.tsx
and replace its content with:
function App() {
return (
<div className="App">
Hello World
</div>
);
}
export default App;
4. Update index.css
Replace the content of src/index.css with:
.App {
color: white;
text-align: center;
}
.SidePanel {
background-color: #282c34;
color: blue;
padding: 1rem;
width: 20%;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
.container {
width: 15rem;
height: 15rem;
background-color: green;
}
5. Modify main.tsx
Update src/main.tsx
to properly render the app in the Chrome extension context:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
const rootElement = document.createElement('div');
rootElement.className = 'container';
document.body.appendChild(rootElement);
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
6. Create the manifest.json File
At the root of your project, create a manifest.json
file with the following content:
{
"manifest_version": 3,
"name": "React Chrome Extension with Vite",
"version": "1.0.0",
"description": "A Chrome extension built with React, TypeScript, Tailwind CSS, and Vite",
"action": {
"default_popup": "index.html",
"default_title": "React Chrome Extension"
}
}
7. Configure Vite for Chrome Extension Build
7.1 Update vite.config.ts
Create or modify vite.config.ts
at the root of your project. We will use the vite-plugin-static-copy plugin to copy the manifest into the /dist
(distribution) folder:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig({
plugins: [
react(),
viteStaticCopy({
targets: [
{
src: 'manifest.json',
dest: ''
}
]
})
],
build: {
outDir: 'dist',
rollupOptions: {
input: {
popup: resolve(__dirname, 'index.html')
},
output: {
entryFileNames: '[name].js',
chunkFileNames: '[name].[hash].js',
assetFileNames: '[name].[ext]'
}
}
}
});
7.2 Adjust index.html
Ensure index.html
is correctly set up:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>React Chrome Extension</title>
</head>
<body>
<!-- Vite will replace this with your app -->
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
8. Build the Extension
Run the build command to generate the extension files in the /dist
directory:
npm run build
9. Load the Extension in Chrome
• Open Chrome and navigate to chrome://extensions. • Enable “Developer mode” if it’s not already enabled. • Click “Load unpacked” and select the dist directory from your project.
Your extension should now be loaded. Click the extension icon to see your popup with the green box and “Hello World” message.
Creating a Side Panel
Let’s add a side panel to the extension.
1. Install the Chrome Types
Since we will be using the global chrome namespace in our TypeScript project, we need to install the appropriate types:
npm i @types/chrome
2. Create a Background Service Worker
Create a new file src/background.ts
:
chrome.runtime.onInstalled.addListener(() => {
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
});
export {};
3. Update manifest.json
Modify manifest.json
to include the background service worker and side panel. I also removed the action
field:
{
"manifest_version": 3,
"name": "React Chrome Extension with Vite",
"version": "1.0.0",
"description": "A Chrome extension built with React, TypeScript, Tailwind CSS, and Vite",
"permissions": ["sidePanel"],
"background": {
"service_worker": "background.js"
},
"side_panel": {
"default_path": "sidepanel.html"
}
}
4. Create the Side Panel Component
Create src/SidePanel.tsx
:
function SidePanel() {
return <div className="SidePanel">Side Panel</div>;
}
export default SidePanel;
5. Create sidepanel.html
Add a new HTML file sidepanel.html
at the root:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Side Panel</title>
</head>
<body>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
6. Create index.tsx
Create src/index.tsx
to render the SidePanel component:
import React from 'react';
import ReactDOM from 'react-dom/client';
import SidePanel from './SidePanel';
import './index.css';
const rootElement = document.createElement('div');
rootElement.className = 'container';
document.body.appendChild(rootElement);
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<SidePanel />
</React.StrictMode>
);
7. Update vite.config.ts
Modify vite.config.ts
to include the new entry point for the side panel and the background script:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { resolve } from "path";
import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig({
plugins: [
react(),
viteStaticCopy({
targets: [
{
src: "manifest.json",
dest: "",
},
],
}),
],
build: {
outDir: 'dist',
rollupOptions: {
input: {
popup: resolve(__dirname, 'index.html'),
sidepanel: resolve(__dirname, 'sidepanel.html')
background: resolve(__dirname, "src/background.ts"),
},
output: {
entryFileNames: '[name].js',
chunkFileNames: '[name].[hash].js',
assetFileNames: '[name].[ext]'
}
}
}
});
8. Build and Reload the Extension
Rebuild the extension:
npm run build
Reload the extension in Chrome: • Go back to chrome://extensions. • Click the “Reload” button for your extension.
Now, when you click the extension icon, it should open the side panel displaying “Side Panel”.
Organize Your Project
Organizing your project files enhances maintainability.
1. Create a background Folder
Move background.ts
to src/app/background.ts
.
2. Update vite.config.ts
Update the path in vite.config.ts
:
export default defineConfig({
// ... previous config
build: {
// ... previous build options
rollupOptions: {
input: {
popup: resolve(__dirname, 'index.html'),
sidepanel: resolve(__dirname, 'sidepanel.html'),
background: resolve(__dirname, 'src/app/background.ts')
},
// ... output options
}
}
});
3. Build and Reload
Rebuild and reload the extension to ensure everything works as expected.
Add More Features
1. Create an Options Page
1.1 Create src/features/options/Options.tsx
function Options() {
return (
<div className="Options">
<h1>Extension Options</h1>
</div>
);
}
export default Options;
1.2 Create src/features/options/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import Options from './Options';
import "../../index.css";
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Options />
</React.StrictMode>
);
1.3 Create options.html
Add options.html
at the root:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Extension Options</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/features/options/index.tsx"></script>
</body>
</html>
1.4 Update manifest.json
Add the options page:
{
// ... existing manifest content
"options_page": "options.html"
}
1.5 Update vite.config.ts
Include the options page in the build inputs:
rollupOptions: {
input: {
popup: resolve(__dirname, 'index.html'),
sidepanel: resolve(__dirname, 'sidepanel.html'),
options: resolve(__dirname, 'options.html')
},
// ... output options
}
2. Build and Reload
Rebuild and reload the extension to include the new options page.
Communicate Between Components
Use Chrome’s messaging API to enable communication between different parts of your extension.
1. Sending a Message from Options
In Options.tsx
:
function Options() {
const sendMessage = () => {
chrome.runtime.sendMessage(
{ type: 'GREETING', payload: 'Hello from Options' },
(response) => {
console.log('Response:', response);
}
);
};
return (
<div className="Options">
<h1>Extension Options</h1>
<button onClick={sendMessage}>Send Message</button>
</div>
);
}
export default Options;
2. Receiving the Message in Side Panel
In SidePanel.tsx
:
import { useEffect } from 'react';
function SidePanel() {
useEffect(() => {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GREETING') {
console.log('Received message in Side Panel:', message.payload);
sendResponse({ message: 'Hello from Side Panel' });
}
});
}, []);
return <div className="SidePanel">Side Panel</div>;
}
export default SidePanel;
3. Build and Test
Rebuild the extension, reload it, and test the messaging functionality.
Add Tailwind CSS
Integrate Tailwind CSS to style your components.
1. Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
2. Initialize Tailwind CSS
npx tailwindcss init -p
This creates tailwind.config.js
and postcss.config.js
.
3. Configure tailwind.config.js
Update the content property:
module.exports = {
content: ['./index.html', './sidepanel.html', './options.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
4. Include Tailwind Directives
Replace the contents of src/tailindexwind.css
with this:
@tailwind base;
@tailwind components;
@tailwind utilities;
5. Update the Components
In your component files, you can now use the Tailwind classes. Here is an updated SidePanel.tsx
:
function SidePanel() {
return <div className="SidePanel bg-red-400 p-4">Side Panel</div>;
}
export default SidePanel;
6. Update Vite Config for CSS
Ensure your Vite config supports CSS processing. Vite handles this by default, so no additional configuration is needed.
7. Build and Verify
Rebuild the extension and verify that Tailwind CSS styles are applied correctly.
Conclusion
Using Vite simplifies the development of Chrome extensions with React and TypeScript. The integration with Tailwind CSS allows for rapid UI development with utility-first styling.
Happy coding! ☕️