Monorepo 在網易的工程改造實踐

大家好,我是若川。我持續組織了近一年的源碼共讀活動,感興趣的可以?點此掃碼加我微信?lxchuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信進群。



背景

目前云音樂內有多個RN收銀臺場景分布在不同的工程,比如頁面收銀臺,浮層收銀臺,個性收銀臺等,后續可能還會有別的收銀臺場景。

那在開發過程中存在的問題就是每個收銀臺的核心邏輯如商品展示、支付方式展示、下單購買等邏輯都大致相同,而每次有修改或者新增需求的時候都需要開發多次,重復代碼較多效率低下。

雖然可以通過發npm包的形式復用代碼,但是有些組件和代碼塊不太好抽成包,還會帶來調試麻煩,發版等問題。所以為了提高代碼復用,提高開發效率,我們希望能夠在一個倉庫內包含多個工程,也就是Monorepo形式。

Monorepo

什么是Monorepo

Monorepo是一種將多個項目的代碼集中在同一個倉庫中的軟件開發策略,與之對立的是傳統的MultiRepo策略,即每個項目在一個單獨的倉庫進行管理。afbbfc48177aa2bd1e3b2246b283fba9.png目前像社區內一些著名的開源項目Babel、React和Vue等都是用這種策略來管理代碼。

Monorepo解決的問題

要想知道Monorepo解決了哪些問題與其優勢,我們先來看下MultiRepo存在的問題。

當我們在MultiRepo下兩個工程之前需要復用一些代碼時,往往會采用抽成npm包的形式。但當npm包有改動時我們需要做以下事情:

  1. 修改npm包代碼,通過npm link與兩個工程調試

  2. 調試完成后發布新版本

  3. 兩個工程升級npm包新版本,再進行發布

整個流程可以看出還是比較繁瑣的,那如果是在Monorepo下我們可以將公共部分抽成一個workspace,我們的兩個工程分別也是workspace可以直接引用公共workspace的代碼,工具會幫我們管理這些依賴關系,開發過程中調試起來也非常方便,而且不涉及到發包,版本依賴等,公共部分代碼改動完成后兩個工程部署即可。

從上述可以看出Monorepo主要有代碼復用容易調試方便簡化依賴管理等優點,這也是我們選擇這個方案的原因。

當然Monorepo也有一些缺點,比如:倉庫體積大、工程權限不好控制等。所以不管是Monorepo還是MultiRepo都不是完美的方案,只要能解決當下的問題就是好方案。

Monorepo的工具

目前業界最常見的實現monorepo工具和方案有lerna、yarn workspace和pnpm等。

Lerna

lerna是一個通過使用git和npm來優化多包倉庫管理工作流的工具,多用于多個npm包相互依賴的大型前端工程,提供了許多CLI命令幫助開發者簡化從npm開發,調試到發版的整個流程。但是目前已官宣停止維護。

Pnpm

pnpm是一個新型的依賴包管理工具,并支持workspace功能,它的優勢主要是通過全局存儲和硬鏈接來節省磁盤空間并提升安裝速度,通過軟鏈接來解決幻影依賴問題。但是RN的構建工具metro對于符號鏈接的解析還存在問題需要改造,成本較大。

Yarn workspace

yarn workspace是yarn提供的Menorepo依賴管理機制,是一個底層的工具,用于在倉庫根目錄下管理多個package的依賴,天然支持hoist功能,安裝依賴時會將packages中相同的依賴提升到根目錄,減少重復依賴安裝。workspace之間的引用在依賴安裝時通過yarn link建立軟鏈,代碼修改時可以在依賴其的workspace中實時生效,調試方便。

通常業界主流方案是lerna + yarn worksapce,lerna負責發布和版本升級,yarn workspace負責依賴管理。因為我們的RN工程是頁面工程,不涉及到發npm包,而且需要依賴提升的功能(這個后面會說到),所以最終采用yarn worspace方案。

Metro

在工程改造之前,我們先了解下ReactNative的構建工具Metro。

Metro在構建過程中主要會經歷三個階段:

  1. Resolution:此階段Metro會從入口文件出發分析所依賴的模塊生成一個所有模塊的依賴圖,主要是使用jest-haste-map這個包做依賴分析。這個階段和Transformation階段是并行的;

  2. Transformation:此階段主要是將模塊代碼轉換成目標平臺可識別的格式;

  3. Serialization:此階段主要是將Transform后的模塊進行序列化,然后組合這些模塊生成一個或多個Bundle

jest-haste-map是單元測試框架Jest的其中一個包,主要用來獲取監聽的所有文件及其依賴關系。

工程改造

