中文字幕 日本 在线 高清,久久精品国产99精品国,超碰人人香蕉,一区二区三区无码高清视频

Monorepo,大型前端項(xiàng)目管理模式實(shí)踐(前端項(xiàng)目管理工具)

閱讀本文您將了解到:什么是 monorepo、為什么要 monorepo、如何實(shí)踐 monorepo。

項(xiàng)目管理模式

Monorepo 這個(gè)詞您可能不是首次聽(tīng)說(shuō),在當(dāng)下大型前端項(xiàng)目中基于 monorepo 的解決方案已經(jīng)深入人心,無(wú)論是比如 Google、Facebook,社區(qū)內(nèi)部知名的開(kāi)源項(xiàng)目 Babel、Vue-next ,還是集團(tuán)中 rax-components 等等,都使用了 monorepo 方案來(lái)管理他們的代碼。

?發(fā)展歷程

倉(cāng)庫(kù)(repository,簡(jiǎn)稱 repo),是我們用來(lái)管理項(xiàng)目代碼的一個(gè)基本單元。通常每個(gè)倉(cāng)庫(kù)負(fù)責(zé)一個(gè)模塊或包的編碼、構(gòu)建、測(cè)試和發(fā)布,代碼規(guī)模相對(duì)較小,邏輯聚合,業(yè)務(wù)場(chǎng)景也比較收攏。

當(dāng)我們?cè)谝徽麎K業(yè)務(wù)域下進(jìn)行研發(fā)時(shí),代碼的解耦復(fù)用是一個(gè)非常重要的問(wèn)題。

Monorepo,大型前端項(xiàng)目管理模式實(shí)踐(前端項(xiàng)目管理工具)

初期業(yè)務(wù)系統(tǒng)不復(fù)雜時(shí),通常只用一個(gè)倉(cāng)庫(kù)來(lái)管理項(xiàng)目,項(xiàng)目為單體應(yīng)用架構(gòu) Monolithic。這時(shí)我們會(huì)以合理劃分目錄,提取公共組件的方式來(lái)解決問(wèn)題。由文件的層級(jí)劃分和引入,來(lái)進(jìn)行頁(yè)面、組件和工具方法等的管理。此時(shí)其整個(gè)依賴和工作流都是統(tǒng)一的、單向的。

當(dāng)業(yè)務(wù)復(fù)雜度的提升,項(xiàng)目的復(fù)雜性增長(zhǎng),由此就會(huì)導(dǎo)致一系列的問(wèn)題:比如項(xiàng)目編譯速度變慢(調(diào)試成本變大)、部署效率/頻率低(非業(yè)務(wù)開(kāi)發(fā)耗時(shí)增加)、單場(chǎng)景下加載內(nèi)容冗余等等,技術(shù)債務(wù)會(huì)越積越多。同時(shí)又有了代碼共享的需求,此時(shí)就需要按照業(yè)務(wù)和模塊來(lái)拆分。那么組件化開(kāi)發(fā)是一個(gè)不錯(cuò)的選擇。這樣每個(gè)倉(cāng)庫(kù)都能獨(dú)立進(jìn)行各模塊的編碼、測(cè)試和發(fā)版,又能實(shí)現(xiàn)多項(xiàng)目共享代碼,研發(fā)效率提升也很明顯(特別是調(diào) UI 樣式的時(shí)候)。同時(shí)在團(tuán)隊(duì)規(guī)模變大,人員分工開(kāi)始明確,拆分的好處還帶來(lái)不同開(kāi)發(fā)人員關(guān)注點(diǎn)可按照域來(lái)分散研發(fā),隊(duì)員只需關(guān)心自己模塊所在的倉(cāng)庫(kù),對(duì)各自核心的業(yè)務(wù)場(chǎng)景關(guān)注思考更加集中和收攏。這種管理模式我們稱之為多倉(cāng)多模塊管理 Multirepo(Polyrepo也是一個(gè)意思)。

再隨著時(shí)間的沉淀,模塊數(shù)量也在飛速增長(zhǎng)。Multirepo 這種方式雖然從業(yè)務(wù)邏輯上解耦了,但也同時(shí)增加了項(xiàng)目的工程管理難度。組件化的前期可以忽略不計(jì),當(dāng)模塊量到達(dá)一定體量程度下,這個(gè)問(wèn)題會(huì)逐漸明顯。比如:

  1. 代碼和配置很難共享:每個(gè)倉(cāng)庫(kù)都需要做一些重復(fù)的工程化能力配置(如 eslint/test/ci 等)且無(wú)法統(tǒng)一維護(hù),當(dāng)有工程上的升級(jí)時(shí),沒(méi)能同步更新到所有涉及模塊,就會(huì)一直存在一個(gè)過(guò)渡態(tài)的情況,對(duì)工程的不斷優(yōu)化非常不利。
  2. 依賴的治理復(fù)雜:模塊越來(lái)越多,涉及多模塊同時(shí)改動(dòng)的可能性急劇增加。如何保障底層組件升級(jí)后,其引用到的組件也能同步更新到位。這點(diǎn)很難做到,如果沒(méi)及時(shí)升級(jí),各工程的依賴版本不一致,往往會(huì)引發(fā)一些意想不到的問(wèn)題。
  3. 存儲(chǔ)和構(gòu)建消耗增加:假如多個(gè)工程依賴 pkg-a,那么每個(gè)工程下 node_modules 都會(huì)重復(fù)安裝 pkg-a,對(duì)本地磁盤(pán)內(nèi)存和本地啟動(dòng)都是個(gè)很大的挑戰(zhàn),增加了開(kāi)發(fā)時(shí)調(diào)試的困難。而且每個(gè)模塊的發(fā)布都是相對(duì)獨(dú)立的,當(dāng)一次迭代修改較多模塊時(shí),總體發(fā)布時(shí)效就是每個(gè)發(fā)布流程的串聯(lián)。對(duì)發(fā)布者來(lái)說(shuō)是一個(gè)非常大的負(fù)擔(dān)。

