If you know HTML, CSS, and JavaScript, you're already equipped to build Chrome extensions! And if you're familiar with React, even better - we can create a powerful extension using modern tools and best practices.
Getting Started
First, let's create a new Vite project:
npm create vite@latest my-extension --template react-ts
After creating your project, we need the CRXJS Vite plugin for Chrome extension development:
npm install @crxjs/vite-plugin
This plugin is crucial as it provides hot reloading functionality - your extension will update automatically as you code, without manual refreshing and building everytime.
Essential Configuration
Let's set up the Vite configuration first:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'
import { resolve } from 'path'
export default defineConfig({
plugins: [
react(),
crx({
manifest,
contentScripts: {
injectCss: true,
},
}),
],
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
})
Next, create your manifest file:
// manifest.json
{
"manifest_version": 3,
"name": "My First Extension",
"version": "1.0",
"description": "A sample extension to demonstrate core files",
"permissions": ["tabs", "storage"],
"action": {
"default_popup": "src/popup/index.html"
},
"content_scripts": [
{
"js": ["src/content/index.tsx"]
}
]
}
Core Files Structure
Here's the essential folder structure for your extension:
vite.config.ts
manifest.json
src
├── assets
│ └── styles
│ └── index.css
├── background
│ └── index.ts
├── content
│ ├── content.tsx
│ └── index.tsx
├── popup
│ ├── index.html
│ ├── index.tsx
│ └── popup.tsx
└── utils
└── create-shadow-root.tsx
Want to understand the core files use case better? For a detailed explanation of each file's purpose and use cases, check out A Deep Dive into Chrome Extension Architecture: Understanding the Core Files
Let's break down each core component:
1. Background Script
// src/background/index.ts
console.log('background script is activated')
2. Content Scripts
// src/content/content.tsx
import React from 'react'
const Content = () => {
return (
<div>Content</div>
)
}
export default Content
// src/content/index.tsx
import styles from '../assets/styles/index.css?inline'
import createShadowRoot from '../utils/create-shadow-root'
import Content from './content'
const root = createShadowRoot(styles)
root.render(<Content/>)
3. Popup Interface
<!-- src/popup/index.html -->
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Extension Popup</title>
</head>
<body>
<div id="ext-popup-page"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>
// src/popup/popup.tsx
import React from 'react'
const Popup = () => {
return (
<div>Popup</div>
)
}
export default Popup
// src/popup/index.tsx
import styles from "../assets/styles/index.css?inline"
import createShadowRoot from "../utils/create-shadow-root"
import PopUp from "./popup"
const root = createShadowRoot(styles)
root.render(<PopUp />)
Shadow DOM Integration
One crucial aspect that many developers overlook is proper CSS isolation. Here's our shadow root utility:
// src/utils/create-shadow-root.tsx
import { createRoot } from 'react-dom/client'
export default function createShadowRoot(styles: string) {
const container = document.createElement('div')
const shadow = container.attachShadow({ mode: 'open' })
const globalStyleSheet = new CSSStyleSheet()
globalStyleSheet.replaceSync(styles)
shadow.adoptedStyleSheets = [globalStyleSheet]
document.body.appendChild(container)
return createRoot(shadow)
}
This Shadow DOM implementation is crucial because:
It isolates your extension's styles from the host website
Prevents CSS conflicts and style leakage
Ensures consistent styling across different websites
Protects your extension's UI from host page style changes
Pro Tips
Always use the Shadow DOM for content scripts
Keep your manifest.json clean and only request necessary permissions
Use TypeScript for better code maintainability
Leverage hot reloading during development
Organize your code into clear, separate concerns (popup, content, background)
That's it! You now have a solid foundation for building a modern Chrome extension with React. The setup handles all the core functionality while maintaining clean separation of concerns and proper style isolation.