接下來就是對工程的改造,首先我們將兩個RN工程放在一個工程下,并按照yarn workspace的方式進行配置,然后通過腳手架(這里使用的是公司內部自研的腳手架)分別創建app-a和app-b兩個RN工程,如下所示

rn-mono
|--?apps|--?app-a|--?app-b
|--?package.json
//?package.json
{..."workspaces":?{"packages":?["apps/*"]},"private":?true
}

接著我們運行

yarn?install

發現packages中相同的依賴都會安裝在根目錄下的node_modules中,接著我們用如下啟動app-a或app-b

yarn?workspace?app-a?run?dev

這時如果你的app-a工程中的dev啟動命令是用相對路徑的方式可能會出現命令找不到的情況,比如

//?app-a/package.json
{//?這里的react-native是安裝在了根目錄,所以會找不到命令,需要修改下路徑"script":?{"dev":?"node?./node_modules/react-native/local-cli/cli.js?start"}
}

那如果是調用./node_modules/.bin中的命令則不需要,因為在安裝依賴的時候packages中.bin中的命令會有個軟鏈指向根目錄下./node_modules/.bin中的命令。啟動成功后,這時打開頁面會報如下錯誤:

9f0a2e85043e7774afef0eba795acbc4.png
Untitled

這是因為jest-haste-map在做依賴分析時通過metro.config.js中的watcherFolders配置項來指定需要監聽變化的文件目錄。

ce0bbf6ecf95de40afa5dc9b532ecb5c.png
Untitled

watcherFolders默認值為工程根目錄,此時也就是app-a中目錄,但是我們的模塊都是安裝在根目錄下,所以會找不到。我們需要修改下metro.config.js中watcherFolders

//?app-a/metro.config.jsconst?path?=?require('path');module.exports?=?{watchFolders:?[path.resolve(__dirname,?'../../node_modules')],
};

修改完成后我們重新啟動,再打開頁面后發現已經可以正常打開了,同樣的方式app-b也可以正常運行。

但是我們對工程進行monorepo改造的目的是為了抽離公共組件,復用代碼。所以我們在根目錄下建立個common的文件夾來存放公共部分,此時根目錄下的pacage.json中的packages和apps里每個app的metro.config.js中watchFolder配置都需要加入common

rn-mono
|--?common|--?package.json
|--?apps|--?app-a|--?app-b
|--?package.json
//?package.json
{..."workspaces":?{"packages":?["apps/*","common"],},"private":?true
}//?apps/app-a/metro.config.js
const?path?=?require('path');module.exports?=?{watchFolders:?[path.resolve(__dirname,?'../../node_modules'),?path.resolve(__dirname,?'../../common')],
};

接著在common中添加個Button組件,package.json中添加相應的依賴,版本要和apps中對應依賴的版本保持一致

{..."dependencies":?{"react":?"16.8.6","react-native":?"0.60.5",},
}

然后yarn install重新安裝下,這時在根目錄的node_modules下就可以看到common模塊軟鏈到了common目錄,所以在app-a中引入common時就可以像npm包一樣直接引入,同樣app-b也可以。

import?common?from?'common';

到這里我們RN工程的monorepo改造也基本完成了。

依賴提升

這里解釋下為什么需要依賴提升。

我們先來看下取消依賴提升會有什么問題,可以在根目錄中的package.json中nohoist配置來指定不需要提升安裝到根目錄的模塊

{..."workspaces":?{"packages":?["apps/*","common"],"nohoist":?["**react**"],},"private":?true
}

然后重新yarn install,啟動app-a后會發現報如下錯誤

72a3eba5bde5479da87989368084bfee.png
Untitled

這是因為有些模塊jest-haste-map在做依賴分析生成dependency graph時發現在兩個不同的目錄下會產生命名沖突,導致報錯。所以我們需要依賴提升,將所用到的相同依賴安裝到根目錄,這樣只會安裝一次。

相同依賴的版本保持一致

雖然有了依賴提升但如果每個packages中相同依賴的版本不一致,同樣會導致相同的依賴會安裝多次的情況出現,根目錄和對應的package中都會有。這種情況除了會產生以上問題外還有可能產生其他潛在的問題,比如依賴客戶端的第三方模塊,如果存在多個版本在bundle執行時會多次注冊組件導致組件注冊失敗,在調用時會發生找不到組件的報錯。

雖然可以在metro中配置blacklistRE和extraNodeModules來表明要讀取哪個位置的依賴,但是這種方式并不通用,每次在引入新的依賴時都要去配置下較為繁瑣。所以我們需要將每個packages中的依賴版本保持一致。

人為的去約定這個規則肯定是不安全的,可以開發一個依賴版本的lint檢測工具,在提交代碼的時候做強制性的檢測。