有沒(méi)有一種更好的管理模式,既能享受到 組件化多包管理 的收益,又能降輕工程復(fù)雜度引起的影響呢?這時(shí)就提出了單倉(cāng)多模塊管理 Monorepo 的概念。Monorepo 其實(shí)不是一個(gè)新的概念,在軟件工程領(lǐng)域,它已經(jīng)有著十多年的歷史了。它是相對(duì)于 Multirepo 而言的一種模式,概念上非常好理解,就是把多個(gè)項(xiàng)目放在一個(gè)倉(cāng)庫(kù)里面。用統(tǒng)一的本地關(guān)聯(lián)、構(gòu)建、發(fā)布流程,來(lái)消費(fèi)業(yè)務(wù)域下所有管理的組件模塊。

?單體應(yīng)用架構(gòu) Monolithic

項(xiàng)目初期起步階段,團(tuán)隊(duì)規(guī)模很小,此時(shí)適合「單體應(yīng)用」,一個(gè)代碼倉(cāng)庫(kù)承接一個(gè)應(yīng)用,管理成本低,最簡(jiǎn)力度支撐業(yè)務(wù)快速落地。

此時(shí)目錄架構(gòu)大概長(zhǎng)這樣:

project├── node_modules/│ ├── lib@1.0.0├── src/│ ├── compA│ ├── compB│ └── compC└── package.json

優(yōu)點(diǎn):

  1. 代碼管理成本低
  2. 代碼能見(jiàn)度高(無(wú)需額外的學(xué)習(xí)成本)
  3. 發(fā)布簡(jiǎn)單,鏈路輕便

缺點(diǎn):

  1. 代碼量大了后,調(diào)試、構(gòu)建效率顯著下降
  2. 無(wú)法跨項(xiàng)目復(fù)用

?多倉(cāng)多模塊管理 Multirepo

團(tuán)隊(duì)規(guī)模變大,人員分工明確,單體應(yīng)用的缺點(diǎn)會(huì)愈發(fā)突出,此時(shí) 「Multirepo」就更適合。模塊分工更明確,可拓展可復(fù)用性更強(qiáng),調(diào)試構(gòu)建發(fā)布能力也有一定提升。

此時(shí)目錄架構(gòu)大概長(zhǎng)這樣:

project├── node_modules/│ ├── lib@1.0.0│ ├── lib@2.0.0│ ├── pkgA│ ├── pkgB│ └── ..├── src/└── package.jsonpackageA├── node_modules/│ └── lib@1.0.0├── src/└── package.jsonpackageB├── node_modules/│ └── lib@2.0.0├── src/└── package.json

優(yōu)點(diǎn):

  1. 便于代碼復(fù)用
  2. 模塊組件獨(dú)立開(kāi)發(fā)調(diào)試,業(yè)務(wù)理解清晰度高
  3. 人員編排分工更加明確
  4. 提高研發(fā)人員的公共抽取思維能力
  5. 源代碼訪問(wèn)權(quán)限設(shè)置靈活

缺點(diǎn):

  1. 模塊劃分力度不容易把握
  2. 共同引用的版本問(wèn)題,容易導(dǎo)致重復(fù)安裝相同依賴的多個(gè)版本
  3. 構(gòu)建配置不復(fù)用,不好管理
  4. 串行構(gòu)建,修改模塊體量大時(shí),發(fā)布成本急劇上升
  5. Code Review、Merge Request 從各自模塊倉(cāng)庫(kù)執(zhí)行,比較分散

?單倉(cāng)多模塊管理 Monorepo

隨著組件/模塊越來(lái)越多, multirepo 維護(hù)成本越來(lái)越大,于是我們意識(shí)到我們的方案是時(shí)候改進(jìn)了。

此時(shí)目錄架構(gòu)大概長(zhǎng)這樣:

project├── node_modules/│ ├── lib@2.0.0│ ├── pkgA│ ├── pkgB│ └── ..├── src/└── package.jsonmono-project├── node_modules/│ └── lib@2.0.0├── packages/│ ├── packageA│ │ └── package.json│ └── packageB│ └── package.json└── package.json

優(yōu)點(diǎn):

  1. 所有源碼在一個(gè)倉(cāng)庫(kù)內(nèi),分支管理與單體應(yīng)用一樣簡(jiǎn)單
  2. 公共依賴顯示更清晰,更方便統(tǒng)一公共模塊版本
  3. 統(tǒng)一的配置方案,統(tǒng)一的構(gòu)建策略
  4. 并行構(gòu)建,執(zhí)行效率提升
  5. 保留 multirepo 的主要優(yōu)勢(shì)
  6. 代碼復(fù)用
  7. 模塊獨(dú)立管理
  8. 分工明確,業(yè)務(wù)場(chǎng)景獨(dú)立
  9. 代碼耦合度降低
  10. 項(xiàng)目引入時(shí),除去非必要組件代碼
  11. CR、MR 由一個(gè)倉(cāng)庫(kù)發(fā)布,閱讀和處理十分方便

