摘要:引言本周精讀的文章是。精讀總的來(lái)說(shuō),雖然拆分子倉(cāng)庫(kù)拆分子包是進(jìn)行項(xiàng)目隔離的天然方案,但當(dāng)倉(cāng)庫(kù)內(nèi)容出現(xiàn)關(guān)聯(lián)時(shí),沒有任何一種調(diào)試方式比源碼放在一起更高效。前端精讀幫你篩選靠譜的內(nèi)容。
1. 引言
本周精讀的文章是 The many Benefits of Using a Monorepo。
現(xiàn)在介紹 Monorepo 的文章很多,可以分為如下幾類:直接介紹 Lerna API 的;介紹如何從獨(dú)立倉(cāng)庫(kù)遷移到 Lerna 的;通過(guò)舉例子說(shuō)明 Monorepo 重要性的。
本文屬于第三種,從 Android 與 IOS 的開發(fā)故事說(shuō)明了 Monorepo 的重要性。
筆者之所以選擇這篇文章,不是因?yàn)槠涔适聦懙暮?,而是認(rèn)可這種具有普適性的解決思路。畢竟 Lerna 作為 Monorepo 的實(shí)現(xiàn)之一也并不盡善盡美,而不同場(chǎng)景對(duì) Monorepo 依賴的原因、功能也有所不同,所以希望借這篇文章,從理論上解釋清楚為什么會(huì)產(chǎn)生 Monorepo,以及 Monorepo 可以解決哪些問(wèn)題,這樣在工作遇到問(wèn)題時(shí),才能想清楚自己要的是什么。
2. 概述作者的一個(gè)項(xiàng)目是 PDF 服務(wù),簡(jiǎn)稱 PSPDFKit,需要同時(shí)兼顧 Android 與 IOS 平臺(tái),項(xiàng)目的發(fā)展經(jīng)歷了如下幾個(gè)階段。
初始階段在 2011 到 2013 年間,PSPDFKit 僅支持 IOS 平臺(tái),但最終項(xiàng)目需要支持 Android,因此開了一個(gè)新倉(cāng)庫(kù)放置 Android 代碼。Android 倉(cāng)庫(kù)的代碼不僅在 UI 上不同,同時(shí)解析 PDF 文檔的核心代碼也不同,這是因?yàn)?IOS 平臺(tái)上使用內(nèi)置 PDF 渲染引擎同時(shí)做了一些業(yè)務(wù)拓展,但使用的 OC 代碼無(wú)法在 Android 使用。
最終新建了兩個(gè)倉(cāng)庫(kù) PSPDFKit-Android 與 Core 。
倉(cāng)庫(kù) Core 中代碼依賴 Android 平臺(tái) JNI 的支持,所以并不能實(shí)現(xiàn) Core 一處修改,兩處都生效的愿望,而我們又希望兩邊功能始終兼容,且減少分支過(guò)多帶來(lái)的潛在的沖突,因此花了很久才意識(shí)到應(yīng)該將這兩個(gè)倉(cāng)庫(kù)合并起來(lái)。
考慮使用 Monorepo由于 Android 的整套流程自己控制的,因此總是可以快速修復(fù)用戶提出的 BUG,然而 IOS 提供的 CGPDF 總會(huì)遇上各種問(wèn)題。所以在 2014 年,我們開啟了一個(gè)龐大的項(xiàng)目,重寫 IOS 的 Core 庫(kù)。有三中方式可供選擇:
在 IOS 代碼中引用 PSPDFKit-Android。
將 PSPDFKit-Android 提取到 Core 倉(cāng)庫(kù)中并分別維護(hù)。
將 IOS 與 Android 代碼合并到一個(gè)倉(cāng)庫(kù)中。
經(jīng)過(guò)討論,最終作者的團(tuán)隊(duì)選擇了第三種方案,因此目錄結(jié)構(gòu)類似如下:
- ios-platform - android-platform - core特例
Web 與后臺(tái)服務(wù)代碼一直是一個(gè)特例,我們認(rèn)為這些內(nèi)容相對(duì)獨(dú)立,所以沒有將其代碼放置到 Monorepo 中。
直到一年后,開始探索 WebAssembly 時(shí),PSPDFKit-web 模塊就出現(xiàn)了,因?yàn)榭梢岳?WebAssembly 將 Core 的代碼編譯并在 Web 平臺(tái)使用,因此 Core 倉(cāng)庫(kù)與 Web 倉(cāng)庫(kù)的關(guān)系變得非常緊密,最終,我們將 Web、Server 也都遷移到 Monorepo 中了。
問(wèn)題Monorepo 瑕不掩瑜,但作者還是列舉了一些缺陷。
由于源碼在一起,倉(cāng)庫(kù)變更非常常見,存儲(chǔ)空間也變得很大,甚至幾 GB,CI 測(cè)試運(yùn)行時(shí)間也會(huì)變長(zhǎng)。即便如此,團(tuán)隊(duì)中任何人都不想回到 git submodules 多倉(cāng)庫(kù)的方式。
3. 精讀總的來(lái)說(shuō),雖然拆分子倉(cāng)庫(kù)、拆分子 NPM 包(For web)是進(jìn)行項(xiàng)目隔離的天然方案,但當(dāng)倉(cāng)庫(kù)內(nèi)容出現(xiàn)關(guān)聯(lián)時(shí),沒有任何一種調(diào)試方式比源碼放在一起更高效。
工程化的最終目的是讓業(yè)務(wù)開發(fā)可以 100% 聚焦在業(yè)務(wù)邏輯上,那么這不僅僅是腳手架、框架需要從自動(dòng)化、設(shè)計(jì)上解決的問(wèn)題,這涉及到倉(cāng)庫(kù)管理的設(shè)計(jì)。
一個(gè)理想的開發(fā)環(huán)境可以抽象成這樣:
“只關(guān)心業(yè)務(wù)代碼,可以直接跨業(yè)務(wù)復(fù)用而不關(guān)心復(fù)用方式,調(diào)試時(shí)所有代碼都在源碼中?!?/p>
在前端開發(fā)環(huán)境中,多 Git Repo,多 Npm 則是這個(gè)理想的阻力,它們導(dǎo)致復(fù)用要關(guān)心版本號(hào),調(diào)試需要 Npm Link。
另外對(duì)于多倉(cāng)庫(kù)的缺點(diǎn),文中還有一些沒有提到的因素,這里一并列舉出來(lái):
管理、調(diào)試?yán)щy
多個(gè) git 倉(cāng)庫(kù)管理起來(lái)天然是麻煩的。對(duì)于功能類似的模塊,如果拆成了多個(gè)倉(cāng)庫(kù),無(wú)論對(duì)于多人協(xié)作還是獨(dú)立開發(fā),都需要打開多個(gè)倉(cāng)庫(kù)頁(yè)面。
雖然 vscode 通過(guò) Workspaces 解決多倉(cāng)庫(kù)管理的問(wèn)題,但在多人協(xié)作的場(chǎng)景下,無(wú)法保證每個(gè)人的環(huán)境配置一致。
對(duì)于共用的包通過(guò) Npm 安裝,如果不能接受調(diào)試編譯后的代碼,或每次 npm link 一下,就沒有辦法調(diào)試依賴的子包。
分支管理混亂
假如一個(gè)倉(cāng)庫(kù)提供給 A、B 兩個(gè)項(xiàng)目用,而 B 項(xiàng)目?jī)?yōu)先開發(fā)了功能 b,無(wú)法與 A 項(xiàng)目兼容,此時(shí)就要在這個(gè)倉(cāng)庫(kù)開一個(gè) feature/b 的分支支持這個(gè)功能,并且在未來(lái)合并到主干同步到項(xiàng)目 A。
一旦需要開分支的組件變多了,且之間出來(lái)依賴關(guān)聯(lián),分支管理復(fù)雜度就會(huì)呈指數(shù)上升。
依賴關(guān)系復(fù)雜
獨(dú)立倉(cāng)庫(kù)間組件版本號(hào)的維護(hù)需要手動(dòng)操作,因?yàn)樵创a不在一起,所以沒有辦法整體分析依賴,自動(dòng)化管理版本號(hào)的依賴。
三方依賴版本可能不一致
一個(gè)獨(dú)立的包擁有一套獨(dú)立的開發(fā)環(huán)境,難以保證子模塊的版本和主項(xiàng)目完全一直,就存在運(yùn)行結(jié)果不一致的風(fēng)險(xiǎn)。
占用總空間大
正常情況下,一個(gè)公司的業(yè)務(wù)項(xiàng)目只有一個(gè)主干,多 git repo 的方式浪費(fèi)了大量存儲(chǔ)空間重復(fù)安裝比如 React 等大型模塊,時(shí)間久了可能會(huì)占用幾十 GB 的額外空間,對(duì)于沒有外接硬盤的同學(xué)來(lái)說(shuō),定期清理不用的項(xiàng)目下 node_modules 也是一件麻煩事。
不利于團(tuán)隊(duì)協(xié)作
一個(gè)大項(xiàng)目可能會(huì)用到數(shù)百個(gè)二方包,不同二方包的維護(hù)頻率不同,權(quán)限不同,倉(cāng)庫(kù)位置也不同,主倉(cāng)庫(kù)對(duì)它們的依賴方式也不同。
一旦其中一個(gè)包進(jìn)行了非正常改動(dòng),就會(huì)影響到整個(gè)項(xiàng)目,而我們精力有限,只盯著主倉(cāng)庫(kù),往往會(huì)栽在不起眼的二方包發(fā)布上。
所以對(duì)于一個(gè)非常復(fù)雜,又具有技術(shù)挑戰(zhàn)的大型系統(tǒng)在協(xié)作人員多的情況下出現(xiàn)問(wèn)題的概率非常大,需要通過(guò) Review 制度避免錯(cuò)誤的發(fā)生,那么將所有相關(guān)的源碼聚合在一個(gè)倉(cāng)庫(kù)下,是更好管理的。
理想 monorepo 的設(shè)計(jì)參考 Lerna 的規(guī)范,以 packages 作為子模塊根文件夾,筆者設(shè)計(jì)一個(gè)理想的 monorepo 結(jié)構(gòu):
. ├── packages │ ├─ module-a │ │ ├─ src # 模塊 a 的源碼 │ │ └─ package.json # 自動(dòng)生成的,僅模塊 a 的依賴 │ └─ module-b │ ├─ src # 模塊 b 的源碼 │ └─ package.json # 自動(dòng)生成的,僅模塊 b 的依賴 ├── tsconfig.json # 配置文件,對(duì)整個(gè)項(xiàng)目生效 ├── .eslintrc # 配置文件,對(duì)整個(gè)項(xiàng)目生效 ├── node_modules # 整個(gè)項(xiàng)目只有一個(gè)外層 node_modules └── package.json # 包含整個(gè)項(xiàng)目所有依賴
所有全局配置文件只有一個(gè),這樣不會(huì)導(dǎo)致 IDE 遇到子文件夾中的配置文件,導(dǎo)致全局配置失效或異常。node_modules 也只有一個(gè),既保證了項(xiàng)目依賴的一致性,又避免了依賴被重復(fù)安裝,節(jié)省空間的同時(shí)還提高了安裝速度。
兄弟模塊之間通過(guò)模塊 package.json 定義的 name 相互引用,保證模塊之間的獨(dú)立性,但又不需要真正發(fā)布或安裝這個(gè)模塊,通過(guò) tsconfig.json 的 paths 與 webpack 的 alias 共同實(shí)現(xiàn)虛擬模塊路徑的效果。
再結(jié)合 Lerna 根據(jù)聯(lián)動(dòng)發(fā)布功能,使每個(gè)子模塊都可以獨(dú)立發(fā)布。
4. 總結(jié)Lerna 是業(yè)界知名度最高的 Monorepo 管理工具,功能完整。但由于通用性要求非常高,需要支持任意項(xiàng)目間 Monorepo 的組合,因此在 packages 文件夾下的配置文件還是與獨(dú)立倉(cāng)庫(kù)保持一致,這樣在 TS 環(huán)境下會(huì)造成配置截?cái)嗟膯?wèn)題。同時(shí)包之間的引用也通過(guò)更通用的 symlink 完成,這導(dǎo)致了還是要在子模塊目錄存在 node_modules 文件夾,而且效果依賴項(xiàng)目初始化命令。
如果加一些限定條件,比如基于 Webpack + Typescript 環(huán)境的 Monorepo,可以換一套思路,利用這些工具自身運(yùn)行時(shí)功能,減少更多模版代碼或配置文件,進(jìn)一步提升 Monorepo 的效果。
對(duì)于別名映射,對(duì) symlink 與 alias 進(jìn)行對(duì)比:
symlink: 更通用,適合任何構(gòu)建器。但需要初始化,且在每個(gè)關(guān)聯(lián)模塊下新增 node_modules 文件夾。
alias: 限定構(gòu)建器。但不需要初始化,不新增文件夾,甚至可以運(yùn)行時(shí)動(dòng)態(tài)修改別名配置。
可見如果限定了構(gòu)建器,別名映射可以做得更輕量,且無(wú)需初始化。
今天的問(wèn)題是,你的項(xiàng)目需要使用 Monorepo 嗎?你對(duì) Monorepo 有其他要求嗎?
討論地址是:精讀《Monorepo 的優(yōu)勢(shì)》 · Issue #151 · dt-fe/weekly
如果你想?yún)⑴c討論,請(qǐng) 點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀 - 幫你篩選靠譜的內(nèi)容。
關(guān)注 前端精讀微信公眾號(hào)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109444.html
摘要:目前最常見的解決方案是和的特性。具體的使用方法移步官網(wǎng)而使用作為包管理器的同學(xué),可以在中以字段聲明,就會(huì)以的方式管理。這樣的話,無(wú)論你的包管理器是還是,都能發(fā)揮的優(yōu)勢(shì)要是包管理是,就會(huì)把依賴安裝交給處理。 最近我接手了一個(gè)項(xiàng)目,代碼量比較大、有點(diǎn)復(fù)雜。倉(cāng)庫(kù) clone 下來(lái)代碼有 50+ MB,npm install 安裝完體積飚到了近 2GB …… 熟悉了一下,這個(gè)項(xiàng)目比較復(fù)雜,采用...
我們先說(shuō)下 Yarn workspace 首先Yarn workspace 是 Yarn 提供的 monorepo 下,管理依賴的機(jī)制。這就說(shuō)主要對(duì)代碼倉(cāng)庫(kù)下,多個(gè) package 的依賴,進(jìn)行管理:將共同的依賴,做 hosting(提升)。前述這樣就可以有效的防止 package 中的包重復(fù)安裝。 workspace 機(jī)制,會(huì)在根目錄下,統(tǒng)一安裝依賴到 node_module,并生成...
摘要:需要說(shuō)明是的,這里說(shuō)的專家不再關(guān)心細(xì)節(jié),不代表成為專家后學(xué)不會(huì)細(xì)節(jié),也不代表專家不了解細(xì)節(jié)。本文將從三個(gè)點(diǎn)去解釋,為什么專家看上去越來(lái)越原理技術(shù)細(xì)節(jié)。試想一個(gè)不能理解業(yè)務(wù)要做什么的人,即便懂得再多技術(shù)細(xì)節(jié),對(duì)業(yè)務(wù)也是沒有價(jià)值的。1. 引言 本周的精讀是有感而發(fā)。 筆者接觸前端已有八年,觀察了不少前端大牛的發(fā)展路徑,發(fā)現(xiàn)成功的人都具有相似的經(jīng)歷: 初期技術(shù)熱情極大 -> 大量標(biāo)志性技術(shù)項(xiàng)目 -...
摘要:最近發(fā)現(xiàn)公司一個(gè)項(xiàng)目的目錄組織挺奇怪的,所有的子項(xiàng)目都放在了目錄里,還有這種騷操作特意查了下資料,發(fā)現(xiàn)是一種比較流行的項(xiàng)目管理模式。 最近發(fā)現(xiàn)公司一個(gè)項(xiàng)目的目錄組織挺奇怪的,所有的子項(xiàng)目都放在了packages目錄里,還有這種騷操作?特意查了下資料,發(fā)現(xiàn)是一種比較流行的monorepo項(xiàng)目管理模式。近幾年比較火的React,Vue,Babel都是用的這種模式: showImg(http...
摘要:指向一個(gè)文件,這個(gè)文件描述了所有的字段以及約束。其中一個(gè)項(xiàng)目為一個(gè)子項(xiàng),如為一個(gè)項(xiàng)目,在創(chuàng)建時(shí)自動(dòng)生成。整個(gè)也有此字段,默認(rèn)生效于所有。默認(rèn)項(xiàng)目,當(dāng)使用一些命令沒有指定項(xiàng)目名稱時(shí),默認(rèn)指向的項(xiàng)目。 ... 在Angular CLI 6+的版本后,原先的angular-cli.json就被換成了angular.json,而其中里面的字段變化挺大了,如果不了解基本的組成,或者直接把老版本的...
閱讀 744·2021-11-11 16:54
閱讀 3065·2021-09-26 09:55
閱讀 2015·2021-09-07 10:20
閱讀 1211·2019-08-30 10:58
閱讀 1057·2019-08-28 18:04
閱讀 707·2019-08-26 13:57
閱讀 3598·2019-08-26 13:45
閱讀 1164·2019-08-26 11:42