將多個前端專案整合至 Monorepo 架構
WHY: 為什麼需要這次優化/重構?
背景: 隨著公司業務發展,我們的前端專案數量逐漸增加。初期我們有一個持續維護的 React 內部後台 (Dashboard),接著因應展場活動需求,開發了一個以行動裝置操作為主的簡易購物車。同時,還有數個新的 React 專案正在規劃中。
挑戰: 在多專案並行的情況下,我們面臨了幾個顯著的痛點:
-
程式碼複用困難: 許多共用的元件或是商業邏輯需要在不同專案間複製貼上,無法有效複用與同步更新。
-
流程與環境不一致: 每個專案都有獨立的開發環境、依賴版本和部署流程,不僅增加了維護成本,也讓新成員的上手時間變長。
-
設計規範難以統一: 即使設計師已經制定了全域的 Design Guideline,但在分散的專案中,導致 Design Guideline 的更新需花費大量時間在每個專案做調整。
目標: 為了解決上述問題,考慮到 codebase 的擴展性以及將來的維護難易度,我提出了將所有前端專案整合成一個 Monorepo 的計畫。
HOW: 是如何分析並執行這次任務的?
作為這個計畫的主要負責人與發起人,從前期的推動、溝通,到後期的技術選型與實作,都由我主導,其中大致上的過程如下:
推動與溝通: 初期最大的挑戰來自於溝通。在正式導入前我與主管已經有過多次的討論需不需要導入 Monorepo,但是結果都是目前還沒有急迫性。但當團隊即將開啟第三個基於 Next.js 的專案時,這個整合的急迫性變得更加明顯,最終成功說服主管並獲得支持。在正式動工前,我還為團隊舉辦了一場技術分享會,說明 Monorepo 的概念與它能為我們團隊帶來的價值。
技術選型與問題解決: 在導入過程中,我們主要面臨三大技術挑戰:
-
處理版本衝突: 舊的 Dashboard 專案使用了與新版 React 不相容的函式庫。
-
整合不同打包工具: 我們有多種專案類型,包含 CRA (Webpack)、Next.js,需要一個能整合不同打包流程的方案。
-
比較 Monorepo 工具: 主要在 Turborepo 與 Nx 之間進行評估。
經過深入研究與實測,我們最終選擇了 Turborepo。主要原因如下:
-
對比 Nx: Nx 功能非常強大完整,但對當時的我們來說,許多功能是「可有可無」。更關鍵的是,Nx 對於 library 的 hoisting 策略會強制將所有依賴提升至根目錄的
node_modules,這使我們無法為舊專案保留特定的舊版 React。 -
Turborepo 的優勢: Turborepo 完美解決了我們的版本衝突問題。它允許我們透過在 workspace 中設定
.npmrc,為特定的專案(如舊的 Dashboard)建立獨立的node_modules,使其能繼續使用較低版本的 React,而其他新專案則共享根目錄的最新版 React。此外,Turborepo 能靈活地呼叫各個專案自身的打包指令,完美兼容我們既有的 CRA 與 Next.js 打包流程。
WHAT: 最終達成了什麼具體成果?
-
成功導入 Monorepo 架構: 將三個獨立的前端專案(包含 CRA、Next.js)成功整合至由 Turborepo 管理的 Monorepo 中,建立了一個可擴展、易維護的前端基礎設施。
-
建立共享代碼庫 (Shared Codebase): 我們建立了一個 library 資料夾,用來存放所有專案共用的 UI 元件、Hooks 與工具函數,大幅減少了重複的程式碼。
-
標準化開發流程: 統一了程式碼風格、測試與部署腳本。未來新專案的啟動時間從原本的數天縮短至數小時,部署方面也因只需要將舊有流程稍微調整即可支援新的專案。
-
提升依賴管理效率: 全面導入 pnpm 作為套件管理器,有效利用其磁碟連結 (symlinks) 機制,大幅提升了依賴安裝速度並顯著節省了磁碟空間。
總結與學習 (Bonus Reflection)
這次經驗讓我收穫最深刻的是,一項成功的技術導入,其前期的溝通與準備,重要性完全不亞於技術實作本身。
-
理由要充分: 要推動一項技術改革,必須清晰地闡述「現行做法為何不行」,並證明「新做法的好處遠大於轉換成本」,才能有效說服決策者與團隊。
-
Trade-off: 沒有完美的技術或工具。在做決策時,必須綜合考量時程、產品現況、團隊習慣、社群支援度等多重因素,做出當下最合適的選擇。
-
時機的重要性: 有時候一個好的提案在當下被拒絕,可能只是時機不對。當團隊或業務發展到某個新的階段,問題變得更加迫切時,當初的議題可能再次浮現,而那時或許就是一個更適合改革的時機。