缺點(diǎn):

  1. git 服務(wù)根據(jù)目錄進(jìn)行訪問(wèn)權(quán)限劃分,倉(cāng)庫(kù)內(nèi)全部代碼開(kāi)發(fā)給所有開(kāi)發(fā)成員(這種非特殊限制場(chǎng)景不用考慮)
  2. 當(dāng)代碼規(guī)模大到一定程度時(shí),git 的操作速度達(dá)到瓶頸,影響 git 操作體驗(yàn)(中小型規(guī)模不用考慮,而且就算是 def 平臺(tái)可并行量也為 500)

?優(yōu)缺點(diǎn)對(duì)比梳理

場(chǎng)景

multirepo

monorepo

項(xiàng)目代碼維護(hù)

? 多個(gè)倉(cāng)庫(kù)需要分別download各自的node_modules,像這種上百個(gè)包的,多少內(nèi)存都不夠用

? 代碼都只一個(gè)倉(cāng)庫(kù)中,相同依賴無(wú)需多分磁盤(pán)內(nèi)存。

代碼可見(jiàn)性

? 包管理按照各自owner劃分,當(dāng)出現(xiàn)問(wèn)題時(shí),需要到依賴包中進(jìn)行判斷并解決

? 對(duì)需要代碼隔離的情況友好,研發(fā)者只關(guān)注自己核心管理模塊本身

? 每個(gè)人可以方便地閱讀到其他人的代碼,這個(gè)橫向可以為團(tuán)隊(duì)帶來(lái)更好的協(xié)作和跨團(tuán)隊(duì)貢獻(xiàn),不同開(kāi)發(fā)者容易關(guān)注到代碼問(wèn)題本身

? 但同時(shí)也會(huì)容易產(chǎn)生非owner管理者的改動(dòng)風(fēng)險(xiǎn)

? 不好進(jìn)行代碼可視隔離

代碼一致性

? 需要收口eslint等配置包到統(tǒng)一的npm包,再到各自項(xiàng)目引用,這就允許每個(gè)包還能手動(dòng)調(diào)整配置文件

? 當(dāng)您將所有代碼庫(kù)放在一個(gè)地方時(shí),執(zhí)行代碼質(zhì)量標(biāo)準(zhǔn)和統(tǒng)一風(fēng)格會(huì)更容易。

代碼提交

? 底層組件升級(jí),需要通知到所有項(xiàng)目依賴的相關(guān)方,并進(jìn)行回歸

? 每個(gè)包的修改需要分別提交

? API 或共享庫(kù)中的重大更改能夠立即公開(kāi),迫使不同的開(kāi)發(fā)者需要提前溝通并聯(lián)合起來(lái)。每個(gè)人都必須跟上變化。

? 提交使大規(guī)模重構(gòu)更容易。開(kāi)發(fā)人員可以在一次提交中更新多個(gè)包或項(xiàng)目。

唯一來(lái)源

? 子包引用的相同依賴的不同版本的包

? 每個(gè)依賴項(xiàng)的一個(gè)版本意味著沒(méi)有版本沖突,也沒(méi)有依賴地獄。

開(kāi)發(fā)

? 倉(cāng)庫(kù)體積小,模塊劃分清晰。

? 多倉(cāng)庫(kù)來(lái)回切換(編輯器及命令行),項(xiàng)目一多真的得暈。如果倉(cāng)庫(kù)之間存在依賴,還得各種 npm link。

? 只需在一個(gè)倉(cāng)庫(kù)中開(kāi)發(fā),編碼會(huì)相當(dāng)方便。

? 代碼復(fù)用高,方便進(jìn)行代碼重構(gòu)。

? 項(xiàng)目如果變的很龐大,那么 git clone、安裝依賴、構(gòu)建都會(huì)是一件耗時(shí)的事情。

工程配置

? 各個(gè)團(tuán)隊(duì)可能各自有一套標(biāo)準(zhǔn),新建一個(gè)倉(cāng)庫(kù)又得重新配置一遍工程及 CI / CD 等內(nèi)容。

? 工程統(tǒng)一標(biāo)準(zhǔn)化

依賴管理

? 依賴重復(fù)安裝,多個(gè)依賴可能在多個(gè)倉(cāng)庫(kù)中存在不同的版本,npm link 時(shí)不同項(xiàng)目的依賴可能會(huì)存在沖突問(wèn)題。

? 共同依賴可以提取至 root,版本控制更加容易,依賴管理會(huì)變的方便。

代碼管理

? 各個(gè)團(tuán)隊(duì)可以控制代碼權(quán)限,也幾乎不會(huì)有項(xiàng)目太大的問(wèn)題。

? 代碼全在一個(gè)倉(cāng)庫(kù),如果項(xiàng)目一大,幾個(gè) G 的話,用 Git 管理可能會(huì)存在問(wèn)題。

? 代碼權(quán)限如果需要設(shè)置,暫時(shí)不支持

部署(這部分兩者其實(shí)都存在問(wèn)題)

? multi repo 的話,如果各個(gè)包之間不存在依賴關(guān)系倒沒(méi)事,一旦存在依賴關(guān)系的話,開(kāi)發(fā)者就需要在不同的倉(cāng)庫(kù)按照依賴先后順序去修改版本及進(jìn)行部署。

