Recently, I wanted to participate in an open-source project for a click-to-search Japanese word feature in the Moji dictionary. It's more convenient to use than the official version, but I found that it hasn't been maintained for a long time. Strangely, the RSS feed of Xlog cannot be combined with AI summaries, so now I have to fill in the word count.
- Project address: https://github.com/Yukaii/mojidict-helper
- Chrome Store: https://chrome.google.com/webstore/detail/mojidict-helper/eknkjedaohafedihakaobhjfaabelkem
This article only demonstrates the use of Vite/Webpack+React in the Chrome browser:
You can enter chrome://extensions/
in the address bar of the Chrome browser to view all the installed plugins.
Also, please make sure that the developer mode is enabled in the upper right corner:
💻 Development Environment#
General#
IDE: Visual Studio Code
React: v18+
Vite: v3+
Vite: v4.2.0
Webpack: v5.76.2
I use pnpm
to manage all dependencies on my local machine. If you don't have it or use another package manager, you can replace pnpm
with the corresponding command!
Other tools#
- TailwindCSS
- TypeScript
📦 Module Bundler#
Native Development (VanillaJS)#
You can directly refer to the demos provided by the official documentation:
Webpack#
Vite (Recommended)#
How to use this library? After cloning, install the package dependencies with pnpm install
, and then start the development mode:
pnpm dev
: This is the development mode
pnpm build
: This is the production mode
The code generated by the above commands will be in the build directory, so you just need to load this directory in the chrome://extension
directory to develop and see the results at the same time:
📑 Official Documentation#
https://developer.chrome.com/docs/extensions/
👟 Prerequisites#
Lifecycle#
- onInstalled: Triggered when the extension is installed, updated, or reloaded.
- onSuspend: Triggered when the extension is about to be suspended.
- onSuspendCanceled: Triggered when the extension's suspension is canceled.
- onUpdateAvailable: Triggered when the extension can be updated (can be used to notify the user).
- onStartup: Triggered when Chrome starts up and loads the extension.
- onConnect event: Triggered when the extension establishes a connection with another part of Chrome (such as a content script).
- onConnectExternal: Triggered when a connection is established from an external application (such as a native application).
- onMessage: Triggered when a message is received from another part of Chrome (such as a content script).
How to use it in the code?
chrome.runtime.XX
XX is the name of one of the lifecycles mentioned above, for example:
chrome.runtime.onInstalled.addListener((details) => {
console.log('Extension installed:', details.reason);
});
Extension Configuration manifest.json#
There are many differences between manifest v3 and the previous version, manifest v2. One of the important differences is that the direct modification mechanism of manifest v2 has been replaced by service workers. In summary, when it is not needed, the service worker will be selectively blocked by Chrome, but we can still manipulate the DOM through other means.
Detailed explanation: https://developer.chrome.com/docs/extensions/mv3/service_workers/
A simple manifest configuration file looks like this:
{
"manifest_version": 3,
"name": "Life Helper",
"version": "1.0.0",
"description": "The things we must know in our life.",
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"action": {
"default_popup": "popup.html",
"default_title": "Popup"
},
"permissions": [
"scripting",
"bookmarks"
],
"background": {
"service_worker": "background.js"
},
"options_page": "options.html"
}
Please refer to the format of
manifest.json
: https://developer.chrome.com/docs/extensions/mv3/manifest/
We only need to understand the following:
-
Permissions: The enabled permissions correspond to the APIs that can be called in the background.js file. For example, if you have the "bookmarks" permission, it means you can use the methods under chrome.runtime.
-
Actions: Mainly specify the position and name of the popup window.
- Popup: The page that pops up when the plugin icon is clicked.
- Popup: The page that pops up when the plugin icon is clicked.
-
Background: Corresponds to the carrier object of the service worker, which can be understood as the backend/server of the plugin.
-
ContentScript: The position of the frontend script. Here, it directly corresponds to the entry point of the React program (app.tsx -> index.tsx), and then all the logic, styles (popup, options, newTab), etc. can be organized according to the structure of React.
Development Process#
The division of labor for each module is as follows:
In short, all the work previously handled by the backend is now handled by the background
module, and all the work previously handled by the frontend is now handled by the contentScript
responsible for options
, newTab
, and popup
.
This also means that when you want to do state management, you can also follow the principle of atomicity to organize the respective states (M), styles (V), types (M), etc.
Communication#
There is no specific direction for communication here, and there are no restrictions on files, modules, etc. Use it as needed.
But based on the approximate scope of work defined earlier, when communicating, it is best for the frontend to communicate about styles, API calls, and user events, while the backend should prioritize organizing data, building APIs, or executing external requests.
The basic format is as follows:
// Send a message and callback
chrome.runtime.sendMessage({greeting: "hello"})
.then(response => console.log(response.farewell))
.catch(error => console.error(error));
// Receive a message and callback
chrome.runtime.onMessage.addListener(function(request, sender) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello") {
return Promise.resolve({farewell: "goodbye"});
}
});
Message sending and receiving can filter by tab ID and specify the type. For example, if we want to send a message from the popup to the background and specify the currently active tab:
Global state management and plugin data storage#
Use Redux Toolkit!
Today, the native Redux is no longer recommended, and the new Redux Toolkit can also help us better integrate existing modules and divide the work, similar to the usage of pinia
:
First, initialize the overall state and build the services according to the division of labor. It is best to do this in the backend (i.e., the background module).
// store.ts
import { configureStore } from '@reduxjs/toolkit';
import { slice1, slice2 } from './slices';
export default configureStore({
reducer: {
slice1: slice1.reducer,
slice2: slice2.reducer,
},
});
export default store;
Define each slice as follows:
import { createSlice } from '@reduxjs/toolkit';
// Define the initial value
const initialState1 = {
value: 0,
};
// Define the first slice
export const slice1 = createSlice({
name: 'slice1',
initialState: initialState1,
reducers: {
increment1: state => {
state.value += 1;
},
decrement1: state => {
state.value -= 1;
},
},
});
export default slice1.reducer;
Combine with chrome.storage to ensure persistent state#
You need to install two libraries, redux-persist-storage-chrome
and redux-persist
, and make the following changes:
In store.ts
, make the following changes:
import { configureStore } from '@reduxjs/toolkit'
import { chromeStorage } from 'redux-persist-storage-chrome';
import { persistReducer } from 'redux-persist';
import { slice1, slice2 } from './slices';
const persistConfig = {
key: 'chrome-extension-extended',
storage: chromeStorage,
}
export const store = configureStore({
reducer: {
slice1: persistReducer(persistConfig, slice1.reducer),
slice2: persistReducer(persistConfig, slice2.reducer),
},
})
export default store;
Debugging#
If you are using HMR or other forms of hot reloading, you should only be concerned with the messages sent by the background or contentScript itself.
- Monitoring the background script:
Click on the service worker in the plugin program on the chrome://extensions/
page, and a separate debugging window will pop up.
- Monitoring options, popup, or new tab pages:
Right-click on the corresponding page and select Inspect
, and a separate debugging window will pop up.
Summary#
This article provides a quick guide for those who have never worked with browser extensions before. I'm also a beginner, but fortunately, the official documentation provides many samples, and there are also many tutorials and boilerplates available online to get started.
Coming Soon#
In the next article, I will try to explore the combination of rollup and ws in the chrome-extension-boilerplate-react-vite project. (This is no longer within the scope of extension development.)
In the next article, I will explore the use of other browser extensions and other boilerplate templates with Vue 3.
(Updated on 08 May:)
antfu's browser extension template is very useful. However, it does not work on Firefox because Firefox's support for manifestV3 is still unstable. Firefox browser does not support manifestV3.
(Updated on 01 June:)
Diy.God recommended the Plasmo SDK for browser extensions (used to help integrate resources and package the extension). I tried it and found it very useful. Plasmo can cover all the functions mentioned in the above boilerplate templates. Highly recommended!
🧩 References#
https://dev.to/anobjectisa/how-to-build-a-chrome-extension-new-manifest-v3-5edk