我前段时间参与了一个react
为主的大前端项目,覆盖Web、Android、Ios三个平台。由于整个业务逻辑侧重在手机端,且Web端也是到了项目中期才开始启动,我在搭底层框架时就没有考虑用类似react-native-web
这样的框架把三端统一,而是分别以react-native
和react
为主起了两个项目来应对。
由于无论哪一端,调用的后端微服务集群都是同一个,导致两个项目中还是不可避免的出现了一些重复逻辑,我尝试了封装成npm
包来重用逻辑,但仅限于哪些通用且变化较少工具类代码,对于变化频繁的业务逻辑代码,封装出来的npm
包时不时就要更新版本,且抽出到项目之外也不易开发和调试,用起来格外麻烦,得不偿失。
最近尝试了lerna
,惊喜的发现它不但能解决当时项目的痛点,还能额外带来一些多项目管理相关的好处。
lerna
的名字来源于希腊神话中的九头蛇海德拉(Lernaean Hydra),长相可参考项目logo,拿它形容多项目工程是再贴切不过了。
lerna
的引入比想象中简单,其实,与其说引入lerna
,倒不如说是导入到lerna
更合适,因为具体的做法是通过命令行创建了一个新的lerna
项目,然后把所有项目导入进去。而且在导入的同时,每个项目的git提交记录也都合并在了一起。
lerna init lerna import 你本地的项目路径
每个被导入的项目都会被存放在根路径的packages目录下,下面是我demo项目的截图。
引入lerna
后,第一件事就是要处理安装依赖的问题,我们需要用lerna add
命令来代替我们习惯的npm
或yarn
,比如说现在给截图中的rntest
项目安装lodash
,就要执行下面的命令,该命令的底层实现也还是调用哦npm install
之类的命令。
lerna add lodash --scope=rntest
不过,执行后你会发现其他项目中package-lock.json都发生了变化,让人非常困惑,这背后的原因是跟添加依赖后自动执行的安装命令lerna bootstrap
有关。
lerna
可以通过lerna bootstrap
一行命令安装所有子项目的依赖包,而且在安装依赖时还有依赖提升功能,所谓“依赖提升”,就是把所有项目npm依赖文件都提升到根目录下,这样能避免相同依赖包在不同项目安装多次。比如多个项目都用了redux
,通过依赖提升,只需要下载一次放到根目录的node_modules目录下,就可供其他所有项目来使用。不过,需要额外的参数--hoist
让依赖提升生效。
lerna bootstrap --hoist
但是自动执行lerna bootstrap
命令是不带依赖提升参数的,这就导致上面每个项目的lock文件都会被修改的原因。
当然,要解决这个问题也容易,可以通过lerna
的配置来避免npm对lock文件的修改即可,写法如下:
lerna
默认使用npm
作为安装依赖包工具,但也可以选择其他工具。yarn
在1.0版本之后提供了workspaces的功能,该功能从更底层的地方提供了依赖提升,做的事情跟lerna
如出一辙。把它跟lerna
放在一起看,简直就像是为lerna
量身定做一样。因此,推荐在lerna中搭配yarn一起使用。
把npm替换成yarn只需在lerna的配置文件添加两行代码即可,配置完以后立刻顺畅百倍。
在我参与的这个大前端项目里,多端之间代码重复的部分包含redux
中的业务逻辑、http请求的处理、代码规范工具的检查、git
钩子中的自定义脚本等等。在lerna
架构下,前两者可直接抽取到一个独立的项目,然后被其他项目引用,比如在我的demo中,可以像其他依赖包一样直接引入shared
项目, lerna会自动识别并把它导向内部项目。
import shared from 'shared'
这跟直接封装成npm
包的一大区别就是实时更新,修改立刻可见,就像在同一个项目一样,不影响开发和调试。
我尝试把处理git
钩子的工具husky
安装到了根目录,触发的事件和自定义脚本能覆盖到每个项目,给这部分代码重用带来了极大遍历。比如,不少项目会添加自定义脚本来约束git commit
提交时的消息描述,在lerna
架构下,只需写一次即可。
那些常常需要在根目录添加配置文件的第三方依赖,比如eslint
、prettier
、babel
等,在lerna
中无法简单粗暴的提升合并到一处。因此,对于eslint
这种前端开发已不可或缺的工具,可以尝试将所有配置项抽取到独立项目,然后安装第三方依赖的方式引入,类似eslint-config-airbnb
,eslint-config-prettier
,eslint-config-google
这样。
不得不说,即便不用lerna
框架我们也可以这么做,只不过在lerna
框架下修改立刻可见,方便了调试和开发。
多项目的结构无疑给CI/CD
带来挑战,好在主流的CI框架能完美解决这个问题。比如在gitlab上,only/changes
参数完全满足了我们的需求,让我们可以为每一个子项目设置单独的pipeline,比如现在我们设置一个pipeline,只当rntest项目下的文件被修改时才会触发:
在lerna框架下,所有项目都合在一个工程里,但CI/CD
并不必这样。通过把脚本中的关键参数配置到CI/CD
的项目内里,共用同一份.gitlab-ci.yml
文件,从而能够实现每个子项目对应一个独立的CI/CD
项目,最终CI/CD
结构如下图:
由于所有的项目都归并到了一个lerna
工程下,一旦有了访问权限意味着你可以修改所有子项目中的代码,在实际的开发工作中多多少少会带来一些麻烦。比如说,开发web和开发mobile平台的是两个不同的团队,假如我作为web组的一员,一不小心修改了或删除了mobile项目的文件该怎么办?假如不加入任何限制,这种事情迟早会发生,我想这可能是lerna
框架与生俱来的的痛点。
不幸的是,在lerna
框架下,gitlab或github这类第三方代码托管平台,本身的权限管理功能无法解决这问题。但好在有其他工具的帮助可以缓解这种痛,我尝试用来约束开源贡献者提交PR规范的工具dangerjs
来完成权限分隔,利用的信息就是当前gitlab账号的用户名,看起来效果还不错。
这个工具会在合并MR的时候,判断出我gitlab账号没有权限修改rntest子项目内的文件,因此禁止合并此MR,并将这些信息自动添加到MR的评论里。当然,脚本判断的逻辑比较简陋,仅用来做演示用,而关于dangerjs
的部分我会另写一篇文章详细介绍。
大前端项目将会是前端发展的趋势,如何更好的管理大前端项目是每一位前端开发躲不开的课题。lerna
框架通过合而为一的理念提供了一种解决方案,通过扬长避短,我们可以发挥出lerna
的最大效用。假如你还没有用过,也许,下一个项目就可以试试看。
相关资料lerna的github地址
文章中的demo