Building a chrome extension may seem like a daunting task, but it is quite easy to implement. By following a guide, you will be able to create your extension in no time.
This tutorial will provide you with information on how to create a popup, use a content script, and work with chrome local storage, among other things.
Why Chrome extension?
Google Chrome extension is like a special tool that you can add to your Google Chrome web browser to make it do extra things! Just like how you can add different tools to a toolbox, you can add different extensions to your web browser.
For example, you might have an extension that blocks ads or an extension that helps you save pictures you find on the internet.
It's just like having a magic wand, you can add different spells to it and use it whenever you want.
Chrome Extension Setup with ReactJs
1. Create Manifest Json
First, we need to create manifest.json
with version 3 which is the latest
{
"name": "Chrome Extension With ReactJs",
"description": "This is the boilerplate for the chrome extension with React",
"version": "1.0.0",
"manifest_version": 3,
"icons": {
"16": "logo.png",
"48": "logo.png",
"128": "logo.png"
},
"action": {
"default_title": "Chrome Extension With React",
"default_popup": "popup.html"
},
"options_page": "options.html",
"content_scripts": [
{
"matches": ["https://www.google.com/"],
"js": ["contentScript.js"]
}
],
"permissions": ["storage"]
}
Main metadata fields that need to understand
manifest_version
: This field specifies which version of the manifest file format is being used.
permissions
: This field lists the specific permissions that the extension requires in order to function, such as access to a user's browsing history or the ability to modify web pages.
content_scripts
: This field lists the JavaScript files that should be injected into web pages to add functionality to the extension.
background
: This field describes the background script that runs in the background when the extension is active.
browser_action
or page_action
: This field describes the icon that appears in the browser toolbar when the extension is active, and what happens when the user clicks on it.
options_page
: This field describes the URL of the options page that appears when the user clicks on the extension's settings.
icons
: This field contains the path to the icons that are used to represent the extension in the browser's UI.
web_accessible_resources
: This field lists the resources that should be made available to web pages.
2. Install Webpack, Webpack CLI
yarn add webpack webpack-cli -D
Here is an example of a webpack configuration file, "webpack.config.js", that can be used to bundle your extension's code:
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlPlugin = require("html-webpack-plugin");
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const Dotenv = require('dotenv-webpack');
module.exports = {
mode: "development",
devtool: "cheap-module-source-map",
entry: {
popup: path.resolve("src/popup/index.tsx"),
options: path.resolve("src/options/index.tsx"),
contentScript: path.resolve("src/contentScript/index.tsx"),
},
module: {
rules: [
{
use: "ts-loader",
test: /\.(tsx|ts)$/,
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1,
},
},
{
loader: "postcss-loader", // postcss loader needed for tailwindcss
options: {
postcssOptions: {
ident: "postcss",
plugins: [tailwindcss, autoprefixer],
},
},
},
],
},
{
type: "assets/resource",
test: /\.(png|jpg|jpeg|gif|woff|woff2|tff|eot|svg)$/,
},
],
},
plugins: [
new Dotenv(),
new CleanWebpackPlugin({
cleanStaleWebpackAssets: false,
}),
new CopyPlugin({
patterns: [
{
from: path.resolve("src/static"),
to: path.resolve("dist"),
},
],
}),
...getHtmlPlugins(["popup", "options", "contentScript"]),
],
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
output: {
filename: "[name].js",
path: path.join(__dirname, "dist"),
},
optimization: {
splitChunks: {
chunks(chunk) {
return chunk.name !== "contentScript";
},
},
},
};
function getHtmlPlugins(chunks) {
return chunks.map(
(chunk) =>
new HtmlPlugin({
title: "Chrome Extension with ReactJs",
filename: `${chunk}.html`,
chunks: [chunk],
})
);
}
Core functionality about webpack
entry
: The entry point is the starting point for webpack to begin building the dependency graph for the application. It tells webpack where to start looking for code to bundle.
It can be a single file or an object of multiple files.
rules
: The "rules" field is used to specify loaders that should be used to process certain types of files. These rules are used to transform different types of files, such as TypeScript, JSX, and CSS, into a format that the browser can understand.
Each rule has a "test" property that is used to match the files that should be processed by the loader and a "use" property that defines the loader that should be used.
plugins
: Plugins are used to perform a wider range of tasks than loaders, such as bundle optimization, asset management, and injection of environment variables.
They are added to the config using the "plugins" field and they can be used to perform a wide range of tasks, like bundle optimization, asset management, and injection of environment variables.
Some examples of plugins are HtmlWebpackPlugin, CleanWebpackPlugin, and UglifyJsPlugin.
Reference: Visit Webpack
3. Install ReactJs and Typescript
For ReactJs
Run yarn add react react-dom
For Typescript
Run yarn add typescript ts-loader -D
ts-loader
is the TypeScript loader for webpack.
Create a simple tsconfig.json file in the root directory
{
"compilerOptions": {
"experimentalDecorators": true,
"jsx": "react",
"module": "es6",
"target": "es6",
"moduleResolution": "node",
"esModuleInterop": true,
"incremental": true,
"downlevelIteration": true
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}
4. Setup the project folder
5. Create Popup Component
First, let's create an index.tsx
file in the popup folder
import React from "react";
import { createRoot } from "react-dom/client";
import "../assets/css/tailwind.css";
import Popup from "./Popup";
function init() {
const appContainer = document.createElement("div");
document.body.appendChild(appContainer);
if (!appContainer) {
throw new Error("Cannot find appContainer");
}
const root = createRoot(appContainer);
root.render(<Popup />);
}
init()
Popup.tsx
import React from 'react'
function Popup() {
return (
<div className='w-52 h-20'>
<div>
<h1 className='text-center p-5 text-xl'>This is a popup section</h1>
</div>
</div>
)
}
export default Popup
To include the filename "popup" in the webpack configuration, we need to make the following changes in the webpack.config.js
file:
6. Content Script
You can create a contentscript component that works similarly to a popup component by adding the corresponding file in the webpack.config.js file.
This will allow webpack to bundle and process the content script in the same way as it would the popup component, making it ready for use in the extension.
But here is the twist. You need to add the contentscript.js file in the manifest.json
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["contentScript.js"] //Path to contentScript.js
}
],
In the above code, the field is defined as an array with a single object, which has the following properties:
matches
: This property specifies the URLs that the content script should be injected into. The value of "" means that the script will be injected into all URLs, regardless of the domain.
js
: This property specifies the JavaScript files that should be injected into the web pages. The value ["contentScript.js"] means that the file located at "contentScript.js" will be injected.
This configuration tells the browser to inject the content script into all URLs and the script will be the one located at "contentScript.js".
This allows the extension to run the JavaScript code on any webpage the user visits and make modifications to the web page and interact with the web page.
Reference: Content Script with Manifest v3
7. Chrome Storage
Chrome storage
is a way to store data in the browser, which can be used to persist data across browser sessions. Chrome provides two types of storage options: "local
" and "sync
".
"local" storage: Data stored in local storage will only be available on the device where it was stored, and will not be synced across devices.
"sync" storage: Data stored in sync storage will be synced across all devices where the user is signed in to Chrome with the same account.
First, we need to add certain information in manifest.json
{
"name": "Chrome Extension With ReactJs",
...
"permissions": [
"storage"
],
...
}
Below is the code example using chrome.storage API to store and retrieve data in local storage in the Popup React Component
function Popup() {
const [value, setValue] = useState<string>('');
const handleSave = () => {
chrome.storage.local.set({ key: value }, () => {
console.log('Value is set to ' + value);
});
}
const handleLoad = () => {
chrome.storage.local.get(['key'], (result) => {
console.log('Value currently is ' + result.key);
setValue(result.key);
});
}
return (
<div className='w-72'>
<div className='p-4'>
<h1 className='text-base mb-4 font-medium'>Chrome Extension with ReactJs</h1>
<div className='mb-10'>
<input
value={value}
onChange={e => setValue(e.target.value)}
className="bg-gray-50 border border-gray-200 w-full rounded-lg px-4 py-2 text-black focus:outline-none mb-5" />
<div className='flex items-center justify-end'>
<button onClick={handleSave} className="bg-green-500 hover:bg-green-400 transition duration-300 px-6 py-2 rounded-md text-white text-center">
<span>Save</span>
</button>
</div>
</div>
<div className=''>
<p className='text-sm mb-2'>Local Storage value</p>
<div className='flex items-center space-x-2'>
<button onClick={handleLoad} className="bg-orange-500 hover:bg-orange-400 transition duration-300 px-6 py-2 rounded-md text-white text-center">Load</button>
<span className='text-lg font-semibold'>{value}</span>
</div>
</div>
</div>
</div>
)
}
Reference: Chrome Storage
8. Tailwind CSS
Run yarn add tailwindcss postcss autoprefixer -D
For webpack, we also need to install
yarn add postcss-loader style-loader css-loader -D
Create postcss.config.js
file in the root directory
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
Also, create tailwind.css
file inside the src/assets/css folder and add
@tailwind base;
@tailwind components;
@tailwind utilities;
9. Build the file and Load it into chrome://extensions/
in the Chrome browser
Run yarn build
to bundle your code. It creates a "dist" folder, which will contain all the final files needed for your extension.
Go to "chrome://extensions" page from the URL.
Load your build file from the "dist" folder
Enable the developer mode.
Test your extension and make any necessary changes. Once you are satisfied, submit it to the Chrome Web Store for others to use.
10. Get the final code
You can find the final code for this project in a GitHub repository
Conclusion
Coming this far, creating a chrome extension with ReactJS can be a great way to add functionality to the browser and enhance the user experience. With the use of React, you can build a robust, maintainable, and scalable extension. It is important to understand the chrome extension API and how it works, as well as be familiar with webpack and manifest.json file
It is also important to note that Google has announced the Manifest V3 for chrome extension which will impact the way extensions work and change the way they interact with web pages. So, it's important to keep yourself updated with the latest changes and best practices.
👏👏 👏👏
I encourage you to try building your own chrome extension using ReactJS and see how it goes.
If you have any issues or questions, please don't hesitate to reach out and leave a comment. Additionally, I'd love to hear your thoughts and feedback on the project.
Till then,
Keep on Hacking, Cheers
Top comments (4)
Unless your extension is considerable in scope, I would suggest that using this amount of tooling and bloat is extreme overkill. You'd be much better off with vanillaJS
I have a small/medium size chrome extension, but I'm using React for it, because there are so many free UI libraries that are specifically designed to use inside React. I have a great UI due to that, and that alone is worth using react
Definitely true but when we need to do some DOM manipulation and API fetch. ReactJs is the best tool for me.
DOM manipulation and API fetching - 100% built in to JS