往日星尘

Celestial Echoes of Yesteryears

瀏覽器插件開發簡易指南0

最近想參與一個開源項目,用於 moji 詞典的點擊查詢日語單詞,比官方的用得順手一些。用著還不錯但發現年久失修。奇怪的是 xlog 的 rss 不能和 ai 總結結合在一起,這下要湊下字數了。

本篇只展示 Vite/Webpack+React 在 Chrome 瀏覽器中的使用:

你可以在 Chrome 瀏覽器地址欄鍵入chrome://extensions/來查看你現在安裝的所有插件,

另外請保證右上角的開發者模式是打開的:

image

💻 開發環境#

一般#

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目錄載入一下這個目錄就能邊開發邊看結果了:

image

📑官方文檔#

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 點擊插件圖標彈出的頁面
      image
  • background 對應 service_worker 的承載對象,可以理解為插件後端 / 服務端

  • contentScript 前端腳本的位置,這裡直接對應 react 的程序入口 (app.tsx->index.tsx),之後所有邏輯 & 樣式 (popup, options, newTab) 都參照 react 的組織形式即可

開發流程#

各模塊的分工如下:

image

簡單來說,所有之前交由後端的工作都由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-chromeredux-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,會彈出單獨的調試窗口

image

  • 監視 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

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。