? 而對(duì)于 mono repo 來(lái)說(shuō),有工具鏈支持的話,部署會(huì)很方便,但是沒(méi)有工具鏈的話,存在的問(wèn)題一樣蛋疼。(社區(qū)推薦pnpm、lerna)

持續(xù)集成

? 每個(gè)repo需要定制統(tǒng)一的構(gòu)建部署過(guò)程,然后再各自執(zhí)行

? 可以為 repo 中的每個(gè)項(xiàng)目使用相同的CI/CD部署過(guò)程。

? 同時(shí)未來(lái)可以實(shí)現(xiàn)更自動(dòng)化的部署方式,一次命令完成所有的部署

總體來(lái)說(shuō),當(dāng)業(yè)務(wù)發(fā)展到一定規(guī)模時(shí),monorepo 的升級(jí)相比 multirepo 來(lái)說(shuō),是利遠(yuǎn)大于弊的。

Monorepo 使用 or not

?業(yè)務(wù)現(xiàn)狀

天貓校園如意pos業(yè)務(wù)域場(chǎng)景豐富,整體的代碼邏輯比較復(fù)雜,因此采取按照 app(項(xiàng)目入口)-bundle(業(yè)務(wù)域板塊:可以理解為頁(yè)面)-component/util(通用組件:base組件、biz組件、utils和sdk平鋪,都屬于這個(gè)) 的形式進(jìn)行整個(gè)項(xiàng)目的管理。目前項(xiàng)目所涉及的 npm 業(yè)務(wù)模塊數(shù)量已經(jīng)超過(guò)了 100 個(gè)。

?存在的制約

  1. 應(yīng)用規(guī)模增長(zhǎng),構(gòu)建依賴本地環(huán)境,構(gòu)建效率低下,非業(yè)務(wù)投入成本不斷上升
    1. 主應(yīng)用需要頻繁構(gòu)建
    2. 構(gòu)建前依賴的模塊需要單獨(dú)構(gòu)建,構(gòu)建速度串行
    3. 組件還在不斷增長(zhǎng),愈加不利于工程的維護(hù)
  2. 組件類發(fā)布沒(méi)有對(duì)接集團(tuán)規(guī)范,無(wú)CR卡點(diǎn),二級(jí)依賴凌亂
    1. 代碼 review 全靠 人工 diff 進(jìn)行 cr
    2. 每次的版本信息都是通過(guò)手動(dòng)維護(hù)
    3. 組件依賴的二級(jí)依賴不統(tǒng)一,package-conflict 非常多

?優(yōu)化目標(biāo)

構(gòu)建部署發(fā)布提效,全鏈路CR及需求管控,全代碼卡點(diǎn)管控,后續(xù)代碼質(zhì)量,單測(cè)節(jié)點(diǎn)補(bǔ)充等等

  1. 降低構(gòu)建部署成本,對(duì)于一次合理的多包改動(dòng),只需要進(jìn)行1~2次的構(gòu)建即可完成部署任務(wù)
  2. 降低每次迭代的應(yīng)用發(fā)布的維護(hù)成本,對(duì)于一個(gè)應(yīng)用及其包含的子應(yīng)用(包括集成包和微應(yīng)用模式),一次完整的研發(fā)流程只需要維護(hù)一個(gè)發(fā)布迭代。發(fā)布依賴關(guān)系通過(guò)自動(dòng)化流程進(jìn)行優(yōu)化。
  3. 對(duì)于主子應(yīng)用/組件可以進(jìn)行合理的CR管控
  4. 每個(gè)有變更的子應(yīng)用都可以關(guān)聯(lián)到對(duì)應(yīng)的aone需求(可多個(gè))。
  5. 能將整個(gè)研發(fā)和發(fā)布流程統(tǒng)一到一個(gè)平臺(tái)上進(jìn)行操作,降低理解和操作成本。(更進(jìn)一步的優(yōu)化,將原來(lái)割裂的一些流程節(jié)點(diǎn)進(jìn)行整合,以及版本迭代修改日志的統(tǒng)一維護(hù)。)
  6. 在流程節(jié)點(diǎn)上可以提供擴(kuò)展方式,預(yù)留后續(xù)類似代碼掃碼,質(zhì)量評(píng)估,灰度管控等體系。

?選用結(jié)論

綜上所述,不管是當(dāng)應(yīng)用規(guī)模發(fā)展到一定規(guī)模下普遍遇到的情況,還是歷史包袱,如意pos現(xiàn)在已經(jīng)是一個(gè)超級(jí)復(fù)雜的應(yīng)用。以上的問(wèn)題所帶來(lái)的制約,只會(huì)愈加凸顯。在這個(gè)大背景下,這個(gè)階段,為了解決上面的問(wèn)題,使用 monorepo 進(jìn)行項(xiàng)目管理升級(jí),是非常有價(jià)值的。

?落地結(jié)果

落地過(guò)程參考后面的「最佳實(shí)踐」

打包

開(kāi)發(fā)

發(fā)布

架構(gòu)升級(jí)前

單組件打包時(shí)間70~90s,迭代 n 個(gè)包需要 *n 的時(shí)間

單個(gè)包啟動(dòng)開(kāi)發(fā)需要~60s,同時(shí)開(kāi)發(fā)多個(gè)包會(huì)拖慢速度,進(jìn)入打包發(fā)布流程會(huì)打斷開(kāi)發(fā)

脫軌線上發(fā)布平臺(tái)CR/測(cè)試無(wú)卡點(diǎn),脫離管控

