Create a Chrome Extension with TypeScript React and Webpack
Learn how to create a chrome extension with TypeScript, 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
- Handling TypeScript
- Creating the React Application and Scripts
- Installing Typings
- 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-typescript-react-and-webpack
cd create-a-chrome-extension-with-typescript-react-and-webpack
Now lets create a src folder to hold our source code.
mkdir src
cd src
At the top level of the src folder 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.ts file. Inside the react folder add an index.tsx file.
cd background
touch index.ts
cd ../content
touch index.ts
cd ../react
touch index.tsx
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.tsx file.
cd components
touch App.tsx
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
Handling TypeScript
Now lets turn our project into a TypeScript project. First, lets install TypeScript. TypeScript is simply an npm package called typescript. We can install it as a development dependency by tagging on -D.
npm i typescript -D
Now lets initialize this project as a TypeScript project by running the command npx tsc --init.
npx tsc --init
This creates a tsconfig.json file and fills it with some default values. We will be using this tsconfig.json configuration for type checking and .d.ts file generation. We will use Webpack and Babel for the actual transpilation process. To do this, set the keys jsx, declaration, emitDeclarationOnly, and isolatedModules inside tsconfig.json compilerOptions.
"compilerOptions": {
"jsx": "react-jsx",
"declaration": true,
"emitDeclarationOnly": true,
"isolatedModules": true
}
- jsx - specifies what JSX code is generated
- declaration - generates type files (.d.ts files) from TypeScript and JavaScript files
- emitDeclarationOnly - only emit .d.ts files and not JavaScript files
- isolatedModules - ensures that each TypeScript file can be transpiled without relying on other imports
Lets also set the top level options include and exclude.
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
- include - specifies an array of filenames or patterns to include in the program
- exclude - specifies an array of filenames or patterns that should be skipped when resolving include. We want to exclude any node_modules and dist folders.
Creating the React Application and Scripts
Now lets create the React application, background script, and content script with Webpack. First, create a webpack configuration file called webpack.config.cjs at the top level of the project.
touch webpack.config.cjs
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 TSX and TypeScript 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 @babel/preset-typescript -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 and the babel typescript preset adds support for TypeScript. 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.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'production',
target: 'web',
entry: {
contentScript: './src/content/index.ts',
background: './src/background/index.ts',
react: './src/react/index.tsx'
},
output: {
path: path.resolve(__dirname, '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: /.(ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
['@babel/preset-react', {'runtime': 'automatic'}],
'@babel/preset-typescript'
]
}
}
}
]
},
resolve: {
extensions: ['.ts', '.tsx']
}
};
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 TypeScript and TSX files through the babel loader to convert it into code that the browser understands. We also set the mode to production and the target to web. This is because we will be working in a browser environment.
Installing Typings
To get the benefits of TypeScript, we also need to install npm packages from the @types scope. These packages contain TypeScript declaration files, or files with a .d.ts extension, that inform the TypeScript compiler about all the typing information for the specific library. For example, @types/chrome provides TypeScript type information for working with Chrome. Lets install the @types/chrome package as a development dependency from npm using the command npm i @types/chrome -D.
npm i @types/chrome -D
Lets also install the typings for react and react-dom.
npm i @types/react @types/react-dom -D
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.tsx 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') as HTMLElement);
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.tsx.
const dogSrc: string = 'https://media.tenor.com/fej4_qoxdHYAAAAM/cute-puppy.gif';
const generateDogGif = async () => {
// Get the active tab
const tabs = await chrome.tabs.query({active: true, currentWindow: true});
const activeTab = tabs[0];
// Send the dog Gif
chrome.tabs.sendMessage(activeTab.id || 0, dogSrc);
};
const App = () => {
return (
<main>
<h1>Add a Dog Gif to Webpage</h1>
<img src={dogSrc} />
<button onClick={generateDogGif}>Generate Dog Gif</button>
</main>
);
};
export default App;
This component contains a button that when clicked sends the tiny dog Gif to the active tab.
Creating the Content Script
Inside our content script index.ts 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 dogImg: HTMLImageElement = document.createElement('img');
dogImg.src = msg;
document.body.appendChild(dogImg);
}
);
Here, we attach a message listener to the content script. When a message is received from the React application we create an img tag with the tiny dog Gif and append it to the DOM.
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.cjs"
},
Now when we run npm run build, we will bundle our application with webpack using the configuration inside webpack.config.cjs.
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. We can also create a command that will build our project with tsc using the configuration supplied inside tsconfig.json.
"types": "npx tsc"
We can then run this command to generate our .d.ts files.
npm run types
[object Object]