Wu-Hsien Yu/ 文章
文章

kiho - 開發日誌 01

2026.07.13kihopluginindie

這是 kiho 的第一篇開發日誌。

kiho 是利用業餘時間做的一個瀏覽器擴充套件,目的是幫助我一邊閱讀、一邊學習英文。 而實際上,kiho 也已經發佈了第一版到 Chrome Web Store,因此這第一篇日誌更像是到目前為止的回顧。

除了每天使用 kiho 學習英文之外,也根據自己的回饋持續改版升級。 但因為一些發佈上正在等待的問題,以及一些其他歷史原因,讓我想開始這一系列的開發日誌來記錄一些細節、進度。 包括:怎麼會有它、怎麼到今天,甚至之後可能要往哪裡去,讓之後的回顧可以有清楚的脈絡。

我很喜歡寫這些紀錄 ... 生活上的、工作上的、雜七雜八的瑣事,包括我自己的無知,希望自己能在 AI 趨勢下繼續保持書寫的能力,並享受這個過程。如果剛好能夠幫助到你,那就太好了。

或者你發現了錯誤,也歡迎隨時聯絡我,可以教我一些正確的方式。

由來

kiho 取自我的母語,台灣的台語用語:「kì-hō」(記號) - 一個清楚標示、讓你日後能一眼認出的記號,每個你查過的字都在頁面上留下一個記號,下次再回到同一頁,記號就在那裡。

其實在 kiho 之前,有另一個相同目的的專案:LinguaLens

  • Lingua:拉丁文,意為「語言」
  • Lens:透鏡、鏡頭

合起來就是 - 透過一層語言的鏡片去看網頁。

那是一個依照官方教學打造的第一個擴充套件,也是我最早的原型。 但是一些原因 ( 更多是自己的無知 ) 最終沒有持續下去。

問題

我的母語不是英語,對於英語也沒有特別的熟悉與道地。但因為工作與興趣的關係,每天都會需要經常瀏覽英語的網站、資料、文件以及相關的影片等等。遇到不會的單字、片語字詞,在過去的流程裡大概就是:

  • 選擇單字、片語字詞
  • Chrome Google 翻譯、貼到 Claude Chat 裡,請 AI 分析、解釋
  • 複製到筆記裡、做整理
  • 切回原文,重新找回剛剛讀到哪

但不管是透過什麼工具,這個流程一直有問題,因為:

  • 上下文切換打斷了閱讀。
  • 字詞記錄在筆記裡,會需要回到筆記裡查找。
  • 經常重複筆記相同的字詞。
  • 查過的字散落在一堆 AI 對話裡,什麼都累積不下來。
  • 幾週後重讀同一篇文章時,根本不記得哪些字已經查過。
  • 更多時候,需要查詢的字詞需要包含上下文,才能讓 AI 提供更準確的解釋。
  • Google 翻譯的品質通常都不太符合當下的文意。
  • 我要的不是另一個全文翻譯。
  • 需要手動將這些字詞建立到 Anki 等工具裡,以便安排複習時間。

而且,下次再瀏覽同一個頁面時,有很大機率會重複一次流程 (因為我又忘了上次的字詞)。

所以,我需要一個符合自己的方式來收斂這個流程。

選擇

先把目前的技術選擇都列出來:

我是一個後端工程師,對於前端的技術選型並沒有非常的深入,所以如果硬要說為什麼這麼選擇,大概是:

  • 不希望在不同的瀏覽器之間重複建立擴充,當然 Safari 應該是不大可能。
  • 我跟 Claude 一起協作,需要一個 Claude 最熟悉的技術。
  • 我理解一些 Tailwind CSS,也希望採用 shadcn/ui 的設計,因為真的很好看。

Shadow DOM

回顧第一個功能,也是自己最無知的部分。

kiho 會把 UI 畫在正在閱讀的網頁上面 - 劃選字詞時,會浮出一個小小的「查詢」按鈕,還有一張顯示解釋的 hover 卡片。

天真的我,認為只要在擴充裡捕捉這個事件,並接著將擴充的 UI 畫在網頁上就好了。

但實際上遭遇到的是 - 反覆調整 UI 依舊無法達到預期的結果。這也是第一版擴充:LinguaLens 失敗的主要原因。 後來才理解到,注入 UI 最明顯的風險就是 CSS。每個網頁有它自己的樣式,有些頁面能把精心做的小 tooltip 弄成一團糟。反方向也一樣重要 - 因為我不會希望 kiho 的 CSS 外洩出去、把別人的文章排版弄得亂七八糟。

標準解法是 Shadow DOM:把 UI 渲染在一個 shadow root 裡,它會封裝樣式,讓網頁原本的樣式進不來、我的樣式也不會洩漏出去。WXT 剛好給了一個 helper 做這件事 - createShadowRootUi,kiho 用的就是它。

