TypeScript 一體化:Monorepo 的挑戰與收益
在本文中,我不會比較 monorepo 和 polyrepo,因為這完全是關於哲學。相反,我會專注於構建和進化的經驗,並假設你已經熟悉 JS/TS 生態系統。
介紹
我一直夢想著 monorepo。
在我為 Airbnb 工作時,我看到了 monorepo 的方法,但只限於前端。出於對 JavaScript 生態系統和“愉快”TypeScript 開發體驗的深愛,大約三年前,我開始將前端和後端代碼統一成同一語言。這對於招聘來說很棒,但在開發方面就不那麼棒了,因為我們的項目仍舊散落在多個 repo 中。
俗話說:「重構項目的最佳方法是重新開始一個」。所以當我在大約一年前創立我的初創公司時,我決定採用全面的 monorepo 策略:將前端和後端項目,甚至是數據庫架構放到一個 repo 中。
在這篇文章中,我不會比較 monorepo 和 polyrepo,因為這完全是關於哲學。相反,我會專注於構建和進化的經驗,並假設你已經熟悉 JS/TS 生態系統。
最終結果在 GitHub 上可用。
為什麼選擇 TypeScript?
坦白說,我是 JavaScript 和 TypeScript 的粉絲。我喜歡它的靈活性和嚴謹性的兼容性:你可以退回到 unknown
或 any
(雖然我們在代碼庫中禁止任何形式的 any
),或者使用超嚴格的 lint 規則集來統一團隊的代碼風格。
當我們之前談論“全棧”概念時,我們通常會想象至少兩個生態系統和編程語言:一個用於前端,一個用於後端。
有一天,我突然意識到它可以更簡單:Node.js 快得足夠(相信我,在大多數情況下,代碼質量比運行速度更重要),TypeScript 足夠成熟(在大型前端項目中運行良好),而 monorepo 的概念已被許多知名團隊(React、Babel 等)實踐——那麼為什麼不把所有代碼從前端到後端結合在一起呢?這可以讓工程師在一個 repo 中完成工作而不需要上下文切換,並(幾乎)在一種語言中實現完整功能 。
選擇包管理器
作為一名開發者,習慣性地,我迫不及待地想要開始編碼。但這次,事情有所不同。
包管理器的選擇對 monorepo 中的開發體驗至關重要。
惰性的痛苦
那是 2021 年 7 月。我從 [email protected]
開始,因為我已經使用它很久了。Yarn 很快,但我很快遇到了 Yarn Workspaces 的幾個問題。例如,未正確提升依賴,以及大量標記為「fixed in modern」的問題,這些都將我重定向到 v2(berry)。
「好吧,我現在就升級。」我停止在 v1 上掙扎,並開始遷移。但 berry 的長遷移指南嚇到了我,在經歷了數次失敗的嘗試後,我放棄了。
它就這麼運行了
於是包管理器的研究開始了。在試用了 pnpm
後,我被它吸引:它像 yarn 一樣快,本地支持 monorepo,命令類似於 npm
,硬鏈接等。最重要的是,它就這麼運行了。作為一名開發者,我想要的是開始一個產品,而不是開發包管理器,我只是想添加一些依賴並開始項目,而不需要了解包管理器如何運作或任何其他花哨的概念。
基於同樣的想法,我們選擇了一個老朋友 lerna
來在包之間執行命令和發布工作空間包。
定義包範圍
一開始很難明確搞清每個包的最終範圍。根據現狀,儘量從最佳嘗試開始,記住你可以隨時在開發過程中進行重構。
我們的初始結構包含四個包:
core
:後端綜合服務。phrases
:i18n 鍵 → 短語資源。schemas
:數據庫和共享 TypeScript 架構。ui
:與core
交互的網頁 SPA。
全棧技術棧
由於我們正在擁抱 JavaScript 生態系統並使用 TypeScript 作為我們的主要編程語言,很多選擇就很直接了(根據我的喜好😊):
koajs
作為後端服務(核心):在express
中使用async/await
的經驗不太好,因此我決定使用具有本地支持的工具。i18next/react-i18next
用於 i18n(短語/界面):喜歡其簡單的 API 和良好的 TypeScript 支持。react
用於 SPA(界面):只是個人喜好。
那麼架構呢?
這裡仍然缺少一些東西:數據庫系統和架構 <-> TypeScript 定義映射。