As of December 2024, Chrome remains the most popular browser worldwide. Learning to develop extensions for Chrome can open exciting opportunities to explore browser-based development and enhance your understanding of how browsers work under the hood. The Chrome Web Store offers a vast array of extensions that allow users to customize the browser’s default behavior and extend the functionality of various websites.
In this blog, we’ll go through setting up a local development environment for creating Chrome extensions using TypeScript and Webpack. This guide is perfect for anyone looking to grasp the basics of Chrome extension development. By the end, you’ll have a functioning development environment ready to experiment with your first extension. Before diving in, make sure you have a foundational understanding of web technologies, JavaScript, and commonly used tools in the JavaScript ecosystem. Details are listed in the prerequisites section.
Quick Overview of Chrome Extension Components
Before we jump into the setup, let’s briefly understand some key components of a Chrome extension:
Popup : Manages the extension’s user interface but does not have direct access to the DOM of the parent web page.
Content Script: Has direct access to the DOM of the parent web page but runs in a separate execution context. This separation means it cannot directly access JavaScript objects of the parent page.
Injected Script : Shares the same execution context as the parent web page, allowing access to its DOM and JavaScript objects.
Background Script : Operates in an isolated context, with no direct access to the DOM or JavaScript objects of the parent page.
The Popup, Content, and Background scripts operate in the extension’s contexts, whereas the Injected script runs in the context of the parent web page. The parent page refers to the active web page on which the extension performs its functionality. Permissions and access to these pages are defined in the manifest.json
file, which we’ll cover later in this blog.
Prerequisites
To follow along with this tutorial, ensure you have the following tools installed:
Node.js (v18.16 LTS)
NPM (Node Package Manager)
TypeScript
Webpack
VS Code Editor (or any code editor of your choice)
Setup
Every Chrome extension requires a file named manifest.json
at the root level of the project. This file serves as the configuration blueprint for your extension, containing essential details about the project. While there’s no strict rule about the overall project structure, we’ll begin by creating this file and progressively build the project as outlined in this blog.
Before proceeding, ensure that all prerequisites are installed on your machine.
Follow the steps below to set up your project and its dependencies:
Create a directory for your project and go inside that directory. This will be the root of your project. Everything from here on will be based of the root of your project unless stated otherwise.
mkdir chrome-extension && cd ./chrome-extension\
Create a file called as manifest.json
{
"manifest_version": 3,
"name": "My First Chrome App",
"version": "1.0",
"description": "A Chrome extension built with TypeScript using webpack.",
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
}
}
Most of the content in the manifest.json
is self explanatory except action
object. The default_icon
is the icon of your app that will appear next to your app’s name in Chrome and default_popup
specifies the HTML file to display as a popup when the extension’s icon is clicked.
Create a file called as popup.html
with the following content,
<html>
<head>
<title>First Chrome Extension</title>
</head>
<body>
<h1>This is my app popup</h1>
</body>
</html>
Include an image file named icon.png
in the root directory. This will serve as your app’s icon, displayed in the Chrome toolbar. Make sure the image is in a supported format (e.g., PNG) and appropriately sized.
Quick Test with bare minimum setup
Before diving into more complex functionality, let’s test this basic extension to ensure everything is set up correctly. This initial test will serve as the foundation for our development process and confirm that changes we make later are working as expected.
Open Chrome’s Extension Management : Open Chrome and navigate to the Manage Extensions
page by typing chrome://extensions/
in the address bar. This will take you to the Extensions
screen.
Enable Developer Mode : Locate the Developer mode
toggle at the top right of the screen and switch it on if it’s not already enabled. Enabling this mode allows Chrome to load extensions built locally in addition to those downloaded from the Chrome Web Store.
Load Your Extension : Click the Load unpacked
button at the top of the page. Browse to and select the root directory of your project.
Verify Installation : Once loaded, your extension should appear in the list of installed extensions.
Render the Popup : Click on the My First Chrome App
icon in the top-right corner of Chrome, just above the Manage Extensions
button. This action should render the popup.html
file, displaying the content you defined earlier.
If you have managed to get it to work then our first test was successful and we can build on this. If not then please read through the above content carefully to make sure that you have not missed any step along the way.
The next step is to create a package.json
file for managing project dependencies. Run the following command:
npm init -y\
This command initializes a package.json
file with default values. If you’d like to customize it, omit the -y
flag and answer the prompts interactively.
A default package.json
file will look like this:
{
"name": "chrome-extension",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Now we can install all the required dependencies into the project. You can use the following commands to do so.
npm install -D typescript
— To install TypeScript and add it to thedevDependencies
section ofpackage.json
npm install -D
@types/chrome
— To install types for Chrome and add it to thedevDependencies
section ofpackage.json
npm install -D webpack
— To install webpack into the project and add it to thedevDependencies
section ofpackage.json
npm install -D webpack-cli
— This is required because we will be doing a hot reload whenever we make a change to the codebasenpm i -D copy-webpack-plugin
— This is required to copy any static assets into the output directory or thedist
directorynpm i -D path
— This is required to resolve the path of the static assets in the webpack configurations later onnpm i -D
@babel/core
@babel/preset-env
babel-loader ts-loader
— This is required to compile the code during the webpack build process
For the next test we will have to make this app work with typescript and webpack. We already have the required dependencies installed. Now we will have to create some configuration files and write some code to get this to work.
We are going to create two configuration files, one for TypeScript and other for webpack. Create a file called as tsconfig.json
with following content.
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
The above is for TypeScript compiler to correctly identify the locations of .ts
files and compile them properly. Per above configurations, the .ts
files are expected to be present under the src
directory or its sub-directories. and the transpiled .js
files will be created in the dist
directory.
Now create webpack.config.cjs
file which will have the configurations required for building and generating the compiled files along with other static assets ex. images
// [webpack.config.cjs]
const path = require("path");
const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
mode: "development", // Use 'production' for production builds
target: "web",
devtool: "source-map",
entry: {
"popup": path.resolve(
__dirname,
"src/scripts/popup.ts"
),
},
output: {
filename: "[name].js", // Output file name for each entry
path: path.resolve(__dirname, "dist"), // Output directory
clean: true, // Clean the output directory before each build
libraryTarget: "umd", // Universal Module Definition
},
resolve: {
extensions: [".ts", ".js"], // Resolve .ts and .js extensions
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
"ts-loader",
],
},
],
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, "popup-style.css"), // Source directory
to: path.resolve(__dirname, "dist"), // Destination directory
},
{
from: path.resolve(__dirname, "content-style.css"), // Source directory
to: path.resolve(__dirname, "dist"), // Destination directory
},
{
from: path.resolve(__dirname, "icon.png"), // Source directory
to: path.resolve(__dirname, "dist"), // Destination directory
},
{
from: path.resolve(__dirname, "popup.html"), // Source directory
to: path.resolve(__dirname, "dist"), // Destination directory
},
{
from: path.resolve(__dirname, "manifest.json"), // Source directory
to: path.resolve(__dirname, "dist"), // Destination directory
},
],
}),
],
}
Note: CopyWebpackPlugin
copies the static assets from the source code to the dist
directory.
Now create the required directories using the following command:
mkdir src/scripts -p\
Inside the script
directory, create a simple Typescript file with the following content,
src/scripts/popup.ts\
(() => {
const message:string = "This is a console statement from the popup script";
console.log(message)
})();
The above code will print the message to the console whenever the popup is rendered. We also have to include the link of this popup.ts
file (which will be compiled to popup.js
file) in popup.html
. Also, create files called popup-style.css
and content-style.css
. We can later use these files for styling the popup and other pages. So lets do that,
Include the links to popup-style.css
and popup.js
in popup.html
that we created earlier
popup.html\
....
<link href="popup-style.css" rel="stylesheet"/>
....
<script src="popup.js"></script>
....
Create popup-style.css
file:
body {
background-color: #d1bba2;
}
Create content-style.css
file:
/* [content-style.css] */
/* just as placeholder */
Now its time to add the webpack related command to the package.json
file so that we can build the extension.
{
....
"script": {
"build": "webpack --watch",
....
}
....
}
Run the following command to start the build process:
npm run build\
This command watches for changes in your files and rebuilds the project automatically.
Quick Test with bare minimum setup
We can do another test now to check if our webpack related configurations are working as expected. But before you try to test, delete the old extension from the first test where you had uploaded the projects root directory to Chrome. Now this time we will have to upload the dist
directory as the compiled code will be in this directory and not the project’s root directory. After you upload the newer version of the extension and render the popup
, right click on the popup and open the dev console and check if you can see the console statement from the popup.ts
. The statement should be present, if not , please check for any mistakes done in the previous steps.
Enhancing the setup
At this point, we have a basic version of the extension which is functional but there are some components which we have to add so that we can do the local development with ease.
Until now we have seen popup
component. Now its time to add the other components. Create three files namely injected.ts
,content.ts\
and background.ts
in the src/scripts
directory with the following content,
src/scripts/injected.ts
(() => {
const message:string = "This is a console statement from the injected script";
console.log(message)
})();
injected.[ts|js]
is a special file which has access to the Dom as well as the JavaScript object exposed by the parent site. We need to add this file to the host site dynamically using the content script.
Create content.ts
file:
src/scripts/content.ts\
(() => {
const message:string = "This is a console statement from the content script";
console.log(message);
const script = document.createElement("script");
script.src = chrome.runtime.getURL('injected.js'); // Load the script from the extension
document.documentElement.appendChild(script);
script.onload = function () {
script.remove(); // Clean up once the script is loaded
};
})();
Create background.ts
file:
src/scripts/background.ts\
(() => {
const message:string = "This is a console statement from the background script";
console.log(message)
})();
Now we will have to update the webpack.config.cjs
file to add these three entries as the entry points.
// [webpack.config.cjs]
.....
{
.....
entry: {
.......
"content": path.resolve(
__dirname,
"src/scripts/content.ts"
),
"background": path.resolve(
__dirname,
"src/scripts/background.ts"
),
"injected": path.resolve(
__dirname,
"src/scripts/injected.ts"
),
},
.....
}
......
As a last step, we will have to update the manifest.json
file to include all these configurations so that Chrome’s environment can detect them.
// [manifest.json]
...
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"css": ["popup-style.css"],
"run_at": "document_start"
},{
"matches": ["https://*.google.com/*"],
"js": ["content.js"],
"run_at": "document_end"
}
],
"permissions": [
"scripting"
],
"host_permissions": [
"https://*.google.com/*"
],
"web_accessible_resources": [{
"resources": ["injected.js"],
"matches": ["https://*.google.com/*"]
}]
....
In the manifest.json
above we configured the extension to work with google.com
meaning this extension will execute its logic only when google.com
is loaded in the browser. You can change this to any other website of your choice.
Quick Test with Enhanced setup
We can do the final testing now with the enhanced setup. Before proceeding, please make sure that you reinstall the app in chrome so that you avoid the issues due to mismatch with older version of the code. To see if all the components are working properly, check the different console.log
statements. Remember that we have put console.log
statements in four different files: injected.ts
, background.ts
, content.ts
and popup.ts
. There should be four messages logged in the console. So, here are the steps,
Stop the
npm run build
command in the terminal if it was running and start it again so that it picks up the new files that we just createdRemove and Install the app again
Open the popup from the extension settings screen, right click on that popup and open the developer console. You should see the messages coming from
background script
andpopup script
in this consoleOpen
https://www.google.com
in the browser in another tabRight click on the opened google site and open developer console. You should be able to see the messages coming from
content script
andinjected script
Congratulations on successfully setting up and running your first Chrome extension! If you encounter any issues, please double-check the steps you’ve followed to ensure everything is in place. Additionally, a link to the GitHub repository is provided at the end of this blog for your reference.
Key Takeaways
In this blog, we have learned how to set up a local development environment for Chrome extension development. Here are a few key takeaways:
We explored the main components involved in Chrome extension development.
We learned how to set up a development environment with TypeScript and Webpack.
We also covered how to set up and test the extension in Chrome.
Glimpse Ahead
I’m currently working on another blog where we’ll explore a simple use case for the Chrome extension, showcasing how all the components of the Chrome development environment we discussed in this blog come together to create a functional extension.
Thank you for taking the time to read this blog! Your interest and time are greatly appreciated. I’m excited to share more as I continue this journey. Happy coding!
GitHub link — https://github.com/gauravnadkarni/chrome-extension-starter-ts
This article was originally published on Medium.
Top comments (0)