Service Worker 背景腳本實戰

在 Manifest V2 時代,擴充套件有一個名為 background.htmlbackground.js 的東西,它是一個隱形的網頁,只要你的擴充套件被啟用,它就會一直存在於背景消耗記憶體。這導致如果使用者裝了幾十個套件,瀏覽器就會卡到爆炸。

在 Manifest V3 中,Google 強制把常駐的 Background Pages 砍掉,換成了 Service Workers

1. Service Worker 是什麼?

你可以把 Service Worker 想像成一個「隨叫隨到、用完即丟」的打工仔。

  • 當有事件發生時(例如使用者點擊圖示、或是定時器響起),瀏覽器會「喚醒」Service Worker 來執行任務。
  • 任務執行完畢,或閒置超過幾秒鐘後,瀏覽器就會把它「殺掉 (Terminated)」以釋放記憶體。

[!CAUTION] 最大雷區:全域變數會消失! 因為 Service Worker 會被隨機關閉和重啟,你絕對不能在裡面依賴全域變數來儲存重要狀態。

// ❌ 絕對不要這樣做!變數 count 在 worker 被殺掉後就會歸零。
let count = 0;
chrome.action.onClicked.addListener(() => {
  count++;
});

正確的做法是使用 chrome.storage API 來把狀態存進硬碟裡。

2. 註冊 Service Worker

要使用 Service Worker,我們必須先在 manifest.json 中註冊它:

{
  "manifest_version": 3,
  "name": "My Service Worker App",
  "version": "1.0",
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "permissions": [
    "storage",
    "alarms"
  ]
}

注意我們加入了 type: "module",這允許我們在 background.js 中使用 ES6 的 import / export 語法,這對大型專案的程式碼管理非常有幫助。

3. 實戰:建立一個番茄鐘定時器 (Alarms API)

因為 Service Worker 不能使用標準的 setTimeoutsetInterval(如果 worker 進入休眠,計時器就會停止運作),我們必須使用 Chrome 專屬的 chrome.alarms API。

讓我們來寫一個簡單的番茄鐘邏輯在 background.js 中:

// 監聽擴充套件安裝或更新事件
chrome.runtime.onInstalled.addListener(() => {
  console.log("擴充套件已安裝,初始化設定...");
  // 預設番茄鐘時間為 25 分鐘
  chrome.storage.local.set({ timerDuration: 25 });
});

// 監聽來自 Popup 或是 Content Script 的訊息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'startTimer') {
    // 建立一個 Alarm
    chrome.alarms.create('pomodoroTimer', { delayInMinutes: request.minutes });
    console.log(`已設定 ${request.minutes} 分鐘的定時器!`);
    sendResponse({ status: "success" });
  }
});

// 監聽 Alarm 響起的事件
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'pomodoroTimer') {
    // 時間到了!觸發通知
    console.log("時間到!該休息了!");
    
    // 如果你有申請 "notifications" 權限,可以在這裡跳出系統通知
    chrome.notifications.create({
      type: "basic",
      iconUrl: "icons/icon128.png",
      title: "番茄鐘完成",
      message: "辛苦了!站起來活動一下吧!"
    });
  }
});

4. 生命週期與事件驅動設計

開發 V3 Service Worker 最核心的心法就是「事件驅動 (Event-driven)」。 你的 background.js 應該從頭到尾都由 chrome.xxx.addListener() 包裝起來。

常見的事件監聽器包含:

  • chrome.runtime.onInstalled: 套件剛裝好時執行(適合做初始化資料庫、顯示 onboarding 網頁)。
  • chrome.tabs.onUpdated: 當使用者切換分頁或網頁載入完成時觸發(適合用來判斷是否要在特定網址啟動功能)。
  • chrome.action.onClicked: 當使用者點擊右上角的套件圖示時觸發(注意:如果你的 manifest 有設定 default_popup,這個事件將不會觸發,因為點擊行為已經被 popup 攔截了)。

5. Fetch API 與非同步處理

Service Worker 也是你與後端資料庫(如 Supabase)或外部 API 通訊的最佳場所。因為它不會被跨網域 (CORS) 限制得太死(只要你在 manifest 中有宣告)。

// 在 background.js 中呼叫外部 API
async function fetchUserData(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    const data = await response.json();
    
    // 將抓回來的資料存進 storage 供 popup.js 讀取
    await chrome.storage.local.set({ userData: data });
    return data;
  } catch (error) {
    console.error("API 請求失敗:", error);
  }
}

總結

Service Worker 是 Manifest V3 的靈魂。學會適應它隨機死亡的特性,善用 chrome.storage 保存狀態,並運用事件驅動來寫程式,你就能打造出極致流暢、不卡頓的擴充套件。

下一章,我們將介紹擴充套件的「手和腳」—— Content Scripts,教你如何直接竄改使用者正在看的網頁畫面!

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!