Volume Hero 是我某個週日下午做的。一個瀏覽器擴充功能,可以把音訊和視訊音量提升至 600%,並記憶每個網站的設定。聽起來很簡單,實作不是。
為什麼擴充功能有趣
瀏覽器擴充功能在軟體堆疊中佔據一個獨特的位置。它們是網頁程式碼,卻對頁面、瀏覽器 API,甚至作業系統有更高的存取權限。它們像應用程式一樣發布——使用者安裝、持續運行、到處都能用。
大多數開發者忽視擴充功能作為一種創作媒介。這是個錯誤。
2025 年擴充功能開發的現狀
三年前,做一個跨瀏覽器的擴充功能很麻煩。Manifest V2 vs V3 的差異、browser vs chrome API 前綴、每個瀏覽器各自不同的工具鏈。大多數擴充功能樣板都很老舊。
然後 WXT 出現了。這是一個專為瀏覽器擴充功能設計的框架,處理了:
- 預設使用 Manifest V3(可降級至 V2)
- 開發時的熱重載
- 單一程式碼庫交叉編譯到多個瀏覽器
- TypeScript 開箱即用
- 基於檔案的進入點自動發現
這就是 Vite 對網頁應用做的事,但換成了擴充功能。讓開發體驗真的變得愉快。
在 Popup 裡用 SolidJS
擴充功能的 UI 我用了 SolidJS。Popup 需要小、快、響應式——一個拖動時即時更新的滑桿。SolidJS 在這種情境下非常適合,它的細粒度響應性意味著零 VDOM 額外開銷。滑桿的更新感覺是即時的。
棘手的部分不是 UI——是 Web Audio API。
Web Audio API 很厲害
要把音量提升超過 100%,不能只是設定 element.volume = 2.0,那個上限是 1.0。必須透過 GainNode 路由音訊:
const ctx = new AudioContext()const source = ctx.createMediaElementSource(element)const gain = ctx.createGain()gain.gain.value = 3.0 // 300%source.connect(gain)gain.connect(ctx.destination)這對 HTML5 音訊和視訊元素有效。問題在於:一旦你把元素路由到 AudioContext,它就跟那個 context 綁在一起了。如果 context 被暫停(瀏覽器分頁隱藏、自動播放政策),音訊就會中斷。
正確處理這件事——偵測暫停、恢復 context、避免重複路由元素——才是大部分實際工作所在。
每個網站記憶設定
我最驕傲的功能是每個網站記憶設定。到 YouTube,調到 200%,離開。明天回來——還是 200%。
這其實就是一個以網域為鍵的 chrome.storage.sync 呼叫。簡單,但 UX 的差異是巨大的。它把擴充功能從手動控制變成了會學習你偏好的東西。
我會做什麼不同的事
我會更早搞清楚 Content Script 的隔離問題。擴充功能在多個環境中運行——背景 Service Worker、Popup、Content Script——它們之間的訊息傳遞是 bug 的溫床。從一開始就把架構弄對能省很多麻煩。
我也會寫測試。擴充功能測試很煩(需要 mock 瀏覽器 API),但音訊 context 管理的邏輯真的需要覆蓋率。
結語
如果你每天在瀏覽器裡手動解決某個問題,考慮做一個擴充功能吧。工具鏈比大多數人想的更成熟。WXT + SolidJS + TypeScript 是一個出乎意料扎實的技術組合。
Volume Hero 是開源的,在 github.com/sinhong2011/volume-hero 可以看源碼。