架構(gòu)升級(jí)后

并行構(gòu)建打包 n 個(gè)包只需要~90s

本地開(kāi)發(fā)不會(huì)被打斷,節(jié)省重啟時(shí)間

云平臺(tái)構(gòu)建模式,接入 CR 卡點(diǎn),進(jìn)一步提高質(zhì)量穩(wěn)定性

提效總結(jié)

組件打包成本降低90%

啟動(dòng)成本降低100%

發(fā)版提效80%,本地構(gòu)建轉(zhuǎn)云構(gòu)建

Monorepo 生態(tài)

Monorepo 只是一個(gè)管理概念,實(shí)際上它并不代表某項(xiàng)具體的技術(shù),更不是所謂的框架。開(kāi)發(fā)人員需要根據(jù)不同場(chǎng)景、不同的研發(fā)習(xí)慣,使用相應(yīng)的技術(shù)手段或者工具,來(lái)達(dá)到或者完善它的整個(gè)流程,從而達(dá)到更好的開(kāi)發(fā)和管理體驗(yàn)。

目前前端領(lǐng)域的 Monorepo 生態(tài)有一個(gè)很顯著的特點(diǎn)就是只有庫(kù),而沒(méi)有大一統(tǒng)的框架或者完整的構(gòu)建系統(tǒng)來(lái)支持。目前的工具形態(tài)上像是傳統(tǒng)的 CMake 那樣的輔助工具,而不是像 Gradle(構(gòu)建語(yǔ)言 生態(tài)鏈)或 Cargo(包管理器自身集成) 那樣統(tǒng)一的方式??赡芪磥?lái)的趨勢(shì)是像 nx 或者 turborepo 這樣的庫(kù)要往完整的框架發(fā)展,或者包管理器自身就逐步支持相應(yīng)的功能,不需要過(guò)多的三方依賴。以下介紹一下生態(tài)中的一些核心技術(shù):

?包管理方案

  • Npm

npm 在 v7 才支持了 workspaces,屬于終于能用上了但是并不好用的情況,重點(diǎn)是比較慢,通常無(wú)法兼容存量的 monorepo 應(yīng)用,出來(lái)的時(shí)間太晚了,不能像 yarn 支持自定義 nohoist 以應(yīng)對(duì)某些依賴被 hoist 到 monorepo root 導(dǎo)致的問(wèn)題,也沒(méi)有做到像 pnpm 以 link 的方式共享依賴,能顯著的減少磁盤(pán)占用,除了 npm 自帶之外沒(méi)有其他優(yōu)點(diǎn)。

  • Yarn

yarn 1.x

最早支持 workspaces 模式的包管理器,配合 lerna 占據(jù)了大部分 monorepo,在比較長(zhǎng)的一段時(shí)間里是 monorepo 的事實(shí)標(biāo)準(zhǔn),缺點(diǎn)是 yarn 的共享包才會(huì)提升到 root node_modules 下,其他非共享庫(kù)都會(huì)每個(gè)地方留一份,占用空間比較多,還有提升到 root 這一行為也會(huì)帶來(lái)兼容性問(wèn)題(有些包的 require 方式比較 hack)

yarn berry(2 ~ 3)

比較新的點(diǎn)就是 pnp 模式,pnp 模式是為了解決 node_modules 臃腫、復(fù)雜度過(guò)高的問(wèn)題而來(lái)的,但是比較激進(jìn),所以很難支持現(xiàn)有的項(xiàng)目。不過(guò) yarn 3 基本上把各個(gè)包管理的功能都支持了(nodeLinker 配置),從功能上可以算是最多,比較復(fù)雜,概念好多。吸收了部分競(jìng)爭(zhēng)對(duì)手的優(yōu)點(diǎn),并開(kāi)辟了許多有趣的功能特性。

  • Pnpm

全稱是 “Performant NPM”,即高性能的 npm。

如它官方文檔介紹的所說(shuō):“Saving disk space and boosting installation speed”,Pnpm 是一個(gè)能夠提高安裝速度、節(jié)省磁盤(pán)空間的包管理工具,并天然支持 Monorepo 的解決方案。除此之外,它也解決了很多令人詬病的問(wèn)題,其中,比較經(jīng)典的就是 Phantom dependencies(幻影依賴)。

pnpm 的優(yōu)勢(shì):

  1. 安裝依賴速度快,軟/硬鏈接結(jié)合
  2. 安裝過(guò)的依賴緩存全局復(fù)用,緩存邏輯基于文件塊,不同版本的依賴可以只緩存 diff
  3. 自身支持 workspaces 相關(guān)

推薦導(dǎo)讀:

pnpm官網(wǎng):https://pnpm.io/zh/

?包版本方案

  • Lerna

Lerna 是一個(gè)管理工具,用于管理包含多個(gè)軟件包(package)的 Javascript 項(xiàng)目。它可以優(yōu)化使用 git 和 npm 管理多包存儲(chǔ)庫(kù)的工作流程。Lerna 主流應(yīng)用在處理版本、構(gòu)建工作流以及發(fā)布包等方面都比較優(yōu)秀,既兼顧版本管理,還支持全量發(fā)布和單獨(dú)發(fā)布等功能。在前端領(lǐng)域,它是最早出現(xiàn)也是相當(dāng)長(zhǎng)一段時(shí)間 monorepo 方案的事實(shí)標(biāo)準(zhǔn),具有統(tǒng)治地位,很多后來(lái)的工具的概念或者 workspaces 結(jié)構(gòu)都借鑒了 lerna,是 lerna 的延續(xù)。在業(yè)界實(shí)踐中,比較多的時(shí)間上,都是采用 Yarn 配合 lerna 組合完整的實(shí)現(xiàn)了 Monorepo 中項(xiàng)目的包管理、更新到發(fā)布的全流程。

