--- published: true date: "2023-01-02 23:40" --- # Blast:一個能執行 Raycast 擴充功能的 React.js 渲染器 2023 年新年快樂!新年有什麼一定要做的事呢?對我來說就是挖新坑寫新專案無誤!這次的專案叫做 Blast,就如標題寫的,它是一個「能執行 Raycast 擴充功能的 React.js 渲染器(Renderer)」。 ## Raycast 和 React.js 對於第一次聽到 [Raycast](https://www.raycast.com/) 的朋友,我以前也寫過一篇[推坑文][blog-post],Raycast 是一套 macOS 用的啟動器軟體(Launcher),提供一個關鍵字搜尋的介面,輸入關鍵字,就能快速啟動程式或進行其他操作。過往類似的軟體也不少,最知名的就是 [Alfred](https://www.alfredapp.com/),但 Raycast 最不同的一點,就是可以用 React.js 來寫的擴充功能。這邊我之前的文章也有[簡略帶過][blog-post],大家可以看官方的 [todo-list 範例](https://github.com/raycast/extensions/tree/main/examples/todo-list)感受一下,有點像在寫 React Native,雖然寫的是 JavaScript,但卻能有原生的介面而非網頁。 [blog-post]: https://yukai.dev/blog/2022/05/20/raycast-for-developers React.js 相信近年熟悉前端開發的朋友應該都多少接觸過(斷言),React.js 提供了聲明式(Declarative)的 API 來開發使用者介面(User Interface),而使用者介面可以用階層、樹狀的結構來表示,去看看設計師 Figma/illustrator/Sketch 裡的圖層和群組物件就有感覺了,有的設計工具甚至還提供「輸出 React.js 元件」的功能。 剛提到了「用 React 來實作使用者元件」,React 核心就是一套 Virtual DOM/Diff/Update 的邏輯,無關終端平臺為合,Android、iOS 還是 Desktop,只要你實作 React 提供的介面,什麼平臺都可以寫 React,所以才有了 React Native 等[一眾自定渲染器](https://github.com/chentsulin/awesome-react-renderer)(Custom Renderer)。 Raycast 能夠用 React 來寫擴充功能,也是內嵌(ㄑㄧㄢ,誰這裡念砍我就...)Node.js 並實作渲染器。寫到這,Raycast 和 React 和 Renderer 的關係,大家應該多瞭解一咪咪了吧 XD 下面就來講講我為何決定做這個題目。 ## 動機 七個字:Raycast 我推我超! 從擴充功能 API 上線一年多以來,我已經寫了[十個 Raycast 擴充功能](https://www.raycast.com/Yukai),還因此收到了 Raycast 寄來的[開發者大禮包](https://www.facebook.com/photo.php?fbid=6185229718163261&set=p.6185229718163261&type=3)。Raycast 有著 **Tier 0** 的開發者體驗,雖說頭幾個寫的擴充功能拿來練手成分比較高,但後期開發的擴充功能如 [HackMD](https://www.raycast.com/Yukai/hackmd) ,我自己都天天在用啊,開筆記搜筆記都超快超方便的(棒讀業配環節) 另一個比較黑的理由就是我內心的[平衡人真島大哥](https://lycoris-recoil.com/)在作祟!隨著 Raycast 開發者社群的壯大,截止行文當日(2023/1/2)已上架 742 個擴充功能,雖然擴充功能的開源授權為 MIT,但除了拿來做擴充功能開發做參考外,這些擴充功能也只能跑在 Raycast 的封閉平臺上,除了僅限一家也僅限 macOS 一個平臺。 我就想到 [VSCodium](https://vscodium.com/) 這個專案,雖然 VSCode 也是開放程式碼,但實際上的發佈版,還是包了遙測和一些追蹤碼。欸,全球的**軟黑產業鏈**似乎都動起來了!VSCodium 就是把 VSCode 有疑慮的授權程式碼部分和遙測通通拔掉,重新發佈的「乾淨版」。這樣 484 在臭各種 SaaS 產品啊 XD 至於擴充功能商店的部分當然也不放過,[Open VSX](https://open-vsx.org/) 是一個提供相容 VSCode 編輯器的擴充功能商店。VSCode 至今已經被作為各個開源 IDE/編輯器的基礎元件來使用,比如用 Theia IDE 做的 IDE 們,就是相容 VSCode 擴充功能的 IDE。Open VSX 讓這些支援的編輯器也能免費擼 VSCode 平臺的擴充功能,打不贏就加入! 總之,「**讓 Raycast 的擴充功能被跨平臺並開放地使用**」,就是本專案的目標!Show me what you got! ## 開發 Blast [Blast](https://github.com/Yukaii/blast) 就是基於上述理由所發起的專案。這兩週陸陸續續的開發過程可以分成三個階段: - 架構設計、技術選擇與基本踩雷試錯 - React 渲染器 & 後端開發 - Client App 開發 ### 架構設計、技術選擇與基本踩雷試錯 開發過程和往常一樣,都丟在我的 [GitHub](https://github.com/Yukaii/blast) 上,這次還額外開了 [Project Board](https://github.com/users/Yukaii/projects/4) 來試用。 一開始我就[決定目標](https://github.com/Yukaii/blast/issues/3):要把 Raycast 官方擴充功能範例中 [Todo List](https://github.com/raycast/extensions/tree/main/examples/todo-list) 原汁原味一刀未減的跑起來。寫完這張 Ticket 後,就開始研究 Custom React renderer 該怎麼實作,最後找到 Jam Risser 的 [Building a Custom React Renderer](https://www.youtube.com/watch?v=SXx-CymMjDM),演講內容和程式碼範例都相當清楚,值得參考。 再來就是撞牆階段,一開始問 ChatGPT 用 Rollup 該怎麽設定 package alias,因為原始的 Raycast 擴充功能會用 `import { XXX } from '@raycast/api'`,而 `@raycast/api` 正式要替換為我自己實作的部分。沒想到開始用 Rollup 才是噩夢的開始,因為我要做的是 Application,鐵定會有一堆有的沒的相依套件,但我本來想把寫的程式打包成 ESM,於是在浪費一堆時間後就直接果斷換回熟悉的 Webpack,半小時內搞定 XD 在瘋狂搗鼓 React Renderer 並有了基本理解後,我設計了以下的架構:  明明是個本機跑的 Launcher App 卻還要弄前後端?這裡的設計我很大的參考了早期的 React native。要寫一個自己的 React Renderer,我們會用到 `react-reconciler` 這個套件,並且實現 Host Config,大概會像[這個檔案一樣](https://github.com/Yukaii/blast/blob/b201eded481b8c54b3e3f38e2284a44a86dd0aee/src/reconciler.ts#L39-L77): ```javascript import Reconciler from "react-reconciler"; const MyCustomReactRenderer = Reconciler({ createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) { // .... }, appendChildToContainer(container, child) { // ... } }) ``` Host Config 就是 Reconciler 函式的參數。你會看到一些類似操作 DOM 的方法,例如 `appendChild`,`removeChild` 等,因為那就是實作給 React 內部更新機制:Reconciliation 呼叫的方法。React 經由一系列騷操作,決定要更新的內容時,就會呼叫 Host Config 提供的方法,更新內容到你的目標平臺上。比如 [ReactDOM 就是實作了操作 DOM 的 Host Config](https://github.com/facebook/react/blob/de7d1c90718ea8f4844a2219991f7115ef2bd2c5/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js),React.js 才能將元件渲染到網頁上。 上一段極其簡陋的說明了 React renderer 和 reconciler 的關係。說回來 React Native,在之前的 React Native 版本,Renderer 部分運用 Bridge Service,讓 JS engine 與 Native 部分溝通,你可以想象 React 內部在構建一顆樹狀結構,所以呼叫 `appendChild` 那些方法,Renderer 會把這些操作和參數佇列轉成 JSON 送到 Bridge,讓訂閱 Bridge 的 Native 端也一樣建造一棵樹(Shadow Tree),Native 端再由這顆樹狀的資料結構將原生的元件產生出來。 之前的 React Native 用 JSON Bridge 來溝通,那 Blast 也用這個方法就好?但 React Native 傳送的是建造樹的操作過程(operation),在 Native 端還要實作各個方法,讓 Native 端也能依照操作建立樹結構。不過我沒有甚麼效能要求,直接透過 JSON 序列化傳送整顆樹過去還比較快,使用者端甚至還可以用 React.js 直接把樹渲染,反正 React 會幫我處裡 Diffing/Re-render,我只要負責把 React Component Tree 建出來就好啦!**Use React Everywhere**!**Learn Once Write Everywhere**!諾貝爾獎是我的啦!好耶!!!努力!未來!A Beautiful Star! 以上就是在摸索中慢慢確立的 Blast 架構,剩下來的就是:實作!實作!實作! ### React 渲染器 & 後端開發 老實說這部分在主架構實作完後就沒遇到太多困難,都是在填肉。比較值得一提的是我用了 [`rpc-websocket`](https://github.com/elpheria/rpc-websockets) 這套雙向 RPC 溝通的 Library。 為何需要雙向?在上面提到的 Blast 架構中,前端需要拿到後端建立的 Component Tree(client request server),後端在更新時也需要通知前端(server request client),前端有事件需要觸發時,也需要通知後端(client request server),所以請求其實是雙向的。RPC library 會幫你處理好 function parameter serialization/deserialization/dynamic event registration 等麻煩事,所以直接找一套現有的還是最快。謝謝你,開源超人。 ### Client App 開發 最後就是麻煩但挑戰性相對本專案後端低的前端部分。本來想試一下 [Tauri](https://tauri.app/) 還是 [Wails](https://wails.io/) 的,但查了一下 menubar API 的部分還是那個萬惡 JS World 的 Electron 支援比較完整及簡單,反正就是層 Web 皮嘛,就還是用了 Electron(逃) 下面的動圖是 Blast 前端的 DevTool,可以看到目前渲染的 JSON Component Tree 長怎麼樣,也可以送 `rpc` 的事件給後端。  元件部分,除了 Tailwind/TypeScript/React.js 基本三套組外,還用了之前被分享到 Raycast Slack 社群的 [cmdk](https://github.com/pacocoursey/cmdk),他也是深深受到了 Raycast API 的影響,看看他 API 長那什麼樣子就知道。cmdk 還實作了 Raycast **完全に一致** 的主題,所以我就直接搬過來用了,哈,哈。 ## 做到可以 Demo 的程度啦:Todo List Demo 欸,你以為這張要放在最前面嗎,放這麼下面就是要騙你捲到最後啦! 以下的錄影,就展示了在 Blast 使用 Raycast Todo list 擴充功能的 Create/Read/Delete 等操作。除了擴充功能本身一行未改,就連[圖示也是原汁原味](https://icon.ray.so/),滿足感之高,值得在半夜吶喊大吼吵醒各位室友們!!  雖然 Demo Driven Development/Talk DD/Blog DD 的推力只能讓我實作到這,但還是想慢慢把剩下來的 Raycast API 弄完啊,如果看我幾週後都沒 commit,那大概就是這樣了 XD(哪樣) 除了我實作的 [Blast](https://github.com/Yukaii/blast) 之外,最近還看到兩個啟動器專案,彷彿都受到了 Raycast 的感召,下面簡單介紹一下: ### [Sunbeam Launcher](https://github.com/sunbeamlauncher/sunbeam) 作者本人也是 Raycast 擴充功能的貢獻者之一,我也是看到他在 Slack 社群裡分享的。Raycast heavily inspired,從專案名稱就看的出來 XD Sunbeam 的擴充功能就是輸出 JSON 格式的 [Shell Script](https://github.com/sunbeamlauncher/sunbeam/tree/main/examples),所以可以用各種語言來寫,或是編譯成執行檔,反正 output 出來就沒你的事了。Sunbeam cli 會提供類似 Raycast 的 TUI 當做使用者介面來用,夠 Geek!夠帥! [真 GUI 部分](https://github.com/sunbeamlauncher/sunbeam-gui),目前實作是直接拿 xterm.js 跑 sunbeam cli 的 TUI 來當 UI,直到昨天都還在 commit 而已,讓我們期待後續發展。 ### [Script kit](https://www.scriptkit.com/) 開源 & MIT Licensed,同樣基於萬惡 Electron,JavaScript 生態系。原本看名稱以為是只支援到 Script 粒度,看了 [API](https://github.com/johnlindquist/kit/blob/main/API.md) 發現能做的事還不少,Launcher 界明日之星! --- 說著說著我們 Launcher 光譜圖都可以畫~~滑~~起來了,從前端選擇、跨平臺與否、生態圈 API 開放程度以及技術選擇,做產品總是個大坑啊,二ㄏ、二ㄏ ## 鳴謝 謝謝 [ChatGPT](https://chat.openai.com/chat),在我寫扣~~無聊的時候陪我聊天~~,在我懶的 Google 的時候幫助~~坑~~我。 謝謝 [Copilot](https://github.com/features/copilot),在我懶的打字的時候幫助我成為更好的 Tab 鍵工程師。 謝謝[孤獨搖滾](https://bocchi.rocks/),在我累的時候,心中的虹太陽燃起了我的動力。 本篇文章還沒有使用 AI 校稿,但是我的 [GitHub README](https://github.com/Yukaii/blast) 有 ChatGPT 幫忙潤稿。
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.