前端代碼 Monorepos 指南
已發表: 2022-03-11Monorepos 是一個熱門話題。 最近有很多關於為什麼應該和不應該在項目中使用這種類型的架構的文章,但其中大多數都以某種方式存在偏見。 本系列試圖收集和解釋盡可能多的信息,以了解如何以及何時使用 monorepos。
Monorepository是一個架構概念,它基本上包含了其標題中的所有含義。 您無需管理多個存儲庫,而是將所有隔離的代碼部分保存在一個存儲庫中。 請記住隔離這個詞——這意味著 monorepo 與單體應用程序沒有任何共同之處。 您可以在一個 repo 中保存多種邏輯應用程序; 例如,一個網站及其 iOS 應用程序。
這個概念比較古老,大約在十年前就出現了。 谷歌是最早採用這種方法來管理其代碼庫的公司之一。 你可能會問,如果它已經存在了十年,那為什麼到現在才這麼火? 大多數情況下,在過去的 5-6 年中,許多事情都發生了巨大的變化。 ES6、SCSS 預處理器、任務管理器、npm 等——如今,要維護一個基於 React 的小型應用程序,您必須處理項目捆綁程序、測試套件、CI/CD 腳本、Docker 配置,還有誰知道還有什麼。 現在想像一下,您需要維護一個由許多功能區域組成的巨大平台,而不是一個小應用程序。 如果您正在考慮架構,您將需要做兩件主要的事情:分離關注點並避免代碼重複。
為了實現這一點,您可能希望將大型功能隔離到一些包中,然後通過主應用程序中的單個入口點使用它們。 但是你如何管理這些包? 每個包都必須有自己的工作流環境配置,這意味著每次你想創建一個新包時,你都必須配置一個新環境,複製所有配置文件等等。 或者,例如,如果您必須更改構建系統中的某些內容,您將不得不檢查每個 repo、進行提交、創建拉取請求並等待每個構建,這會大大減慢您的速度。 在這一步,我們遇到了 monorepos。
與其擁有大量帶有自己配置的存儲庫,我們將只有一個事實來源——monorepo:一個測試套件運行器、一個 Docker 配置文件和一個 Webpack 配置。 而且您仍然擁有可擴展性、分離關注點的機會、與通用包共享代碼以及許多其他優點。 聽起來不錯,對吧? 嗯,是的。 但也有一些缺點。 讓我們仔細看看在野外使用 monorepo 的確切利弊。
Monorepo 優勢:
- 一個存儲所有配置和測試的地方。 由於所有內容都位於一個存儲庫中,因此您可以配置一次 CI/CD 和捆綁器,然後只需重新使用配置來構建所有包,然後再將它們發佈到遠程。 單元測試、e2e 和集成測試也是如此——您的 CI 將能夠啟動所有測試,而無需處理額外的配置。
- 使用原子提交輕鬆重構全局功能。 無需為每個 repo 執行拉取請求,找出構建更改的順序,您只需要發出一個原子拉取請求,其中將包含與您正在處理的功能相關的所有提交。
- 簡化包發布。 如果您計劃在依賴於另一個具有共享代碼的包的包中實現新功能,則可以使用單個命令來完成。 這是一個需要一些額外配置的功能,稍後將在本文的工具評論部分討論。 目前,有豐富的工具可供選擇,包括 Lerna、Yarn Workspaces 和 Bazel。
- 更容易的依賴管理。 只有一個package.json 。 每當您想更新依賴項時,都無需在每個 repo 中重新安裝依賴項。
- 通過共享包重用代碼,同時保持它們的隔離。 Monorepo 允許您從其他包中重用您的包,同時保持它們彼此隔離。 您可以使用對遠程包的引用並通過單個入口點使用它們。 要使用本地版本,您可以使用本地符號鏈接。 此功能可以通過 bash 腳本或引入一些其他工具(如 Lerna 或 Yarn)來實現。
Monorepo的缺點:
- 無法限制僅訪問應用程序的某些部分。 不幸的是,你不能隻共享你的 monorepo 的一部分——你必須授予對整個代碼庫的訪問權限,這可能會導致一些安全問題。
處理大型項目時 Git 性能不佳。 這個問題開始只出現在具有超過一百萬次提交和數百名開發人員每天在同一個 repo 上同時工作的大型應用程序上。 這變得特別麻煩,因為 Git 使用有向無環圖 (DAG) 來表示項目的歷史。 隨著大量提交,任何遍歷圖表的命令都可能隨著歷史的加深而變慢。 由於引用的數量(即,分支或標籤,可通過刪除不再需要的引用來解決)和跟踪的文件數量(以及它們的重量,儘管可以使用解決重文件問題),性能也會降低Git LFS)。
注意:如今,Facebook 試圖通過修補 Mercurial 來解決 VCS 可擴展性問題,而且可能很快,這不會是一個大問題。
- 更高的構建時間。 因為您將在一個地方擁有大量源代碼,所以您的 CI 將需要更多時間來運行所有內容以批准每個 PR。
工具審查
用於管理 monorepos 的工具集在不斷增長,目前,很容易迷失在 monorepos 的各種構建系統中。 通過使用此存儲庫,您始終可以了解流行的解決方案。 但是現在,讓我們快速瀏覽一下現在大量使用 JavaScript 的工具:
- Bazel 是 Google 的面向 monorepo 的構建系統。 更多關於 Bazel:awesome-bazel
- Yarn 是一個 JavaScript 依賴管理工具,通過工作空間支持 monorepos。
- Lerna 是一個基於 Yarn 構建的用於管理具有多個包的 JavaScript 項目的工具。
大多數工具都使用非常相似的方法,但存在一些細微差別。