我們最終的方案是開發一個檢測腳本結合gitlab-ci在分支代碼push的時候檢測,未通過則不允許push代碼來避免風險。

//?.gitlab-ci.yml
test-dev-version:stage:?testbefore_script:-?npm?install?--registry?http://rnpm.hz.netease.comscript:-?npm?run?depVerLintonly:changes:-?"package.json"-?"packages/**/package.json"
390ffd1805cb6622d0780f2a18e2f0e1.png
Untitled

工程遷移過渡

如果是將多個正在快速迭代的工程遷移到一個Monorepo倉庫時,肯定會遇到存量開發分支代碼同步問題。比如我們要將工程A遷移到新倉庫,如果我們只是基于master分支將代碼copy到新工程,并在改造開發過程中還有組內其他同學也在基于master拉取分支做開發,并在你改造完成前開發完成合并到了master,此時你新工程的代碼是落后的,要想同步只能手動copy改動的代碼,很容易出錯。為了解決這個問題我們可以使用git subtree。git subtree允許將一個倉庫作為子倉庫嵌套在另一個倉庫里,所以這里我們可以將工程A作為一個子工程添加到Monorepo新工程對應的packages目錄下,如果有更新可以直接使用pull進行同步。

#?添加
git?subtree?add?--prefix=apps/app-a?https://github.com/xxxx/app-a.git?master?--squash#?更新
git?subtree?pull?--prefix=apps/app-a?https://github.com/xxxx/app-a.git?master?--squash

對于新工程或者新的開發分支就可以直接此工程下進行開發了。

構建

由于我們的構建機還不支持yarn,所以直接使用yarn workspace的命令是有問題的。目前的做法是將yarn作為devDependency,然后在根目錄下創建個腳本文件,將每個package的構建命令收斂在一起。結合yarn workspace的命令,這樣只需要在構建時傳入不同的package name即可。

##?scripts/build.shPLATFORM=$1
PROJECT=$2
EXEC_PARAMS=${@:2}
YARN="${PWD}/node_modules/.bin/yarn"...echo?"start?yarn?install"
${YARN}?cache?clean
${YARN}?installecho?"start?build"
echo?"${YARN}?workspace?${PROJECT}?run?build:${PLATFORM}?${EXEC_PARAMS}"
${YARN}?workspace?${PROJECT}?run?build:${PLATFORM}?${EXEC_PARAMS}
//?package.json
{..."workspaces":?{"packages":?["apps/*"],},"private":?true,"scripts":?{"build":?"./script/build.sh"},
}

比如對app-a進行構建,就可以

npm?run?build?ios?app-a##?實際上執行的是yarn?workspace?app-a?run?build:ios

總結

至此對React Native工程的monorepo改造基本完成了,對于多個功能類似的工程采用Monorepo的管理方式確實會方便代碼復用和調試,提高我們的開發效率。如果公司內部其余場景有類似的需求,未來規劃可以將其沉淀出一個腳手架。

目前對于h5工程的Monorepo方案已經較為成熟了,但是對RN工程來說由于構建機制不同無法完全適用,可參考的資料也較少。本文也是通過實踐記錄了一些踩坑經驗,如果你有更好的實踐,歡迎留言一起討論。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274429.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274429.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274429.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

這一年,Vue.js 生態開源之旅帶給我很大收獲~

大家好,我是若川。我持續組織了近一年的源碼共讀活動,感興趣的可以 點此掃碼加我微信 lxchuan12 參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試…

CSSyphus:煩躁不安的煩惱設計指南。

I’m trapped at home with my website. Or maybe it’s trapped at home with me. While some are using the weird lump of time provided by lockdown to indulge in baking, dancing, painting, singing, I’m using it to play around with code.我 被自己的網站困在家里。…

重構與臭豆腐4

重構要繼續,臭豆腐要做。   這個重構中各種提取類,方法,字段,可以方便的理解,如果使用了設置模式就更加邏輯清晰了。切東西也要講究刀法的。 重構可以方便的使用設計模式。設計模式為重構提供了目標。 比如多個if 可…

你構建的代碼為什么這么大?如何優化~

大家好,我是若川。我持續組織了近一年的源碼共讀活動,感興趣的可以 點此掃碼加我微信 lxchuan12 參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試…

用戶體驗需求層次_需求和用戶體驗

用戶體驗需求層次Shortly after the start of 2020 I led the development of a new website, and it went live in August. A week before the deployment, I paused development and took a step back in order to write about the project. Taking that pause, that step ba…

VMwareWorkstation設置U盤啟動(或U盤使用)

最近在工作中,經常要和LINUX部署打交道,一般在生產環境部署之前需要在自己的機器上進行測試。比如使用U盤安裝操作系統等。 在機器上安裝了VMware Workstation9.0,運行多個測試虛擬機。理由所當然的要使用此做一些操作系統部署,…

