最近想參與一個開源項目,用於 moji 詞典的點擊查詢日語單詞,比官方的用得順手一些。用著還不錯但發現年久失修。奇怪的是 xlog 的 rss 不能和 ai 總結結合在一起,這下要湊下字數了。
- 項目地址: https://github.com/Yukaii/mojidict-helper
- Chrome 商店: https://chrome.google.com/webstore/detail/mojidict-helper/eknkjedaohafedihakaobhjfaabelkem
本篇只展示 Vite/Webpack+React 在 Chrome 瀏覽器中的使用:
你可以在 Chrome 瀏覽器地址欄鍵入chrome://extensions/
來查看你現在安裝的所有插件,
另外請保證右上角的開發者模式是打開的:
💻 開發環境#
一般#
IDE: Visual Studio Code
React: v18+
Vite: v3+
Vite: v4.2.0
Webpack: v5.76.2
我使用了pnpm
來管理本機的所有依賴,如果沒有或者使用其他包管理工具的可以自行替換pnpm
為對應命令!
其他配合食用的#
- TailwindCSS
- TypeScript
📦打包工具 (Module Bundler)#
原生開發 (VanillaJS)#
直接參考官方給的這些 demo 即可:
Webpack#
Vite (推薦)#
如何使用這個庫?clone 後安裝包依賴pnpm install
,然後啟動開發模式:
pnpm dev
: 這是開發模式
pnpm build
: 這是生產模式
以上命令生成的代碼都會在 build 目錄下,因此只需要到chrome://extension
目錄載入一下這個目錄就能邊開發邊看結果了:
📑官方文檔#
https://developer.chrome.com/docs/extensions/
👟前置準備#
生命週期#
- onInstalled: 安裝、更新或重新加載插件觸發
- onSuspend: 插件即將被掛起觸發
- onSuspendCanceled: 插件被掛起取消時觸發
- onUpdateAvailable:插件可更新時(可以用於提醒用戶)觸發
- onStartup: Chrome 啟動並加載擴展時觸發
- onConnect 事件: 插件與 Chrome 的另一部分(例如內容腳本)建立連接時,將觸發
- onConnectExternal:當來自外部應用程序(例如本機應用程序)的連接建立時觸發
- onMessage: 當從 Chrome 的另一部分(例如內容腳本)接收到消息時,將觸發 onMessage 事件
如何在代碼中使用它?
chrome.runtime.XX
XX 為上述對應的一個生命週期名稱,如:
chrome.runtime.onInstalled.addListener((details) => {
console.log('Extension installed:', details.reason);
});
插件配置 manifest.json#
從這點就有很多和 manifest v2 版本很不同的地方,比較重要的是 manifest v2 的直接修改 dom 機制被替換成 service worker,大意是不用它的時候該 service worker 會被 chrome 選擇性屏蔽,但我們仍然可以通過其他方式操縱 dom。
詳細解釋: https://developer.chrome.com/docs/extensions/mv3/service_workers/
一個不太複雜的 manifest 配置文件長這樣
{
"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"
}
manifest.json
的格式請:https://developer.chrome.com/docs/extensions/mv3/manifest/
我們需要理解的只有
-
permissions 開啟的權限就對應了你在 background.js 能夠調用的 api,如
bookmarks
說明你可以用 chrome.runtime 下面的方法 -
actions 主要規定彈出層窗口的位置名稱等
- popup 點擊插件圖標彈出的頁面
- popup 點擊插件圖標彈出的頁面
-
background 對應 service_worker 的承載對象,可以理解為插件後端 / 服務端
-
contentScript 前端腳本的位置,這裡直接對應 react 的程序入口 (app.tsx->index.tsx),之後所有邏輯 & 樣式 (popup, options, newTab) 都參照 react 的組織形式即可
開發流程#
各模塊的分工如下:
簡單來說,所有之前交由後端的工作都由background
來完成,所有之前交由前端的都由contentScript
負責的options
, newTab
, popup
來完成。
這也意味著,當你想做狀態管理時,也能很好地遵循原子性原則組織各自的狀態(M)和樣式(V)、類型(M)等。
通信#
通信在這裡並沒有明確的方向性,甚至也沒有文件、模塊等限制,需要就用。
但根據我們先前規定各模塊大致的工作範疇,作為前端在通訊時最好也是來溝通各組件的樣式、API 調用、用戶事件,而後端在通訊時優先考慮更好地組織數據、構建 API 或執行外部請求。
基本格式如下:
// 發送消息,並回調
chrome.runtime.sendMessage({greeting: "hello"})
.then(response => console.log(response.farewell))
.catch(error => console.error(error));
// 接收消息,並回調
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"});
}
});
消息發送和接受可以篩選 tab id,並指定類型,假設我們想從 popup 往 background 發送消息,並指定當前活動標籤頁:
全局狀態管理和插件數據存儲#
快使用 Redux Toolkit!
時至今日,原生的 Redux 不再被推薦,新的 Redux Toolkit 也可以幫助我們更好結合現有模塊作出分工,有類似pinia
的用法:
我們首先初始化整體的,根據先前我們的分工,這裡最好在後端(即 background 模塊)來完成初始化和服務的構建。
//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;
對於各自的 slice,定義如下:
import { createSlice } from '@reduxjs/toolkit';
// 定義初始值
const initialState1 = {
value: 0,
};
// 定義第一個slice
export const slice1 = createSlice({
name: 'slice1',
initialState: initialState1,
reducers: {
increment1: state => {
state.value += 1;
},
decrement1: state => {
state.value -= 1;
},
},
});
export default slice1.reducer;
和 chrome.storage 結合保證狀態持久#
這裡需要安裝兩個庫redux-persist-storage-chrome
和redux-persist
,然後做如下改動:
在store.ts
中改動如下:
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;
調試#
如果你使用了 HMR 或者其他形式的熱重載, 應該要關心的就只是 background 或 contentScript 本身發出的消息。
- 監視 background 腳本的方法:
在chrome://extensions/
頁的插件程序直接點擊 service worker,會彈出單獨的調試窗口
- 監視 options, popup 或者 new tab 等頁面:
直接在對應的頁面右鍵->inspect
,會彈出單獨的調試窗口
總結#
這篇給完全沒接觸過瀏覽器插件的小夥伴做一個快速指南,其實我也是剛入門,不過好在官方給了很多 sample,網上也有大量的現成教程和 boilderplates 來上手。
預告#
下篇會嘗試研究 chrome-extension-boilerplate-react-vite 項目的 rollup 和 ws 結合過程。(這個已經不屬於插件開發的範疇了)
下篇會研究其他瀏覽器的插件與拓展其他 boilderplate 在 Vue3 上的使用。
(05/08 更新:)
antfu 的瀏覽器 extension 模板很好用。但在 firefox 上行不通,問題在於 web-ext 且Firefox 瀏覽器對 manifestV3 的支持仍不穩定。
(06/01 更新:)
經評論區 Diy.God 推薦了瀏覽器拓展專用的 SDK (用來幫助整合資源和打包的引擎,這樣前端寫寫寫就好了)嘗試下發現很好用,plasmo 。可以覆蓋上面提到所有 boilerplate template 的功能 le,推薦!
🧩參考#
https://dev.to/anobjectisa/how-to-build-a-chrome-extension-new-manifest-v3-5edk