天天看點

現代前端工程為什麼越來越離不開 Monorepo?

随着前端工程日益複雜,某些業務或者工具庫通常涉及到很多個倉庫,那麼時間一長,多個倉庫開發弊端日益顯露,由此出現了一種新的項目管理方式——Monorepo。本文主要以 Monorepo 的概念、MultiRepo的弊端、Monorepo 的收益以及Monorepo 的落地這幾個角度來認識和學習一下 Monorepo,文末會有思考題,歡迎大家來踴躍讨論。

什麼是 Monorepo?

Monorepo 其實不是一個新的概念,在軟體工程領域,它已經有着十多年的曆史了。概念上很好了解,就是把多個項目放在一個倉庫裡面,相對立的是傳統的 MultiRepo 模式,即每個項目對應一個單獨的倉庫來分散管理。

現代前端工程為什麼越來越離不開 Monorepo?

現代的前端工程已經越來越離不開 Monorepo 了,無論是業務代碼還是工具庫,越來越多的項目已經采用 Monorepo 的方式來進行開發。Google 甯願把所有的代碼都放在一個 Monorepo 工程下面,Vue 3、Yarn、Npm7 等等知名開源項目的源碼也是采用 Monorepo 的方式來進行管理的。

一般 Monorepo 的目錄如下所示,在 packages 存放多個子項目,并且每個子項目都有自己的​

​package.json​

​:

├── packages
|   ├── pkg1
|   |   ├── package.json
|   ├── pkg2
|   |   ├── package.json
├── package.json      

那 Monorepo 究竟有什麼魔力,讓大家如此推崇,落地如此之廣呢?

MultiRepo 之痛

要想知道 Monorepo 的優勢,首先得弄清楚之前的開發方式有什麼痛點。

之前傳統的方式​

​MultiRepo​

​當中,每個項目都對應單獨的一個代碼倉庫。我之前也是用這種方式開發的,是真真切切地感受到了這種方式帶來的諸多弊端。現在就和大家一一分享一下。

1.代碼複用

在維護多個項目的時候,有一些邏輯很有可能會被多次用到,比如一些基礎的元件、工具函數,或者一些配置,你可能會想: 要不把代碼直接 copy 過來,多省事兒!但有個問題是,如果這些代碼出現 bug、或者需要做一些調整的時候,就得修改多份,維護成本越來越高。

那如何來解決這個問題呢?比較好的方式是将公共的邏輯代碼抽取出來,作為一個 npm 包進行釋出,一旦需要改動,隻需要改動一份代碼,然後 publish 就行了。

但這真的就完美解決了麼?我舉個例子,比如你引入了 ​

​1.1.0​

​ 版本的 A 包,某個工具函數出現問題了,你需要做這些事情:

  1. 去修改一個工具函數的代碼
  1. 釋出​

    ​1.1.1​

    ​版本的新包
  1. 項目中安裝新版本的 A。

可能隻是改了一行代碼,需要走這麼多流程。然而開發階段是很難保證不出 bug 的,如果有個按鈕需要改個樣式,又需要把上面的流程重新走一遍......停下來想想,這些重複的步驟真的是必須的嗎?我們隻是想複用一下代碼,為什麼每次修改代碼都這麼複雜?

上述的問題其實是 ​

​MultiRepo​

​普遍存在的問題,因為不同的倉庫工作區的割裂,導緻複用代碼的成本很高,開發調試的流程繁瑣,甚至在基礎庫頻繁改動的情況下讓人感到很抓狂,體驗很差。

2.版本管理

在 MultiRepo 的開發方式下,依賴包的版本管理有時候是一個特别玄學的問題。比如說剛開始一個工具包版本是 v1.0.0,有諸多項目都依賴于這個工具包,但在某個時刻,這個工具包發了一個 ​

​break change​

​ 版本,和原來版本的 API 完全不相容。而事實上有些項目并沒有更新這個依賴,導緻一些莫名的報錯。

當項目多了之後,很容易出現這種依賴更新不及時的情況。這又是一個痛點。

3.項目基建