后來(lái)停更了相當(dāng)長(zhǎng)一段時(shí)間,至今還是不支持 pnpm 的 workspaces(pnpm 下有 workspace:protocol,lerna 并沒(méi)有支持),與 yarn 強(qiáng)綁定。

最近由 nx 的開(kāi)發(fā)公司 nrwl 接手維護(hù),不過(guò)新增的 features 都是圍繞 nx 而加,nrwl 目前似乎還并沒(méi)有其他方向的 bug fix 或者新增 features 的計(jì)劃。不過(guò)社區(qū)也出現(xiàn)了 lerna-lite ,可以作為 lerna 長(zhǎng)久停滯的補(bǔ)充和替代,主要的新 features 就是支持在 pnpm workspaces。

推薦導(dǎo)讀:

  1. lerna:https://www.lernajs.cn/
  2. lerna-lite:https://Github.com/ghiscoding/lerna-lite
  • Changesets

Changesets 是一個(gè)用于 Monorepo 項(xiàng)目下版本以及 Changelog 文件管理的工具。在 Changesets 的工作流會(huì)將開(kāi)發(fā)者分為兩類人,一類是項(xiàng)目的維護(hù)者,還有一類為項(xiàng)目的開(kāi)發(fā)者,開(kāi)發(fā)者在Monorepo項(xiàng)目下進(jìn)行開(kāi)發(fā),開(kāi)發(fā)完成后,給對(duì)應(yīng)的子項(xiàng)目添加一個(gè)changeset文件。項(xiàng)目的維護(hù)者后面會(huì)通過(guò)changeset來(lái)消耗掉這些文件并自動(dòng)修改掉對(duì)應(yīng)包的版本以及生成CHANGELOG文件,最后將對(duì)應(yīng)的包發(fā)布出去。

?包構(gòu)建方案

  • Turborepo

上述提到傳統(tǒng)的 Monorepo 解決方案中,項(xiàng)目構(gòu)建時(shí)如果基于多個(gè)應(yīng)用程序存在依賴構(gòu)建,耗時(shí)是非??膳碌?。Turborepo 的出現(xiàn),正是解決 Monorepo 慢的問(wèn)題。

Turborepo 是一個(gè)用于 JavaScript 和 TypeScript 代碼庫(kù)的高性能構(gòu)建系統(tǒng)。通過(guò)增量構(gòu)建、智能遠(yuǎn)程緩存和優(yōu)化的任務(wù)調(diào)度,Turborepo 可以將構(gòu)建速度提高 85% 或更多,使各種規(guī)模的團(tuán)隊(duì)都能夠維護(hù)一個(gè)快速有效的構(gòu)建系統(tǒng),該系統(tǒng)可以隨著代碼庫(kù)和團(tuán)隊(duì)的成長(zhǎng)而擴(kuò)展。

推薦導(dǎo)讀:https://vercel.com/blog/vercel-acquires-turborepo

  • Nx

定位上是 Smart, Fast and Extensible build system,出現(xiàn)得比較早,發(fā)展了挺久,功能特別多,基本上 cover 了各種應(yīng)用場(chǎng)景,文檔也比較詳細(xì),是現(xiàn)在幾個(gè) Monorepo 工具里比較接近完整的解決方案和框架的。

最近的話他們也接手了 lerna 的維護(hù),不過(guò)給 lerna 加的東西都是圍繞 nx 而來(lái)。

推薦導(dǎo)讀:https://nx.dev/

?其它生態(tài)工具

  • Bolt

和 lerna 類似,更像是一個(gè) Task Runner,用于執(zhí)行 workspaces 下的各種 script,用法上和 npm 的 workspaces 類似,已經(jīng)停更一段時(shí)間。

  • Preconstruct

Monorepo 下統(tǒng)一的 dev/Build 工具。亮點(diǎn)是 dev 模式使用了執(zhí)行時(shí)的 require hook,直接引用源文件在運(yùn)行時(shí)執(zhí)行轉(zhuǎn)譯(babel),不需要在開(kāi)發(fā)時(shí) watch 產(chǎn)物實(shí)時(shí)構(gòu)建,調(diào)試很方便。用法上比較像 parcel、microbundle 那樣 zero-config bundler,使用統(tǒng)一的 package.json 字段來(lái)指定輸出產(chǎn)物,缺點(diǎn)是比較死板,整個(gè)項(xiàng)目的配置都得按照這種配置方式,支持的選項(xiàng)目前還不多,不夠靈活。

  • Rushstack

體感上比較像 Lerna Turborepo 各種東西的工具鏈,比較老牌,但是沒(méi)用過(guò),也很少見(jiàn)到有用這個(gè)的。

  • Lage

Microsoft 出的,定位上是一個(gè) Task Runner in JS Monorepos ,亮點(diǎn)是 pipeline 的任務(wù)模式,構(gòu)建產(chǎn)物緩存,遠(yuǎn)程緩存等。

Monorepo 工具鏈選用

最終我們選用了 pnpm lerna-lite turborepo

?Pnpm

