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.mzph.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.我 被自己的网站困在家里。…

你构建的代码为什么这么大?如何优化~

大家好,我是若川。我持续组织了近一年的源码共读活动,感兴趣的可以 点此扫码加我微信 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次书,可以点此查看回馈粉丝,现在又和博文视点合作再次争取了几本书,具体送书规则看文末。众所周知,我在参加掘金人气作者打榜活动(可点击跳转),需要大家投…

程序详细设计之代码编写规范_我在不编写任何代码的情况下建立了一个设计策划网站

程序详细设计之代码编写规范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…

西里尔字符_如何设计西里尔字母Њ(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…

学习 axios 源码整体架构,打造属于自己的请求库

前言这是学习源码整体架构系列第六篇。整体架构这词语好像有点大&#xff0c;姑且就算是源码整体结构吧&#xff0c;主要就是学习是代码整体结构&#xff0c;不深究其他不是主线的具体函数的实现。本篇文章学习的是实际仓库的代码。学习源码整体架构系列文章如下&#xff1a;1.…

404 错误页面_如何设计404错误页面,以使用户留在您的网站上

404 错误页面重点 (Top highlight)网站设计 (Website Design) There is a thin line between engaging and enraging when it comes to a site’s 404 error page. They are the most neglected of any website page. The main reason being, visitors are not supposed to end…

学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理

前言这是学习源码整体架构系列第七篇。整体架构这词语好像有点大&#xff0c;姑且就算是源码整体结构吧&#xff0c;主要就是学习是代码整体结构&#xff0c;不深究其他不是主线的具体函数的实现。本篇文章学习的是实际仓库的代码。学习源码整体架构系列文章如下&#xff1a;1.…

公网对讲机修改对讲机程序_更少的对讲机,对讲机-更多专心,专心

公网对讲机修改对讲机程序重点 (Top highlight)I often like to put a stick into the bike wheel of the UX industry as it’s strolling along feeling proud of itself. I believe — strongly — that as designers we should primarily be doers not talkers.我经常喜欢在…