动态

详情 返回 返回

俯瞰 Monorepo,別一番風景! - 动态 详情

寫在最前

本故事簡要地介紹了 Monorepo 的 What 和 Why,重點篇幅在於搭建一個好用的 Monorepo 工程時應該考慮的點。可以作為你在選擇工具時的條件,也可以作為你在搭建 Monorepo 工程時查漏補缺的參考。希望這對你有所幫助,哪怕只是一點點 ^O^

“在這個 AI 內容生成氾濫的時代,依然有一批人"傻傻"堅持原創,如果您能讀到最後,還請點贊或收藏或關注支持下我唄,感謝 ( ̄︶ ̄)↗”

What?- 獨立和關係

丹尼爾:蛋兄,好久不見,今天我們來聊聊 Monorepo 吧!

蛋先生:Monorepo?就是把多個項目放在同一個倉庫裏的那種嗎?

丹尼爾:是呀,感覺上就是把一堆代碼庫簡單地堆在一起

蛋先生:你説得不太準確,我覺得 Monorepo 最重要的是倆關鍵字:獨立和關係

丹尼爾:怎麼説?

蛋先生:獨立是指這些項目本身是完整的,一般都擁有開發、測試,發佈等完整的生命週期,而不是簡單的包含一堆代碼文件的文件夾

丹尼爾:哦,這個我明白了。那關係呢?

蛋先生:關係是指這些項目之間存在一定的關聯,比如它們屬於同一個業務領域,或是有依賴關係,而不是毫無關聯地硬堆在一起

丹尼爾:懂了!

Why?- 更好地協作

丹尼爾:那用這個 Monorepo 有什麼好處呢?我以前一個項目一個倉庫不也挺好的嗎?

蛋先生:這裏科普一下,一個項目一個倉庫有一個專用的名詞叫 Polyrepo。我認為 Monorepo 最關鍵的好處在於項目與項目之間的協作

丹尼爾:怎麼説呢?

蛋先生:比如共享代碼以減少重複工作方面。當你在開發應用 B 時,如果發現應用 A 中已經實現了很多相似的邏輯,那麼你需要把共享邏輯抽取到一個獨立的庫 α,然後修改應用 A 和應用 B 以依賴於庫 α,因為這一切都在同一個倉庫中完成,非常方便,操作成本較低

丹尼爾:確實,如果採用 Polyrepo 的方式,我得新建一個倉庫,把共享邏輯抽取出來,然後通過本地 link 的方式來開發調試。一切就緒後,還得發佈到 npm,再在應用 A 和應用 B 中安裝依賴。而且每次修改都需要重複這個過程,真是麻煩

蛋先生:再比如庫修改可能導致項目不穩定方面。當一個被依賴的庫進行迭代升級時,特別是有大的變更時,如果沒有及時溝通以採取相應的措施,就會導致各種問題,潛在的風險非常大

丹尼爾:Monorepo 不會有這個問題嗎?

蛋先生:在 Monorepo 中修改是原子的,即當你修改庫 α 時,同倉庫的應用 A 和應用 B 都能及時感知到變更。例如,你刪除了某個接口的入參參數,應用 A 和應用 B 會立刻報錯,這樣就能及時發現並解決潛在風險

丹尼爾:這樣確實挺棒的

蛋先生:最根本的原因是 Polyrepo 帶來了隔離,而隔離影響了協作。Monorepo 的目標則是為了更好地協作。就像部門間協作和部門內協作,顯然同一個部門內的協作效率更高,溝通成本也更低

丹尼爾:一語中的!

How?- 舒適地開發

➥ 初始化階段 - 腳手架

丹尼爾:那採用 Monorepo 的形式來組織項目,我應該怎麼做呢?

蛋先生:我們一起來走一走應用開發的歷程,看看需要有哪些工作吧

丹尼爾:好啊

蛋先生:有兩種開局方式。一種是全新開始,這樣的話你需要一個能生成 Monorepo 大倉的腳手架

丹尼爾:恩,很體貼