pnpm 的依賴全局緩存(global store and hard link)與安裝方式即是天然的依賴共享,相同版本的依賴只會(huì)安裝一次,有效地節(jié)約空間以及節(jié)省安裝時(shí)間,在 monorepo 場(chǎng)景下十分切合。

?Lerna-lite

pnpm 推薦的方案其實(shí)是 changesets,但是 changesets 的發(fā)版流程更貼近 Github Action Workflow,以及 打 changeset 然后 version 的概念和流程相對(duì) lerna 會(huì)復(fù)雜一些。

不直接使用 lerna 的原因是 lerna 并不支持 pnpm 的 workspace protocol 。

同時(shí) lerna 比較久沒(méi)有更新,雖然最近被 nx 的組織 nrwl 接管了,但是 nrwl 只擴(kuò)展了針對(duì) nx lerna 場(chǎng)景的功能,并沒(méi)有對(duì) lerna 的其他功能進(jìn)行維護(hù),所以社區(qū)里出現(xiàn)了 lerna-lite,真正意義上的延續(xù)了 lerna 的發(fā)展,目前比較有用的新功能是其 publish 命令支持了 pnpm 的 workspace protocol (workspace:),支持了 pnpm workspace 下 lerna 的發(fā)布流程。

?Turborepo

如果有高速構(gòu)建緩存需求則使用 turborepo。Turborepo 的基本原則是從不重新計(jì)算以前完成的工作,Turborepo 會(huì)記住你構(gòu)建的內(nèi)容并跳過(guò)已經(jīng)計(jì)算過(guò)的內(nèi)容,把每次構(gòu)建的產(chǎn)物與日志緩存起來(lái),下次構(gòu)建時(shí)只有文件發(fā)生變動(dòng)的部分才會(huì)重新構(gòu)建,沒(méi)有變動(dòng)的直接命中緩存并重現(xiàn)日志。在多次構(gòu)建開(kāi)發(fā)時(shí),這也就意味更少的構(gòu)建耗時(shí)。

天貓校園 Monorepo 最佳實(shí)踐

?前置準(zhǔn)備

使用 pnpm 作為包管理器,全局安裝 pnpm,命令如下:

$ tnpm i -g pnpm

?創(chuàng)建 mono 倉(cāng)庫(kù)

初始化該倉(cāng)庫(kù)為 mono 倉(cāng)庫(kù)

# 初始化成功$ tree ./your-mono-projectyour-mono-project├── packages│ ├── bundles│ │ └── bundle-a│ │ └── package.json│ └── components│ └── util-a│ └── package.json├── .gitignore├── .npmrc├── abc.json├── lerna.json├── package.json├── pnpm-workspace.yaml├── README.md└── turbo.json

項(xiàng)目結(jié)構(gòu)介紹:

packages/..

monorepo 各個(gè) workspaces 的目錄

abc.json

云構(gòu)建 builder 配置,與 def 平臺(tái)相關(guān)聯(lián)

lerna.json

lerna 配置,管理多個(gè)包的發(fā)布版本,變更日志生成的工具

package.json

monorepo 主目錄 package 文件,

pnpm-lock.yaml,

pnpm-workspace.yaml

pnpm lockfile(執(zhí)行 pnpm i 后生成),pnpm workspace 聲明文件

turbo.json

Turborepo 配置,主要用于產(chǎn)物緩存,構(gòu)建加速,構(gòu)建流配置。

Turborepo 地址:https://turborepo.org/

?基礎(chǔ) mono 配置設(shè)置

這里是使用 def 云構(gòu)建 builder 配置,默認(rèn)是 @ali/builder-xpos

{ "builder": "@ali/builder-xpos"}

lerna 的配置,包括 packages 的范圍,publish,version 的命令配置,還有指定 npmClient 為 pnpm,這里需要使用 @lerna-lite/cli

{ "packages": [ "packages/*/*" ], "command": { "publish": { "conventionalCommits": true, }, "version": { "conventionalCommits": true, "syncWorkspaceLock": true } }, "version": "independent", "npmClient": "pnpm"}

配置簡(jiǎn)要說(shuō)明:

packages:指定組件包所在文件夾,限定了packages的管理范圍。我們這里調(diào)整為「packages/*/*」。

需要配置為二級(jí)目錄。因?yàn)槲覀儼凑疹愋蛥^(qū)分各種包,然后相同類型的收納到該類型的目錄下,方便研發(fā)人員閱讀和理解,可以看到初始創(chuàng)建后,packages目錄下有二級(jí)目錄 bundles 和 components

version:配置組件包版本號(hào)管理方式,默認(rèn)是版本號(hào)。我們這里調(diào)整為「independent」。

注意 lerna 默認(rèn)使用的是集中版本,所有的package共用一個(gè)version,如果需要packages下不同的模塊 使用不同的版本號(hào),需要配置Independent模式

command:command主要是配置各種lerna指令的參數(shù),這些命令可以通過(guò)命令行配置,也可以在配置文件中配

更多配置參考,https://github.com/lerna/lerna#lernajson

pnpm-workspace.yaml 的配置

packages: - packages/*/*

同lerna配置說(shuō)明

turborepo 主要用于產(chǎn)物緩存,構(gòu)建加速

{ "$schema": "https://turborepo.org/schema.json", "pipeline": { "build": { "dependsOn": ["^build"], "inputs": [ "src/**/*" ], "outputs": [ "es/**", "lib/**", "build/**" ] } }}

