iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Modern Web

一個活了六年的 Vue 2 後台,升級到 Vue 3 的那兩年系列 第 6

Day 6|官方要 monorepo,我們把 93% 的程式碼留在根目錄

  • 分享至 

  • xImage
  •  

模組二|選型、語言遷移與地基(Day 6–11)

官方文件說主應用要放在 apps/ 底下。我們的主應用有 829 個檔案,全部待在根目錄的 src/

這不是還沒做完,是刻意的。今天講為什麼。

先講結論:我們沒有從零搭,而是用了一套現成的開源 admin 框架(Vue 3 生態裡這類專案不少,挑法後面講)。

但這篇不是要推薦它。我想講的是選型當下真正在權衡什麼,以及一個更有意思的問題:選了之後,你必須全盤照收嗎?

第一個問題很基本:Vue 3 的生態這麼成熟,為什麼不自己組一套?

自己搭要處理的清單

先把「自己組一套」實際要做的事列出來:

登入頁與 token 流程、整體版面(側邊選單 + 頂部列)、多頁籤、動態路由、權限判斷、主題切換、多語系、請求層封裝、環境變數與多環境建置、程式碼風格規則、提交訊息規範……

這串東西有一個共同點:它們跟你的業務價值一點關係都沒有。

沒有人會因為你的多頁籤實作得很優雅而多付一毛錢。但每一項你都得做,而且做不好會每天扎你。

所以選現成的後台底座,本質上是買時間。問題只在於:買哪一個、以及要接受它多少。

三個判準

https://ithelp.ithome.com.tw/upload/images/20260811/20183479AOZS4FrsJq.png

我推上去的這個齒,齒距剛好對得上;地上那個對不上的,我沒有再試第二次

我後來回頭整理,覺得選底座應該看三件事:

一、社群還活著嗎? 這個最容易查:最近一次更新是什麼時候、issue 有沒有人回、有沒有跟上框架的大版本。一個兩年沒更新的底座,等於你接手了一份別人的技術債。

二、我能只拿一部分嗎? 有些底座是「全有全無」的:你想用它的版面,就得連它的狀態管理、請求層、甚至它的目錄結構一起吞下去。可拆解度越高,你之後後悔的成本越低。

三、它的心智模型跟舊系統接得上嗎?

第三點最關鍵,而且最容易被忽略。這也是我們最後那個選擇的主因。

我們的舊系統是後端驅動的動態路由:使用者登入後,後端回傳一份選單清單,前端拿到之後才即時產生對應的路由。權限也是同一套資料決定的。

這個設計有它的道理(權限調整不用改前端、不用重新部署),而且六年下來,整個公司的權限管理流程都是繞著它建立的,後台有人在維護那份選單、有 SOP、有測試流程。

我們選的那套框架原生就支援「後端回傳選單、前端動態產生路由」,所以搬過去的時候,權限的心智模型完全不用改(實作細節怎麼搬,Day 17 會講)。

反過來想就知道這件事多重要:如果選了一個假設「路由寫死在前端」的底座,那要換的就不只是程式碼,是整個公司的權限管理流程,後台維護介面、SOP、測試腳本全部要重做。那已經遠遠超出前端專案的範圍了。

所以第三點的白話版是:選底座不是在選功能清單,是在選「你願不願意讓公司的既有流程遷就它」

但我們沒有照抄它的官方架構

選定之後,還有第二個問題:要接受它多少?

它的新版官方推薦完整的 monorepo 架構。

先解釋一下 monorepo 是什麼:

假設你們公司有三個前端專案。

一般做法(多 repo):三個 repo,各自獨立。共用的工具函式?各自複製一份。

monorepo:一個 repo,裡面有三個資料夾放三個專案,再加一個資料夾放共用的東西。三個專案指向同一份共用程式碼,改一次三邊都生效。

官方的建議結構大概是這樣:各個應用放在 apps/ 底下、共用的能力層抽進 packages/

而我們沒有這樣做。

我們的實際結構是:

專案根目錄/
├── src/            ← 主應用整包在這裡(829 個檔案)
├── apps/
│   ├── portal-view/    ← 幾乎是空的
│   └── test-server/    ← 一個小型測試伺服器
├── packages/       ← 2 個共用套件
├── internal/       ← 4 個建置與規範設定
└── vite.config.ts  ← 打包的是根目錄的 src/

換句話說:monorepo 的殼留著,但主應用沒有搬進 apps/,還是待在根目錄的 src/

數字很直白:根目錄 src/ 有 829 個檔案,而 apps/ + packages/ + internal/ 加起來只有 63 個。93% 的程式碼都在那個「不該在」的地方

https://ithelp.ithome.com.tw/upload/images/20260811/20183479jNPWQzjaRK.png

比例長條圖:根目錄 src/ 佔undefined個檔案共 93%,apps/ packages/ internal/ 合計undefined個佔 7%

右邊那一小條就是整個 monorepo 骨架實際裝的東西。我把這張圖放進來不是為了自嘲,是因為它精準地畫出了本篇的代價:目錄結構會對新人說謊。看到 apps/packages/ 的人,合理地會以為程式碼在那裡。

為什麼刻意不搬

這件事我一開始覺得是「還沒做完」,後來才理解是刻意的。

從舊系統搬過來的是一個大型單體應用。如果在遷移的同時,還要把它拆成符合官方結構的多個 app,你會同時處理兩件事:

  1. 業務邏輯從 Vue 2 搬到 Vue 3(本來就夠難了)
  2. 檔案位置大搬家,順便調整幾百個 import 路徑