蛋先生:不過這種情況發生的概率較低,通常是一次性的。更常見的是在已有的 Monorepo 倉庫中增加新項目。這是經常需要做的事情,所以我們可以提供多種腳手架代碼生成器來快速初始化一個項目,比如創建 TS 工具庫項目、React 應用項目,或者是 TS CLI 項目等等

丹尼爾:確實,常用的項目類型是可以枚舉出來的。有了這些工具,後續增加項目就輕鬆多了,想想就很爽!那另一種開局呢?

➥ 初始化階段 - 依賴安裝

蛋先生:另一種開局是你準備在一個已存在的 Monorepo 大倉上進行開發工作。這時,你的第一件事應該是安裝依賴,對嗎?

丹尼爾:恩,沒錯

蛋先生:不過,大倉裏可能有很多項目,你總不能一個一個項目進行安裝依賴吧,所以需要有一個可以一次性安裝全部項目依賴的能力

丹尼爾:對啊,我可不想把時間浪費在一個個項目裏 cd 來 cd 去的

➥ 開發階段 - 任務編排

蛋先生:無論哪種開局,接下來都是進入到開發階段了。假設你在開發應用 A,而應用 A 依賴庫 α,那麼你是不是得先確保庫 α 有可用的構建產物?

丹尼爾:是啊,所以第一步就是得知道應用 A 依賴了哪些同倉庫中的其他庫,並且提前對它們進行構建。但如果依賴關係比較複雜,就難搞了

蛋先生:正是如此。所以,我們希望能夠不用手動處理這些依賴,只要對應用 A 進行構建,就能自動處理它所依賴的所有庫的構建

丹尼爾:那就太好了!

蛋先生:這就需要任務編排了。我們可以配置任務之間的協作關係,比如在執行某個任務之前,需要先執行哪些任務,這些任務是串行還是並行執行等等

丹尼爾:哦,任務編排還真好用

➥ 開發階段 - 一致命令

蛋先生:好了,萬事俱備,你可以開始本地開發調試了

丹尼爾:哦,那我先看看項目的 README,找找本地開發調試的指引

蛋先生:不用那麼麻煩,直接執行 dev 命令吧。無論你是在開發應用項目還是庫項目,無論是用 JavaScript 還是 Java,開發就運行 dev,構建就運行 build,測試就運行 test,等等。這樣你就不會有任何心智負擔

丹尼爾:哈哈,老早就想這樣了

➥ 開發階段 - 影響檢測

蛋先生:開發過程中,你發現依賴的庫 α 提供的接口有點小問題,現在你準備對應用 A 所依賴的庫 α 進行修改

丹尼爾:哦,反正都是在同一個倉庫,修改起來挺方便的

蛋先生:但我們得確保這個改動不會影響到依賴該庫的其他項目。至少在我們可控的範圍內,比如同一倉庫中依賴該庫的其他項目。所以,我們需要一種自動檢測機制來識別哪些項目受到了影響,然後對這些受影響的項目進行單元測試等操作,以確保它們的穩定性

➥ 開發階段 - 依賴分析

丹尼爾:蛋兄果然很謹慎啊

蛋先生:咳咳~。其實,這一切都需要藉助依賴分析能力。當 Monorepo 的規模越來越大時,依賴關係也會變得越來越複雜。我們需要通過依賴關係圖,清晰地瞭解各項目之間的聯繫和影響,從而做到對項目狀況瞭如指掌

➥ 開發階段 - 依賴權限

蛋先生:你現在是庫 α 的主要負責人。有一天,你發現了一些並不想對外暴露的 API 被倉庫內的其他項目使用,結果你在修改這些 API 時就不得不考慮對這些項目的影響

丹尼爾:啊,雖然我是聲明瞭 export,但這只是為了庫內部的其他代碼使用。可其他項目卻可以通過深層導入來依賴這些 API

蛋先生:嗯,所以我們需要在工程層面上建立機制,防止這些 API 被誤依賴

➥ 開發階段 - 修改權限

蛋先生:庫 α 雖説是由你主要負責的,但是由於代碼庫是放在一起的,其他擁有大倉權限的同學也就有權限進行修改。但是你並不希望他們隨意修改庫 α 的代碼,至少要經過你的同意