類從未使用_如果您從未依賴在線銷售,如何優化您的網站

類從未使用初學者指南 (A beginner’s guide) If you own a small business with a store front, you might have never had to rely on online sales. Maybe you’re a small clothing store or a coffee shop. You just made that website so people could find you online, …

狼書三卷終大成,狼叔親傳Node神功【留言送書】

大家好,我是若川。之前送過N次書,可以點此查看回饋粉絲,現在又和博文視點合作再次爭取了幾本書,具體送書規則看文末。眾所周知,我在參加掘金人氣作者打榜活動(可點擊跳轉),需要大家投…

entity framework5 sqlserver2005 事務(TransactionScope)報未啟用MSDTC錯誤解決辦法

詳情請看:http://stackoverflow.com/questions/12809958/ef-how-do-i-call-savechanges-twice-inside-a-transaction using (var transaction new TransactionScope()) {// Do somethingdb.SaveChanges();// Do something elsedb.SaveChanges();tramsaction.Comple…

程序詳細設計之代碼編寫規范_我在不編寫任何代碼的情況下建立了一個設計策劃網站

程序詳細設計之代碼編寫規范It’s been just over a month since MakeStuffUp.Info — my first solo project as an independent Creator; was released to the world. It was not a big project or complicated in any way, it’s not even unique, but I’m thrilled where …

偷偷告訴你們一個 git 神器 tig,一般人我不告訴TA~

大家好,我是若川。眾所周知,我參加了掘金創作者人氣作者投票活動,最后3天投票。今天可投28票,明天32票,后天36票(結束)。投票操作流程看這里:一個普通小前端,將如何再戰掘…

DAO層使用泛型的兩種方式

package sanitation.dao;import java.util.List;/** * * param <T>*/public interface GenericDAO <T>{/** * 通過ID獲得實體對象 * * param id實體對象的標識符 * return 該主鍵值對應的實體對象*/ T findById(int id);/** * 將實體對象持…

將是驚心動魄的決戰~

大家好&#xff0c;我是若川。一個和大家一起學源碼的普通小前端。眾所周知&#xff0c;我參加了掘金人氣創作者評選活動&#xff08;投票&#xff09;&#xff0c;具體操作見此文&#xff1a;一個普通小前端&#xff0c;將如何再戰掘金年度創作者人氣榜單~。最后再簡單拉拉票吧…

圖書漂流系統的設計和研究_研究在設計系統中的作用

圖書漂流系統的設計和研究Having spent the past 8 months of my academic career working co-ops and internships in marketing & communication roles, my roots actually stem from arts & design. Although I would best describe myself as an early 2000s child…

黑馬-程序員C#泛型簡介

---------------------- Windows Phone 7手機開發、.Net培訓、期待與您交流&#xff01; ---------------------- 泛型&#xff1a;通過參數化類型來實現在同一份代碼上操作多種數據類型。利用“參數化類型”將類型抽象化&#xff0c;從而實現靈活的復用。 例子代碼&#xff1a…

西里爾字符_如何設計西里爾字母?(Nje),?(Lje),?(Tshe)和?(Dje)

西里爾字符This article is about how to design Cyrillic characters ?, ?, ?, and ? (upright caps and lowercase; italics are not covered here). They are often problematic since they are Cyrillic, but not found in the Russian alphabet, so there is no much …

學習 vuex 源碼整體架構,打造屬于自己的狀態管理庫

前言這是學習源碼整體架構第五篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。其余四篇分別是&#xff1a;學習 jQuery 源碼整體…

VMware workstation 8.0上安裝VMware ESXI5.0

首先&#xff0c;在VMware的官網上注冊&#xff0c;下載VMware ESXI的安裝包vmware&#xff0d;vmvisor&#xff0d;installer&#xff0d;5.0.0&#xff0d;469512.x86_64.iso&#xff0c;它是iso文件&#xff0c;刻盤進行安裝&#xff0c;安裝過程中&#xff0c;會將硬盤全部…

最新ui設計趨勢_10個最新且有希望的UI設計趨勢

最新ui設計趨勢重點 (Top highlight)Recently, I’ve spent some time observing the directions in which UI design is heading. I’ve stumbled across a few very creative, promising and inspiring trends that, in my opinion, will shape the UI design in the nearest…

Lists

動態數組&#xff0c;可以存儲不同數據類型 >>> a [spam, eggs, 100, 1234] >>> a [spam, eggs, 100, 1234] 和string一樣&#xff0c;支持索引&#xff0c;&#xff0c;* >>> a[0] spam >>> a[3] 1234 >>> a[-2] 100 >>&…