由于在 MultiRepo 當中,各個項目的工作流是割裂的,是以每個項目需要單獨配置開發環境、配置 CI 流程、配置部署釋出流程等等,甚至每個項目都有自己單獨的一套腳手架工具。

其實,很容易發現這些項目裡的很多基建的邏輯都是重複的,如果是 10 個項目,就需要維護 10 份基建的流程,邏輯重複不說,各個項目間存在建構、部署和釋出的規範不能統一的情況,這樣維護起來就更加麻煩了。

Monorepo 的收益

說清楚 ​

​MultiRepo​

​​ 的痛點之後,相信你也大概能了解為什麼要誕生​

​Monorepo​

​​這個技術了。現在就來細緻地分析一下​

​Monorepo​

​到底給現代的前端工程帶來了哪些收益。

首先是工作流的一緻性,由于所有的項目放在一個倉庫當中,複用起來非常友善,如果有依賴的代碼變動,那麼用到這個依賴的項目當中會立馬感覺到。并且所有的項目都是使用最新的代碼,不會産生其它項目版本更新不及時的情況。

其次是項目基建成本的降低,所有項目複用一套标準的工具和規範,無需切換開發環境,如果有新的項目接入,也可以直接複用已有的基建流程,比如 CI 流程、建構和釋出流程。這樣隻需要很少的人來維護所有項目的基建,維護成本也大大減低。

再者,團隊協作也更加容易,一方面大家都在一個倉庫開發,能夠友善地共享和複用代碼,友善檢索項目源碼,另一方面,git commit 的曆史記錄也支援以功能為機關進行送出,之前對于某個功能的送出,需要改好幾個倉庫,送出多個 commit,現在隻需要送出一次,簡化了 commit 記錄,友善協作。

Monorepo 的落地

如果你還從來沒接觸過 Monorepo 的開發,到這可能你會疑惑了: 剛剛說了這麼多 Monorepo 的好處,可是我還是不知道怎麼用啊!是直接把所有的代碼全部搬到一個倉庫就可以了嗎?

當然不是,在實際場景來落地 Monorepo,需要一套完整的工程體系來進行支撐,因為基于 Monorepo 的項目管理,絕不是僅僅代碼放到一起就可以的,還需要考慮項目間依賴分析、依賴安裝、建構流程、測試流程、CI 及釋出流程等諸多工程環節,同時還要考慮項目規模到達一定程度後的性能問題,比如項目​

​建構/測試​

​時間過長需要進行增量建構/測試、按需執行 CI等等,在實作全面工程化能力的同時,也需要兼顧到性能問題。

是以,要想從零開始定制一套完善的 Monorepo 的工程化工具,是一件難度很高的事情。不過社群已經提供了一些比較成熟的方案,我們可以拿來進行定制,或者對于一些上層的方案直接拿來使用。

其中比較底層的方案比如 lerna[1],封裝了 Monorepo 中的依賴安裝、腳本批量執行等等基本的功能,但沒有一套建構、測試、部署的工具鍊,整體 Monorepo 功能比較弱,但要用到業務項目當中,往往需要基于它進行頂層能力的封裝,提供全面工程能力的支撐。

當然也有一些內建的 Monorepo 方案,比如 nx[2](官網寫的真心不錯,還有不少視訊教程)、rushstack[3],提供從初始化、開發、建構、測試到部署的全流程能力,有一套比較完整的 Monorepo 基礎設施,适合直接拿來進行業務項目的開發。不過由于這些頂層方案内部各種流程和工具鍊都已經非常完善了,如果要基于這些方案來定制,适配和維護的成本過高,基本是不可行的。

總結

總而言之,Monorepo 的開發模式就是将各自獨立的項目,變成一個統一的工程整體,解決 MultiRepo 下出現的各種痛點,提升研發效率和工程品質。那最後我還有有一個問題,采用 Monorepo 解決了之前的痛點之後,産生了哪些新的問題呢?這些問題可以解決嗎?歡迎大家在留言區一起讨論。

參考資料

[1]

​lerna​

​: https://lerna.js.org/

[2]

​nx​

​: https://nx.dev/latest/react/getting-started/getting-started

[3]

​rushstack​

​: https://rushstack.io/

關注「前端加加」, 第一時間擷取優質文章.