丹尼爾:是啊是啊,這真的很重要!

蛋先生:所以我們需要引入類似 OWNER 的機制,對這些修改權限進行限制,以確保代碼的穩定性和一致性

➥ CI 階段 - 本地計算緩存

“注:CI 階段的能力,不僅僅只用於 CI,開發階段也是可以享用,只是為了劇情需要這麼安排而已”

蛋先生:好了,項目修改完畢,提交。CI 開始工作了,然後你發現每次 CI 構建都非常慢

丹尼爾:嗯,我加點戲哈。我喝了一杯咖啡,再回來一看,好傢伙,CI 還在跑。這樣可不行,得優化性能了,不然我快要崩潰了

蛋先生:好吧,這戲加得... 回到正題。這是因為該項目直接或間接依賴了同一倉庫中的好幾個其他庫。所以,每次構建實際上都需要構建多個項目。優化性能的思路之一就是減少不必要的計算,增量執行就變得非常重要。因此,我們需要引入本地計算緩存,緩存計算結果,避免對沒有修改的庫進行重複構建

丹尼爾:本地緩存,我懂

➥ CI 階段 - 分佈式任務執行

蛋先生:性能優化的另一個思路是加速必要的計算

丹尼爾:昨加速捏?

蛋先生:可以採用分佈式任務執行。將一些可以併發執行的任務分配到不同的服務器上並行處理,實現在更短的時間內完成任務。這樣做雖然會增加一定的成本,但對於大型項目來説,是非常有效的性能提升方案

丹尼爾:聽上去好高級的樣子

➥ CI 階段 - 遠程計算緩存

蛋先生:雖然使用了本地緩存,但每個服務器都需要先構建一次才能生成本地緩存。如果我們把緩存的位置移到遠程雲端,是不是就可以進一步優化性能呢?

丹尼爾:Nice! 這樣就可以共享緩存了

➥ 發佈階段

蛋先生:最後,我們需要把庫 α 發佈到 npm 上去,因為它提供的功能非常通用,不僅僅侷限於當前的項目倉庫內

丹尼爾:那就趕緊發佈吧!

蛋先生:發佈階段,根據需要,利用任務編排就可以了

新的問題

丹尼爾:聽起來 Monorepo 灰常好啊,都使用這種方式得了

蛋先生:Monorepo 確實突破了 Polyrepo 的隔離問題,但這樣開放的結構也帶來了讀權限的問題。如果你的大倉中的部分項目需要由第三方團隊來開發,但你又不希望他們能看到其它項目的內容,那麼 Monorepo 就無法解決這個問題了

丹尼爾:啊,那怎麼辦呢?

蛋先生:這種情況下,你可以考慮將這些項目作為 git submodule 分離出去。這樣一來,大倉中的其它項目仍然可以在工作空間內直接依賴這些分離出去的 git submodule 項目

丹尼爾:那有啥需要注意的地方嗎?

蛋先生:要注意的是,git submodule 的項目就不能通過工作空間直接依賴大倉中的其它項目了,它們需要通過 npm 中央倉庫來進行依賴管理

丹尼爾:好咧,這一聊,天色已晚

蛋先生:嗯,今天就先聊到這裏,就此別過吧

丹尼爾:拜拜!

寫在最後

為什麼不直接寫一個使用某個工具(比如 Turborepo)來搭建 Monorepo 項目的教程呢?因為我相信聰明的你,只需要閲讀官方文檔,就可以輕鬆上手了

不同工具工作方式有所不同,但都是圍繞 Monorepo 來提供能力的。我們應以不變應萬變,掌握表面之下的東西,這樣才能更加靈活地應對各種變化

“親們,都到這了,要不,點贊或收藏或關注支持下我唄 o( ̄▽ ̄)d”


《蛋先生説識》是一個致力於用通俗易懂的方式講解各種知識的欄目。採用深入淺出的講解方式,將複雜的概念變得簡單易懂。請跟隨蛋先生一起探索知識的奧秘,把難懂的知識變成好玩的故事吧!

公眾號中.png

Add a new 评论

Some HTML is okay.