?入駐組件

  1. 選擇需要入駐的組件
  2. 判斷該組件屬于哪個(gè)mono分類(如 bundles、components 等)
  3. 切換到相應(yīng)的分類目錄,如 /packages/components
  4. 入駐組件代碼到該目錄下
    1. 通過(guò)手動(dòng)復(fù)制代碼,或者 git clone 方式都可以
  1. 清除入駐組件的 .git
    1. 在其目錄 /packages/components/your-component 下執(zhí)行,rm -rf .git
    2. 只保留 mono 的 git 管理能力即可(重點(diǎn)注意!如果沒(méi)有清除,則無(wú)法被mono的gitdiff檢測(cè)到)
  1. 增加/替換 入駐組件中 package.json 中 build script 為 gulp build
    1. "build": "gulp build"
    2. 注意同時(shí)請(qǐng)保證 組件的構(gòu)建使用的是相同版本的 gulp,如如意pos統(tǒng)一使用的是 gulp@4
  1. 在 mono目錄下(或者 /packages/components/your-component 目錄下,或者mono中任意位置都可),執(zhí)行 pnpm i
    1. 執(zhí)行成功后,pnpm-lock.yaml 文件有對(duì)應(yīng)的更新,即入駐成功
  1. 不需要特別關(guān)注相互依賴問(wèn)題
    1. 入駐之后 pnpm 將會(huì)自動(dòng)識(shí)別本地的 package 變動(dòng)
    2. 仔細(xì)查看 pnpm-lock.yaml 中,本倉(cāng)庫(kù)依賴的組件的版本號(hào),會(huì)變成 link: ../xxx

?本地開(kāi)發(fā)關(guān)聯(lián)

入駐好組件后,就可以盡情地開(kāi)發(fā)編碼了。

正常情況下,組件通過(guò) npm link(tnpm link、pnpm link 相似)方式進(jìn)行本地開(kāi)發(fā)關(guān)聯(lián)。組件體量大時(shí),這樣就非常的麻煩,因此我們升級(jí)了本地關(guān)聯(lián)的方式,通過(guò)webpack alias 方式,將應(yīng)用的依賴路徑與本地mono倉(cāng)庫(kù)中的組件進(jìn)行替換,然后通過(guò)選擇的方式實(shí)現(xiàn)關(guān)聯(lián)。

操作步驟:

  1. 應(yīng)用中配置依賴的mono組件庫(kù)(構(gòu)建器中實(shí)現(xiàn))
  2. module.exports = {
    'monorepo': 'xpos-ruyi-mono'
    }
  3. 啟動(dòng)本地構(gòu)建
  4. $ def dev –mono
  5. 選擇需要關(guān)聯(lián)的本地 mono 組件(構(gòu)建器CLI自行實(shí)現(xiàn)即可)
  6. 啟動(dòng)完成,就可以開(kāi)心編碼了
  7. 不再需要進(jìn)行手動(dòng) link 的操作

核心代碼簡(jiǎn)要如下:

// relateLocalMonoLinks.jsmodule.exports = async function relateLocalMonoLinks(def) { try { const localLinks = await getAppDepsLocalMainJsPath() // 獲取app中的依賴項(xiàng),獲取本地mono中的組件,匹配存在的組件,并將包名映射為本地組件入口文件的路徑 const cacheLinks = await getCacheLinks() // 獲取緩存過(guò)的關(guān)聯(lián)中的本地依賴 const chosenLinks = await getChosenLinks(localLinks, cacheLinks) // 用戶自行選擇需要關(guān)聯(lián)的本地依賴 await updateChosenLinks(chosenLinks) // 編寫(xiě) local-links.js 依賴文件,供 webpack 構(gòu)建時(shí) alias 使用 } catch (e) {}}

// webpack.config.jsconst baseConfig = { resolve: { alias: getAliasMap() }}// lib/util.jsfunction getAliasMap() { let aliasMap = { '@': path.resolve(cwd, './src') } const pjson = require(path.resolve(cwd, 'package.json')) Object.keys(pjson.dependencies || {}) .map(packageName => { return { key: packageName, value: path.resolve(cwd, 'node_modules', packageName) } }) .forEach(({ key, value }) => (aliasMap[key] = value)) const isLocalDev = process.env.IS_LOCAL_DEV if (isLocalDev) { try { let links = require(path.resolve(cwd, 'local-links')) || {} aliasMap = Object.assign({}, aliasMap, links) } catch (e) { console.log('invalid local links') } } return aliasMap}

?構(gòu)建 && 發(fā)布組件

  1. 如果有引入新的依賴,請(qǐng)先執(zhí)行 pnpm i
  2. 開(kāi)發(fā)完成后,正常在 mono 倉(cāng)庫(kù)下,進(jìn)行 git 提交
  3. 通過(guò)上述工具鏈實(shí)現(xiàn)的構(gòu)建器進(jìn)行發(fā)布
  1. 發(fā)布成功后,會(huì)根據(jù)代碼提交,進(jìn)行增量改動(dòng)判斷,產(chǎn)出對(duì)應(yīng)改動(dòng)的組件升級(jí)包
  2. 將相應(yīng)的包的版本號(hào),配置到應(yīng)用的項(xiàng)目中使用即可

原文鏈接:https://mp.weixin.qq.com/s/N0CZABDD0TKTmdljH3y74A

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。

(0)
上一篇 2023年8月25日 上午8:58
下一篇 2023年8月25日 上午9:14

相關(guān)推薦