Create a Chrome Extension with React and Webpack
Learn how to create a chrome extension with React and Webpack. We will also set up Webpack to build our content and background scripts, use the chrome.tabs API to communicate between our popup and content script, and more.
Table of Contents 📖
- Project Structure Setup
- Creating React App with Webpack
- Configuring Manifest.json
- Creating the React Application
- Creating the Content Script
- Creating a Build Script
Project Structure Setup
To begin, lets set up our project structure. First, lets create a directory to hold the project.
mkdir create-a-chrome-extension-with-react-and-webpack
cd create-a-chrome-extension-with-react-and-webpack
Now lets create our src folder to hold our source code.
mkdir src
cd src
At the top level of the src folder, lets add an index.html file that will be our extension popup housing our React application.
touch index.html
Now lets create 3 different folders inside the src folder. One to hold our background script called background, one to hold our content script called content, and one to hold our react application called react.
mkdir background content react
Inside our background and content folders, add an index.js file. Inside the react folder add an index.jsx file.
cd background
touch index.js
cd ../content
touch index.js
cd ../react
touch index.jsx
Inside our react folder lets also add a folder called components to hold our react components.
cd react
mkdir components
Inside the components folder create an App.jsx file.
cd components
touch App.jsx
Next, lets initialize this project as an ES6 npm project with npm init es6 -y.
npm init es6 -y
Finally, lets create a manifest.json file at the top level of the project to provide the required information about the extension.
touch manifest.json
Creating React App with Webpack
Now lets create our React application. To do this, we will use the module bundler Webpack. First, create a webpack configuration file called webpack.config.js at the top level of the project.
touch webpack.config.js
Now we need to install a few libraries to get this working. First lets install Webpack itself and webpack-cli as development dependencies.
npm i webpack webpack-cli -D
Webpack will transpile our JSX code to JavaScript that the browser understands. However, for Webpack to do this we need to install a loader for webpack called babel loader, along with some other Babel dependencies.
npm i @babel/preset-env @babel/preset-react babel-loader -D
A loader is a function that Webpack passes code through to perform some sort of transformation. The Babel loader is a Webpack loader that transpiles JavaScript code. Babel presets are used to configure the Babel transipler. For example, the babel react preset adds support for JSX code. We also want to install a couple plugins for Webpack called html-webpack-plugin and copy-webpack-plugin.
npm i html-webpack-plugin copy-webpack-plugin -D
A Webpack plugin interacts with the Webpack lifecycle. The html-webpack-plugin creates an HTML file to place our bundled JavaScript code into. The copy-webpack-plugin is used to copy individual files or directories into the build folder. Now that we have the required libraries, lets configure webpack with a webpack configuration file.
import HtmlWebpackPlugin from 'html-webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin';
import path from 'path';
export default {
mode: 'production',
entry: {
contentScript: './src/content/index.js',
background: './src/background/index.js',
react: './src/react/index.jsx'
},
output: {
path: path.resolve('dist'),
filename: '[name].js',
clean: true
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CopyPlugin({
patterns: [{
from: path.resolve('manifest.json'),
to: path.resolve('dist')
}]
})
],
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
['@babel/preset-react', {'runtime': 'automatic'}]
]
}
}
}
]
},
resolve: {
extensions: ['.js', '.jsx']
}
};
This configuration will create three different output files. One called contentScript.js, one called background.js, and one called react.js. Webpack will place each of these inside a folder called dist. We also tell Webpack to copy over our manifest.json file into the dist folder. This is because this file is required for working with Chrome extensions. We also tell Webpack to pass all JavaScript and JSX files through the babel loader to convert it into code that the browser understands. We also set the mode to production.
Configuring Manifest.json
Every chrome extension requires a manifest.json file in its root directory. This file contains information such as the name of the extension, the location of the popup, permissions that the extension has, etc. Lets fill in this file for our extension.
{
"manifest_version": 3,
"name": "Create a Chrome Extension with React and Webpack",
"description": "Demo Chrome Extension that uses React and Webpack",
"version": "1.0.0",
"action": {
"default_popup": "index.html"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["contentScript.js"]
}
]
}
Here, we give the location of our popup, background script, and content script. We give the content script the permission to run on every URL. We also give our extension a name, description, and version. Note that the locations passed in here are the locations of these files inside the dist folder that Webpack will create.
Creating the React Application
Now that we have our project setup lets build our React application. First, lets install react and react-dom.
npm i react react-dom
Inside our index.jsx file, lets render our React application inside a div with the id root.
import {createRoot} from 'react-dom/client';
import App from './components/App';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
Now, lets fill in the index.html file that contains this div element.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="./react.js"></ script>
</body>
</html>
We can see the div with the id root that will house our React application. Below that, we can see the script file that contains the React code. The react.js file is what Webpack will create inside the final dist folder. Now, lets create the App component inside App.jsx.
import {useState} from 'react';
const App = () => {
const [scriptResp, setScriptResp] = useState(null);
const getRandomUser = async () => {
// Get users
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await resp.json();
// Get email
const randomEmail = users[Math.floor(Math.random() * users.length)].email;
// Get active tab
const tabs = await chrome.tabs.query({active: true, currentWindow: true});
const activeTab = tabs[0];
// Get the response
const tabResp = await chrome.tabs.sendMessage(activeTab.id, randomEmail);
setScriptResp(tabResp);
};
return (
<main>
<h1>Add User Contact to Page</h1>
<button onClick={getRandomUser}>Get Random User</button>
<h2>Content script says: {scriptResp}</h2>
</main>
);
};
export default App;
This component contains a button that when clicked, makes an API call to get some dummy user information. After this user information is retrieved, the chrome.tabs API is used to retrieve the current tab. Next, the chrome.tabs API is used to send a message to the current active tab. The message is the dummy user information retrieved from the API call. We then get a message back from the current tab and display that in the React application. Note that it will be the content script that will receive this message and return a response.
Creating the Content Script
Inside our content script index.js file, lets make it alter the current tab by appending the message sent from the React application.
chrome.runtime.onMessage.addListener(
function(msg, sender, sendResponse) {
const h1 = document.createElement('h1');
const text = document.createTextNode(`Please contact: ${msg}`);
h1.appendChild(text);
document.body.appendChild(h1);
sendResponse('Looks good to me bro!');
}
);
Here, we attach a message listener to the content script. When a message is received, the message sent from the React application, we create an h1 element and append it to the current webpage. We then send back a message to be displayed inside the React application.
Creating a Build Script
Now that our application is built, lets create a simple npm build script to build our application with Webpack.
"scripts": {
"build": "webpack --config webpack.config.js"
},
Now when we run npm run build, we will bundle our application with webpack using the configuration inside webpack.config.js.
npm run build
This creates the folder structure below.
[object Object]
Finally, visit chrome://extensions/ in a chrome browser and load the dist folder as an extension.