Get started with Chrome extensions development using webpack, TypeScript, Sass, and more.
Announcements
Nothing to see here yet.
Features
Chrome Extension Webpack is a simple boilerplate for fast extension development. It helps writing modern TypeScript code with SCSS support. It is meant to be lightweight and scalable, hence easily adaptable to your needs.
It features:
If you need React support, please check this awesome boilerplate created by Michael Xieyang Liu: chrome-extension-boilerplate-react.
Getting started
Installing and running
- Clone the repository
- Run
npm install
- Run
npm run start
for development mode,npm run build
for production build - Add the extension to Chrome:
- Go to
chrome://extensions/
- Enable the
Developer mode
- Click on
Load unpacked
- Choose the
dist
directory
- Go to
- You are good to go! You can also pin the extension to the toolbar for easy access.
Project structure
All TypeScript files are placed in src
directory. There are few files already prepared for you:
contentScript.ts
- the content script to be run in the context of selected web pagesserviceWorker.ts
- the background script usually used to initialize the extension and monitor eventsstorage.ts
- little helper utility to easily manage the extension's storage. In this particular project we are using synced storage areapopup.ts
andoptions.ts
- per-page scripts
Style files are placed in styles
directory. There you can find per-page stylesheets and common.scss
with stylings common across the pages.
We also use Normalize.css so your extensions look good and consistent wherever they are installed.
The static
directory includes all the files to be copied over to the final build. It consists of manifest.json
defining our extension, .html
pages and icon set.
The test
directory contains your tests. See the dedicated section below for some more information on this topic.
Pages
Currently, there are two pages: popup.html
and options.html
, which can be found in static
directory. Both have corresponding script and style files at src
and styles
directories accordingly.
Popup
It's a default extension's page, visible after clicking on extension's icon in toolbar. According to the documentation:
The popup cannot be smaller than 25x25 and cannot be larger than 800x600.
Read more here.
Options
Options page shown by right-clicking the extension icon in the toolbar and selecting Options.
There are two available types of options pages: full page
and embedded
. By default it is set to full page
. You can change that behaviour in the manifest.json
:
"open_in_tab": true // For `full page`
"open_in_tab": false // For `embedded`
Read more here.
Storage
I have prepared a bunch of helper functions to simplify storage usage:
function getStorageData(): Promise<Storage> {...}
// Example usage
const storageData = await getStorageData();
console.log(storageData);
function setStorageData(data: Storage): Promise<void> {...}
// Example usage
const newStorageData = { visible: true };
await setStorageData(newStorageData);
function getStorageItem<Key extends keyof Storage>(
key: Key,
): Promise<Storage[Key]> {...}
// Example usage
const isVisible = await getStorageItem('visible');
console.log(isVisible);
function setStorageItem<Key extends keyof Storage>(
key: Key,
value: Storage[Key],
): Promise<void> {...}
// Example usage
await setStorageItem('visible', true);
async function initializeStorageWithDefaults(defaults: Storage) {...}
// If `visible` property is already set in the storage, it won't be replaced.
// This function might be used in `onInstalled` event in service worker
// to set default storage values on extension's initialization.
const defaultStorageData = { visible: false };
await initializeStorageWithDefaults(defaultStorageData);
All of the above functions use Storage
interface which guarantees type safety. In the above use-case scenario, it could be declared as:
interface Storage {
visible: boolean;
}
IMPORTANT! Don't forget to change the interface according to your needs.
Check src/storage.ts
for implementation details.
Content scripts
Content scripts are files that run in the context of web pages. They live in an isolated world (private execution environment), so they do not conflict with the page or other extensions' content sripts.
The content script can be declared statically or programmatically injected.
Static declaration (match patterns)
Statically declared scripts are registered in the manifest file under the "content_scripts"
field. They all must specify corresponding match patterns. In this boilerplate, the content script will be injected under all URLs by default. You can change that behaviour in manifest.json
file.
You can edit the default content script at src/contentScript.ts
.
Programmatic injection
You can also inject the scripts programmatically. It might come in handy when you want to inject the script only in response to certain events. You also need to set extra permissions in manifest file. Read more about programmatic injection here.
Adding new content script
To add a new content script, create a new script file in src
directory. You also need to create a new entry in the webpack config file - webpack.common.js
:
entry: {
serviceWorker: './src/serviceWorker.ts',
contentScript: './src/contentScript.ts',
popup: './src/popup.ts',
options: './src/options.ts',
// New entry down here
myNewContentScript: './src/myNewContentScript.ts',
},
In case of static declaration, you might also need to modify the manifest file.
Service worker (old background pages)
If you are coming from Manifest V2, you might want to read this page first: Migrating from background pages to service workers.
As per docs:
Extensions are event-based programs used to modify or enhance the Chrome browsing experience. Events are browser triggers, such as navigating to a new page, removing a bookmark, or closing a tab. Extensions monitor these events using scripts in their background service worker, which then react with specified instructions.
The most common event you will listen to is chrome.runtime.onInstalled
:
chrome.runtime.onInstalled.addListener(async () => {
// Here goes everything you want to execute after extension initialization
console.log('Extension successfully installed!');
});
It is also the perfect (and the only) place to create a context menu.
You can edit the service worker at src/serviceWorker.ts
.
Read more about service workers here.
Tests
The boilerplate comes with a test suite using mocha, and coverage tracking using c8.
Some basic tests that test the interaction with the chrome storage API have already been implemented to get you started. You can run the test suite using npm test
.
Testing the chrome API is especially interesting, as it is not available in the test environment. To get around this, you can use the sinon-chrome
package to mock the API, some examples of this have been pre-implemented and can be found in test/setup.js
. This setup file will run before the other tests.
The test.yml
file in .github/workflows
contains a GitHub Actions workflow that will run the test suite on every PR against the main
branch and every push to the main
branch.