React is one of the most popular front-end frameworks. Using React for Chrome extension development is also recommendable. I'll walk through my implementation using create-react-app to bootstrap the React project, then manually create the additional files required for the Chrome extension.
Create A React Project
I'll bootstrap the project by create-react-app. First install it:
npm install -g create-react-app
then create a project named react-proj:
npx create-react-app react-proj
Customize Webpack Configuration
To gain control over the build process, I customized the Webpack configuration.
install necessary packages:
npm install --save-dev webpack webpack-cli
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react
npm install --save-dev babel-loader style-loader css-loader postcss-loader
npm install --save-dev html-webpack-plugin copy-webpack-plugin
the versions of React & Webpack:
{
"react": "^19.1.0",
"react-dom": "^19.1.0",
...
"webpack": "^5.99.8",
"webpack-cli": "^6.0.1"
}
Then create a new webpack.config.js file in the project's root directory with the following content:
// webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HTMLPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: {
index: "./src/index.js"
},
mode: "production",
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
['@babel/preset-react', {
"runtime": "automatic"
}]
]
}
},
},
{
test: /\.css$/i,
use: [
"style-loader",
"css-loader",
"postcss-loader"
]
},
],
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new HTMLPlugin({
title: "React extension",
filename: 'index.html',
chunks: ['index'],
templateContent: `
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="root"></div>
</body>
</html>
`
}),
new CopyPlugin({
patterns: [
// files needed by extension
{ from: "icon.png", to: "../icon.png" },
{ from: "manifest.json", to: "../manifest.json" },
{ from: "background.js", to: "../background.js" },
],
})
],
output: {
path: path.join(__dirname, "dist/my-extension"),
filename: "index.js",
},
};
With the customized webpack.config.js, the build process outputs both the final index.js bundle and index.html into the dist/my-extension
directory.
Using Webpack's LimitChunkCountPlugin alongside HTMLPlugin ensures all source code and styles get bundled into a single chunk (index.js), and the generated index.html automatically includes references to this consolidated bundle. Later we will see the extension's manifest.json specifies this HTML file (index.html) as the entry point.
At this point, our React project is almost ready. The next step involves adding the necessary extension-specific files to make it operational. Now let's see how these files created.
Create Extension Files
According to my implementation, the extension-specific files are directly placed under the project's root directory. While the webpack.config.js configured these files to be copied from the project's root directory to the dist, you can place it anywhere you want. Just remember to update the corresponding paths in your Webpack configuration accordingly.
The required extension-specific files may vary depending on your chosen implementation approach. For a comprehensive overview of extension files, please refer to my previous post. In this guide, I'll demonstrate the 'New Tab' implementation.
To implement the 'New Tab' approach - open a new browser tab when users click the extension's toolbar icon - we need to add two key files:
- manifest.json - The extension's configuration file
- background.js - Handles the extension's click event
Create a new file of manifest.json:
{
"manifest_version": 3,
"version": "0.0.1",
"name": "my-extension",
"description": "extension description here",
"action": {
"default_icon": "icon.png"
},
"background": {
"service_worker": "/background.js"
}
}
Create a new file of background.js:
chrome.action.onClicked.addListener(function(tab) {
chrome.tabs.create({'url': chrome.runtime.getURL('my-extension/index.html')},
function(tab) {
// Tab opened.
});
});
The generated HTML file of 'my-extension/index.html' is specified as the entry point, it will be loaded by resolving its path through the chrome.runtime.getURL() API.
Finally, add a new file of icon.png under the project's root directory, it will be copied into the extension (configured from webpack.config.js) and serve as your extension's toolbar icon, as specified from manifest.json.
Define build script
Everything is ready. The final step is to set up a build script. Add the following to your React project's package.json:
"scripts": {
"build-extension": "rm -rf ./dist/* && webpack --config webpack.config.js",
// other scripts
}
When you run npm run build-extension
, all existing files under the dist directory will be purged, then a fresh bundle with the latest build files will be generated, and extension-specific files will be copied into dist again.
After the exexution of build-extension, the dist directory will become a complete, ready-to-use Chrome extension that immediately loadable for testing or distribution.
dist directory's structure:
dist
├── background.js
├── icon.png
├── manifest.json
└── my-extension
├── index.html
├── index.js
Finally
The React project contains the extension's core content, which can be displayed in various contexts - whether in a popup, sidebar, or new tab - depending on your implementation approach. The key requirement is ensuring the React components properly adapt to their container dimensions.
If you love working with React, why not try building your own browser extension with it?
I just developed a Chrome extension called Bookmark Dashboard, a bookmark management tool, and I’d love to share it with fellow bookmark enthusiasts!
Thanks for reading!