樣式就這樣隔離了。

Font

為了讓自己看這些解釋字詞時,眼睛可以相對舒適,所以我選擇了兩個 web font:

  • Inter - 用於顯示 UI 元件的字型
  • Source Serif 4 - 閱讀文字

但不知道為何,一直沒有被正確地載入,UI 一直使用系統的字型,沒有任何錯誤或是警告。

後來才理解到,宣告在 shadow root 裡面的 @font-face 規則,會被 Chrome 忽略。 字型載入是對著 document 解析的,不是對著 shadow tree,所以一個只活在 shadow root stylesheet 裡的 @font-face 永遠不會被註冊。

標準解法是:把字型宣告在 document.head 上,再從 shadow root 裡面用字型家族名稱去引用它。

除此之外,是 MV3-specific 的問題。

@font-face 規則裡的 src 不能像一般網頁那樣直接指向字型檔,page context 碰不到擴充的 bundled URL:

  • vendored 那些字型檔住在擴充裡 origin:chrome-extension://<extension-id>/fonts/...
  • 但 @font-face 的 CSS 是掛在網頁的 document 上,例如 https://example.com。瀏覽器要載入 src: url(...) 時,是 站在 page context 去解析,不是站在擴充的情境。

那要如何取得?需要透過 browser.runtime.getURL(...) 解析,而且 URL 要能從頁面載入,字型檔就必須在 manifest 裡被宣告成 web_accessible_resources

ts
// wxt.config.ts
web_accessible_resources: [
  { resources: ['fonts/*.woff2'], matches: ['*://*/*'] },
],

接著,在每個頁面把字型 face 註冊到 document 層,指向 web-accessible:

ts
const interUrl = browser.runtime.getURL('/fonts/inter-latin.woff2');
const style = document.createElement('style');

style.id = 'kiho-overlay-fonts';
style.textContent = `
  @font-face {
    font-family: 'Inter Variable';
    font-weight: 100 900;
    font-display: swap;
    src: url('${interUrl}') format('woff2-variations');
  }`;

(document.head ?? document.documentElement).appendChild(style);

持久化

雖然在理解 Shadow DOM、Overlay、Font 或是 MV3-specific 時花了很多時間,也一直在鬼打牆。但真正難的並不是在這些地方。因為這些是屬於開發一個擴充功能需要理解的部分。

kiho 的主要功能,也是我認為值得做的事,是 持久化

一個能夠跨造訪而留下來的標記、記號,不會因為這次關閉瀏覽器、頁面後,下次再次開啟相同頁面時,就失去當初學習的記號。 又因為這些記號不會不見,就可以加上重複複習的能力,讓整個學習流程完整。

當然,無知的我再次認為,只要存到 storage 就可以了。

但是當標記了一個字後,kiho 必須有辦法在下次打開這一頁時,重新找到同樣那一段文字,只是:

  • 這一頁可能已經變了。
  • DOM 位移了。
  • 這個站是會隨著導覽重新渲染的 single-page。
  • 內容是後來才載入的。
  • 文字躲在另一個 shadow root 裡。

要在這些狀況下都能穩定地重新找到那一段,才是 kiho 真正的核心,也是真正解決我自己的痛點。因此,我的基本原則就是:

寧可不要顯示,也不要標錯

一個安靜沒有出現的標記,可能只是一點小失望;但一個接錯位置的標記 - 底線畫到錯的片語、把上週的筆記顯示在一個根本不屬於它的句子上時,就跟我自己在筆記裡重複紀錄相同的字詞是一樣的結果:混亂。所以每一層都被刻意做成寧可回傳找不到,也不去用猜的。

Claude Code

不懂前端工程的我,主要開發的方式是透過與 Claude Code 協作。透過 Claude Code,我可以快速地獲得解決問題的建議,並且在需要時進行調整。

我建立了一個小團隊:Orchestrator、Engineer、QA 以及 Reviewer。他們各司其職,並遵循一套規範:Karpathy 風格的品質與安全審查、刻意的 harness 設計、有限的迴圈上限、主動升級問題,以及把每一次的經驗與過程留存到文件裡。

作為後端工程師,在這個過程裡我不再扮演親手寫程式的角色,我轉移到寫 Spec、判斷與決策,並且對那些我完全沒有搞懂的部分負責。 就像上面提到的:Shadow DOM、Font、MV3 等等,我不需要事先知道它們,但我需要認得出那個修法是對的、值得留下來。

結語

kiho 的目標是成為一個能夠跨造訪而留下來的學習工具,讓學習過程更加順暢、完整。

這是第一篇開發日誌,因此跳過了一些主題 - 複習流程、繁體中文(台灣用語)規則、安全性等等,期許自己會在這系列日誌裡,逐一地記錄下來。