我們將深入探討 Lerna 工作流程以及本文第 2 部分中的其他工具,因為它是一個相當大的主題。 現在,讓我們大致了解一下里面的內容:
勒納
這個工具在處理語義版本、設置構建工作流、推送包等方面確實很有幫助。Lerna 背後的主要思想是你的項目有一個包文件夾,其中包含所有獨立的代碼部分。 除了包之外,您還有一個主應用程序,例如可以位於 src 文件夾中。 Lerna 中的幾乎所有操作都通過一個簡單的規則進行 - 你遍歷所有包,並對它們執行一些操作,例如,增加包版本,更新所有包的依賴關係,構建所有包等。
使用 Lerna,您有兩種使用包的選擇:
- 無需將它們推送到遠程(NPM)
- 將您的包裹推送到遠程
在使用第一種方法時,您可以為您的包使用本地引用,並且基本上並不真正關心符號鏈接來解析它們。
但是如果您使用第二種方法,您將被迫從遠程導入您的包。 (例如, import { something } from @yourcompanyname/packagename;
),這意味著您將始終獲得軟件包的遠程版本。 對於本地開發,您必須在文件夾的根目錄中創建符號鏈接,以使捆綁程序解析本地包,而不是使用node_modules/
中的包。 這就是為什麼在啟動 Webpack 或您最喜歡的捆綁程序之前,您必須啟動lerna bootstrap
,它會自動鏈接所有包。
紗
Yarn 最初是 NPM 包的依賴管理器,最初不是為了支持 monorepos 而構建的。 但在 1.0 版本中,Yarn 開發人員發布了一個名為Workspaces的功能。 在發佈時,它並不是那麼穩定,但過了一段時間,它就可以用於生產項目了。
Workspace基本上是一個包,它有自己的package.json並且可以有一些特定的構建規則(例如,如果您在項目中使用 TypeScript,則需要一個單獨的tsconfig.json 。)。 實際上,您可以使用 bash 以某種方式在沒有 Yarn Workspaces 的情況下進行管理,並具有完全相同的設置,但是此工具有助於簡化每個包的安裝和更新依賴項的過程。
一目了然,Yarn 及其工作區提供了以下有用的功能:
- 所有包的根目錄中的單個
node_modules
文件夾。 例如,如果您有packages/package_a
和packages/package_b
— 帶有它們自己的package.json
— 所有依賴項將僅安裝在根目錄中。 這是 Yarn 和 Lerna 工作方式的不同之處之一。 - 依賴符號鏈接以允許本地包開發。
- 所有依賴項的單個鎖定文件。
- 如果您只想為一個包重新安裝依賴項,則重點關注依賴項更新。 這可以使用
-focus
標誌來完成。 - 與 Lerna 集成。 您可以輕鬆地讓 Yarn 處理所有安裝/符號鏈接,並讓 Lerna 負責發布和版本控制。 這是迄今為止最流行的設置,因為它需要較少的精力並且易於使用。
有用的鏈接:
- 紗線工作區
- 如何構建 TypeScript mono-repo 項目
巴澤爾
Bazel 是一個大型應用的構建工具,可以處理多語言依賴,支持很多現代語言(Java、JS、Go、C++等)。 在大多數情況下,將 Bazel 用於中小型 JS 應用程序是大材小用,但在大規模上,由於其性能,它可能會提供很多好處。
從本質上講,Bazel 看起來類似於 Make、Gradle、Maven 和其他允許基於包含構建規則和項目依賴項描述的文件構建項目的工具。 Bazel 中的同一個文件稱為BUILD ,位於 Bazel 項目的工作空間內。 BUILD文件使用它的 Starlark,這是一種人類可讀的高級構建語言,看起來很像 Python。
通常,您不會與BUILD打交道,因為有很多樣板可以在 Web 上輕鬆找到,並且已經配置好並準備好進行開發。 每當您想構建項目時,Bazel 基本上都會執行以下操作:
- 加載與目標相關的BUILD文件。
- 分析輸入及其依賴關係,應用指定的構建規則,並生成操作圖。
- 對輸入執行構建操作,直到生成最終構建輸出。
有用的鏈接:
- JavaScript 和 Bazel – 從頭開始為 JS 設置 Bazel 項目的文檔。
- Bazel 的 JavaScript 和 TypeScript 規則 – JS 的樣板。
結論
Monorepos 只是一個工具。 關於它是否有未來有很多爭論,但事實是,在某些情況下,這個工具可以有效地完成它的工作並處理它。 在過去幾年的過程中,這個工具已經發展,獲得了更多的靈活性,克服了很多問題,並在配置方面移除了一個複雜層。
還有很多問題需要解決,比如 Git 性能不佳,但希望這會在不久的將來得到解決。
如果您想學習為您的應用程序構建強大的 CI/CD 管道,我推薦如何使用 GitLab CI 構建有效的初始部署管道。