這是一場舒服的 Vibe Coding體驗文章,記錄我如何使用 GitHub Pages + Vibe Coding 打造一個免費的圖床服務的過程。
自從 imgurl 封鎖了台灣的 IP 之後,我一直想弄一個自己的圖床,最好是免費的方案,畢竟這個圖床主要是給我自己的部落格使用。而我不想他只是單純的圖床,希望他有更通用的用途,因此我打算打造一個檔案託管服務,可以上傳各種檔案,並且可以產生短網址來分享這些檔案。
有鑑於之前使用 GitHub Pages 打造短網址系統的成功經驗,又使用 Vibe Coding 的方式讓這個 s.poychang.net 短網址系統有了稍微好看一點的使用介面,因此我就想如法炮製,使用 GitHub Pages + Vibe Coding 來從 0 開始打造檔案託管服務。
這個檔案託管服務僅限我自己使用,雖然程式碼是開放的,但網站需要我的 GitHub PAT 權杖才能運作,因此不會開放給其他人使用。
身分驗證
一開始我還只是在探索,探索我可以怎麼規畫這個檔案託管的架構,雖然我已經訂下目標使用 GitHub Pages 這個免費的靜態網站服務,但我還是想要一點權限的功能,畢竟不想讓所有人都可以上傳檔案,因此最後決定使用 GitHub 的 Personal Access Token (PAT) 來進行簡單的身份驗證。
在這個最後決定之前,我嘗試了很多種方式,像是使用 GitHub OAuth App 或 GitHub App 的方式來進行身份驗證,但這樣的流程都需要至少一個薄後端才有辦法使用,不論是要自己架設一個後端服務,或是使用 Azure Functions 這種無伺服器的服務來處理 OAuth 的流程,甚至直接使用 Auth0 這個服務,對於我這種只想要一個簡單檔案託管服務的人來說,實在是太麻煩了。
雖然上面的方法都是可行的路,甚至是更加安全且標準的方式,但還是期望這個服務能完全以靜態網站的方式來運作,因此最後決定使用 PAT 來簡化流程。
有點資安概念的人應該會想到,PAT 直接放在前端網站中不是一個好主意,因為這樣等於是把密碼公開給所有人看,因此我改變了 PAT 的使用策略,將 PAT 作為進入這個檔案託管服務的鑰匙,也就是說,只有知道 PAT 的人才能使用這個服務。
基本上,不會有人知道這個 PAT,甚至連我自己都不會記得,因為 PAT 產生之後,就再也看不到了。
但總不能每次登入都要去產生一組新的 PAT 吧?因此我決定把 PAT 放在瀏覽器的 Local Storage 中,這樣只要在同一台電腦上使用這個服務,就不需要每次都輸入 PAT。

把 GitHub Repository 當作儲存空間
搞定了身份驗證的部分之後,接下來就是檔案上傳的部分了,GitHub 提供了一個 REST API,可以用來上傳檔案到指定的 Repository 中,因此我就使用這個 API 來實現檔案上傳的功能。
為了探索這個 GitHub API 的使用方式,我在另一個專案中使用了 C# 的方式來進行 POC 開發,當然這過程也是透過 Vibe Coding 的方式來探索。過程中,GitHub Copilot 用到了 Octokit 這個套件,這時我就想著,GitHub 官方不會只對 C# 提供套件,其他語言平台肯定也會有,果不其然,可以在 npm 中找到 octokit.js 的套件,這樣我之後在純靜態的 SPA 架構中,也可以用此套件來省去很多處理 HTTP 請求的操作(雖然也不是我寫)。
在這個階段,我在思考是否要採用 CQRS 命令查詢職責分離模式 (Command Query Responsibility Segregation),將這個檔案託管服務的背後功能用此模式來實現,這樣可以讓系統更容易擴展和維護,但後來我沒有這麼做。
一開始會想到用 CQRS 模式,是因為想讓動作標準化,甚至讓之後的維護能更清晰一些,但後來我感覺在用 Vibe Coding 的方式來開發時,特別是開發 SPA 這類型的應用程式時,是可以讓 GitHub Copilot 一邊寫,我一邊在瀏覽器看成果,過程中持續思索這個應用程式應該要長甚麼樣子。這樣的開發流程讓我覺得舒服,還會想起很多年前 WYSIWYG (What You See Is What You Get) 的概念,只不過現在變成 What AI Code Is What I Get。
因此我沒有特別要求 GitHub Copilot 使用 CQRS 模式,而是讓它自由發揮,寫出我想要的功能。