而這兩件事出錯的時候長得一模一樣:畫面壞掉、找不到模組。你會分不出來是搬邏輯搬錯了,還是路徑改錯了。

Day 5 講過我們刻意讓計畫「隨時可以停」。同樣的邏輯在這裡:在最不穩定的階段,不要增加額外的變因。

用整修餐廳的比喻繼續講:

你要換廚房設備(Vue 2 → Vue 3),這已經是大工程了。
這時候有人說:「順便把廚房移到二樓吧,那樣動線比較好。」

動線確實比較好。但你會搞不清楚上菜慢是因為新爐具不熟,還是因為樓梯。

那我們抽了什麼

雖然沒搬主應用,但我們確實抽出了六個共用套件。它們有個明顯的共同特徵:

套件 內容 跟業務有關嗎
建置設定 打包設定、外掛、環境處理
ESLint 設定 程式碼風格規則
Stylelint 設定 樣式風格規則
TypeScript 設定 型別編譯設定
共用 hooks 請求快取、防抖、節流、重試、輪詢
共用型別 通用型別定義

六個全部跟業務無關。

這不是巧合。這正是 Day 3 那個發現的延伸:當時我比對兩個舊 repo,發現 20 個一字不差的檔案,而它們全部都是跟業務無關的基礎建設。

換句話說:六年的實際使用行為,早就把「哪些東西該共用」的答案標好了。我們不需要猜,只要照著抄。

這個決定的性質是:先拿走便宜的好處,貴的留到有把握再做。 抽建置設定的風險趨近於零;抽業務元件的風險極高(Day 29 會專門講為什麼)。

但這裡有個我必須誠實說的地方

https://ithelp.ithome.com.tw/upload/images/20260811/20183479P2iPAqLuCe.png

我把散了一地的東西掃攏了,結果還是兩堆,而且兩堆已經不太一樣

寫這篇的時候,我做了一件事:我去比對了兩個新專案的那六個共用套件。

結果是這樣:

套件 兩個專案的差異
共用 hooks 0 行
共用型別 0 行
TypeScript 設定 0 行
ESLint 設定 5 行
Stylelint 設定 5 行
建置設定 13 行

看出問題了嗎?

這六個套件,兩個專案各有一份。

monorepo 讓「同一個 repo 內」共用了,但兩個 repo 之間還是各自維護一份。而且三個已經開始出現差異了,就跟 Day 3 那個路由守衛一模一樣。

我當下的反應是:這不就是六年前那個問題嗎?

嚴格說起來,是的。只是規模不同:

舊系統 新系統
兩邊同路徑的檔案 172 個 6 個套件
完全相同 20 個 3 個
已經開始長歪 33 個 3 個

從 172 個檔案縮到 6 個套件,這是巨大的進步。但性質沒有改變:它還是「兩份,而且正在變得不一樣」

所以要精確地說:monorepo 解決的是「一個 repo 內的重複」,它完全沒有解決「跨 repo 的重複」

用公寓來比喻的話:

舊系統像兩棟獨棟房子,各自有水塔、各自有電箱,什麼都要維護兩份。

新系統是蓋了兩棟公寓。每棟公寓裡的住戶共用電梯和水塔了,這確實省了很多事。

但兩棟公寓之間,還是各有一套水塔。

那為什麼不乾脆把兩個專案合成一個 repo?這個問題很好,而且我們到現在也沒做。理由、判斷準則、以及我認為的時機點,留到 Day 29。

代價

不照抄官方架構,有兩個實際成本:

一、專案結構跟官方文件對不上。 新人照著官方文件找檔案會找不到:文件說在 apps/某個應用/src/router,我們的在根目錄 src/router。這代表你必須自己寫一份架構說明,而且要維護它。你等於放棄了「官方文件也是我們的文件」這個好處。

二、之後如果想搬回官方結構,成本只會更高。 我們每多寫一個月的程式碼,那個「之後再搬」的選項就更貴一點。老實說我不確定我們還會不會搬,「暫時這樣」在軟體專案裡通常等於「永遠這樣」,這點要有心理準備。

帶走什麼

  1. 選底座是在選「未來兩年你要跟誰的設計品味共處」,不是在選功能清單。最重要的判準是「它的心智模型跟你的現況接不接得上」。
  2. 接受一個框架的抽象,是有級距的。 你可以只拿一部分。先拿走風險最低的(設定、規則、工具),高風險的等有把握再說。
  3. 哪些東西該共用,不用猜,去看歷史。 那些多年來兩邊都沒改過的檔案,就是答案。
  4. 要誠實面對「沒解決的問題」。 我們的 monorepo 縮小了重複的規模,但沒有消除它。承認這件事,比宣稱「我們已經有共用層了」有用得多,因為只有承認了,才會有人記得要繼續往下做。

明天 Day 7 進入這個模組真正的重頭戲:Vue 2 到 Vue 3,this 消失之後。我會講為什麼我們沒有用自動轉換工具,以及那個檔名拼錯了六年、沒人敢改的 mixin。


上一篇
Day 5|「重構要多久?」這個問題有兩個相差三倍的答案
下一篇
Day 7|一個檔名拼錯六年的 mixin,和 `this` 消失的意義
系列文
一個活了六年的 Vue 2 後台,升級到 Vue 3 的那兩年14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言