很快的,第一版的檔案託管服務就完成了,雖然功能還很簡單,但已經可以上傳檔案到指定的 GitHub Repository 中。這裡的很快,是指這個 SPA 從無到有,探索版面設計和撰寫程式碼,以及手動測試的過程,大約花了 2 個小時。
這時候已經可以上線使用了,即便操作上還有些不順手,專案架構也還不夠完善,若要由人做日後的維護,是一場很可怕的挑戰,不過我沒打算後續用人當主力來維護。
持續改進
在第一版完成之後,我開始思考如何讓這個檔案託管服務的介面變得更好用、更直觀,於是我跟 GitHub Copilot 說:請注意!接下來我要大改介面,請仔細思考再改寫…(描述我想像中的介面)。
過程中遇到了一些狀況,改寫的過程中,有時候 GitHub Copilot 會重複寫一些曾經寫過類似功能的程式碼,導致程式碼變得冗長且難以速讀。這時我心裡埋下了一個想法:這個介面改版之後,要來幫這個專案洗個澡,讓臭味道消失,讓冗餘程式碼不見,讓程式碼變乾淨。
先回到介面改版的部分,這個專案使用 Bootstrap 5 來當作前端框架,我感受到如果你能清楚的說出這個前端框架本身就有的元件名稱,或是他的一些術語,例如 grid system、cards、modals 等等,GitHub Copilot 就能更精準的幫你產生出你想要的介面。
如果你沒有辦法清楚的描述這些術語,GitHub Copilot 也會試著幫你完成,結果可能會如預期,但很可能不會是我們期待的預期。
我們都期待 AI 的成果不僅能符合預期,還能超出期待。事實上,以 AI 目前的能力是有機會辦到的,只是他需要你更精準的表達,也因此,我們還是要讀一點書,多學習一些相關的知識,而不是從此就交給 AI 自己不用學習了。
經過幾次的嘗試和調整,終於完成了第二版的介面設計,這次的設計更加符合我的需求,也讓使用體驗變得更好。


大重構
完成第二版介面之後,我開始著手進行程式碼的重構工作,這部分主要是為了讓程式碼更乾淨、更易讀,並且移除一些冗餘的程式碼。
或許有人會問,為甚麼要讓程式碼變乾淨,反正 AI 看得懂就好啦。
在 Vibe Coding 的過程中,我發現一份程式碼很多的檔案,AI 會嘗試拆成片段來理解,像是一次讀取 100 行,然後再讀取下一個 100 行,這樣的方式雖然可以讓 AI 理解程式碼,但過程中是會消耗更多 Token 的,而且也會降低他的執行效率,畢竟他也需要去找、去猜,如果還要先拆才能理解,那自然效率就會降低了。
如果程式碼本身就很乾淨,除了讓 AI 會更容易理解整體的邏輯,進而產生更好的建議。對於人類來說,也能更容易閱讀和維護程式碼,這樣在未來如果真的需要由人去微調或修正的時候,就不會那麼困難。
還有另一個理由,因為 Vibe Coding 就是一種 What AI Code Is What I Get 的過程中,這個過程中我有時候還滿喜歡看他怎麼”寫”,看著程式碼一段一段長出來,有種莫名的療癒感。如果這時候如果程式碼本身就很亂,產生的過程一直跳來跳去,會讓我覺得沒那麼不舒服。
因此我決定請 GitHub Copilot 進行大重構。
以上的內容就是他在大重構的時候寫的。
或許是我沒有先做單元測試或整合測試的關係,導致重構的過程中出現了一些問題,發生有些功能無法正常運作,這時我就需要手動告訴 GitHub Copilot 去修正這些問題,確保所有功能都能正常運作。
絕大部分的問題都出現在 DOM ID 不一致的地方。
個人覺得,在大重構的過程中,AI 很容易對 DOM ID 出現幻覺,特別是因為重構後會將 DOM ID 散落在多個檔案之中,造成不同檔案中的程式對可能要操作的 DOM ID 不一致,導致功能無法正常運作,這部分需要特別注意。
因此在重構的過程中,為了讓 DOM ID 保有一致性,透過建立一支 constants.js 去設定所有的 DOM ID (含有所有的 Custom Event 名稱),確保所有程式碼中使用的 DOM ID 都是參照該程式碼所設定的,避免發生功能失效的問題。
整體而言,大重構的過程中,雖然花了一些時間,但最終讓程式碼變得更乾淨、更易讀,也讓整個專案的維護變得更容易。
後記
用 AI(Artificial Intelligence)寫程式碼已經快到一個境界了,在明確知道用途和介面的狀況下,AI 可以很快的幫你產生出你想要的程式碼,但這次的過程中,我認為 HI(Human Intelligence,人類智慧)依然是不可或缺的,特別是要知道你想呈現的架構和技術概念在哪裡。
HI 對技術界線的掌握,對整體架構的布局,對細節的把關,這些都是 AI 尚無法完全取代的部分,因為我們是要做出符合我們心中期待的應用程式,而不是產生一個黑盒子,一個只有 I (input) 與 O (output) 的系統。
在整個開發過程中,AI 的輔助讓我能夠更專注於高層次的設計和邏輯思考,而不必過於糾結於細節實現上。這樣的合作模式讓我感受到,AI 不僅僅是工具,更是開發過程中的夥伴。