かかってこいよ NakamuraEmi
  1. 1 かかってこいよ NakamuraEmi
  2. 2 Time Bomb Veela
  3. 3 Last Surprise Lyn
  4. 4 Flower Of Life 发热巫女
  5. 5 Life Will Change Lyn
  6. 6 Warcry mpi
  7. 7 Libertus Chen-U
  8. 8 Hypocrite Nush
  9. 9 One Last You Jen Bird
  10. 10 BREAK IN TO BREAK OUT Lyn
2020-12-26 18:03:32

monorepo项目管理分享文字稿

组内做的一个分享,把文字稿搬过来存一下。

monorepo项目管理

第一页:已知问题

这些是我们项目目前存在的比较重要的问题。如何解决这些问题,其实无关代码细节,而是如何从宏观上组织项目间的关系。这不仅是我们项目,也是任何前端项目需要考虑的问题。随着业务复杂度的上升,前端项目不管是从代码量上,还是从依赖关系上都会爆炸式增长。对于单页面应用或者多应用项目来说,各个应用之间的关系也会更加复杂,多个应用之间如何配合,如何维护相互关系?公共库版本如何管理?如何兼顾开发体验和上线构建效率?应用复杂度的问题如何解决,随着前端业务的发展逐渐浮出水面。

应用复杂度的场景场景大概有如下几类:

  • 需要维护多个不同的包在下游业务中使用
  • 包除了被下游业务依赖之外,也可能存在不同依赖关系
  • 不同的包位于不同的仓库,并有各自的测试,构建,发布流程

如果纯粹只靠 npm install,那么所有的包都必须发布到 NPM 之后才能被其他的包更新。在「联调」这些包的时候,每次稍有更改都走一遍正式的发布流程,无疑是非常繁琐而影响效率的。

第二页: multirepo/monorepo

我们项目本身是个多仓项目,有一个专有名词概括叫multirepo。Multirepo本质上是一种理念,提倡分而治之,把不同的子项目放到不同的仓库中管理。

多仓项目有一些通用的优势和问题。

优势:让每个子团队拥有自己的 repo,可以用他们自己擅长的工具、workflow 等等。多元化能促使各个团队尽可能的提升自己的效率。

问题:子项目A依赖子项目B,如果子项目B经常改动,那么每次B改动了,都要修改A。增加很多沟通成本,如果你在你们项目用到的库中发现了一个 bug,就必须到目标库里修复它、打包、发版本,然后再回到你的库继续工作。在不同的仓库间,你不仅需要处理不同的代码、工具,甚至是不同的工作流程。甚至你只能去问维护这个仓库的人,能不能为你做出改变,然后等着他们去解决。

因为我们项目的特殊性,各个子仓库间存在比较强的关联,所以我们通过动态链接+脚本控制webpack单入口多次打包的形式,手动的维护了项目间的关系,尽量去降低了多仓间切换的开发成本。但实际上能做的有限,所以我开头提到的问题仍然存在。

目前我们的项目管理方案肯定有优化之处,所以可以看一下有没有更多可选的方案适合我们,也就是今天我要谈到的主题——和multirepo对应的另外一种管理方案monorepo。 monorepo提倡集中管理,简单来说就是把所有的子项目放到同一个项目仓库中进行管理。首先必须知道一点,multirepo和monorepo两个概念并没有好坏之分,完全是针对不同场景下的选型,各自有自己的优势。我们现在的项目用multirepo的形式管理有问题,是不是monorepo就会更好呢?其实不一定,选型需要考虑的因素有很多,只有充分了解自身条件和可选方案的优劣之处,才能选择出最合适的方案。

第三页:经典案例

那么在继续深入介绍monorepo的实践之前,我们肯定要先搞清楚,什么样的项目适合monorepo,不妨先看几个业内比较知名的典型例子。

业务实例展示-react,babel,jest

应该可以看到什么样的项目适合采用这种管理模式了,react,babel,jest的项目特点就是一个主项目,多个副项目构成的生态。像我们的中台+bml,最理想的情况肯定也是以中台项目为基础,结合bml,资产中心等等可插入项目构成一个中台生态。

第四页: why is monorepo

也可以看下babel的这篇文档:why is babel a monorepo?简单展示了下优缺点。

monorepo的优点

  • 统一的lint,构建,测试和发布流程
  • 跨模块调试方便等等

monorepo 最主要的好处是统一的工作流和Code Sharing。比如我想看一个 pacakge 的代码、了解某段逻辑,不需要找它的 repo,直接就在当前 repo就可以查询。当某个需求要修改多个 pacakge 时,不需要分别到各自的 repo 进行修改、测试、发版或者 npm link,直接在当前 repo 修改,统一测试、统一发版。只要搭建一套脚手架,就能管理(构建、测试、发布)多个 package。结合react和babel的项目结构,我们也可以参考做出我们项目的理想项目结构设计。

第五页: 理想的目录设计

.
├── packages
│      ├─ module-platform
│      │    ├─ src            # 中台的源码
│      │    └─ package.json   # 自动生成的,仅中台 的依赖,且依赖其他项目,比如bml,aic等
│      └─ module-bml
│      │    ├─ src            # bml 的源码
│      │    └─ package.json   # 自动生成的,仅bml 的依赖
          │    
│      │   
│      └─ module-common       # 所有子项目的通用依赖
│           ├─ src            
│           └─ package.json 
|
├── scripts                   # 脚本文件,对整个项目生效,包含打包配置
├── tsconfig.json             # 配置文件,对整个项目生效
├── .eslintrc                 # 配置文件,对整个项目生效
├── node_modules              # 整个项目只有一个外层 node_modules
└── package.json              # 包含整个项目所有依赖

我们现在是通过手写脚本的形式能做到部分工作,但实际上针对于monorepo已经有了比较成熟的两种工具了。刚才也看到了这些明星项目的相关工具的文件。

第六,七页: yarn workspace和 lerna介绍和关系

两者功能有重叠,比较类似prettier和eslint的关系形成互补。一般用的时候都是用各自的一部分功能。

yarn workspace是yarn的一个特性,主要是支持工作区的相互依赖,类似于npm link但只影响工作区的依赖树。 它将永远不会试图提供像 Lerna 那么高级的功能,但通过实现该解决方案的核心逻辑和 Yarn 内部的连接步骤,希望能够提供新的用法并提高性能。

lerna是babel自己用来维护monorepo并开源的一个工作流管理项目。对于lerna而言,它的主要功能是版本控制与发布。

lerna提供两类管理项目的模式:

  1. fixed/locked mode(default)

Fixed模式下,项目通过单一的版本进行控制。版本号放在项目根目录下的lerna.json文件的version这个字段。当你执行 lerna publish,如果有文件更新,它将发布新的版本。

  1. independent mode(—independent)

这种模式下,项目里的各个package独立维护自己的version,它将会忽略lerna.json中定义的version

官方推荐的做法:

Jest relies on Yarn to bootstrap the project, and on Lerna for running the pulish command(s)

一般使用上就是通过lerna init初始化一个项目,lerna create可以快速创建子项目,再通过lerna add新增依赖,可以通过scope参数决定在哪个子项目中新增依赖。 Lerna version管理版本号,lerna publish发包。

lerna的依赖提升: lerna可以通过lerna bootstrap一键安装所有子项目的依赖包,当加入hoist参数时,可以把所有相同依赖提升到根目录,避免多次安装。

高效的代码重用:重复业务逻辑可以抽取到一个独立项目然后被其他项目引用。

比较推荐的实践就是用yarn处理依赖问题,用lerna处理发布问题。

第八页: monorepo和mutirepo的开发流程区别

  1. 搭建环境

  2. clone下来后直接install即可

  3. 各个库之间存在依赖,需要通过软链接的方式管理。手动管理link有比较大的心智负担,可以通过工具实现自动化的link操作。lerna或yarn都内置了link管理。

  4. 清理环境

  5. 直接删除node_modules和编译后产物

  6. 各个package的nodule_modules都要删除,解决方案有lerna clean/yarn workspaces run clean

  7. 管理依赖

  8. yarn add/yarn remove

  9. 给某个package安装依赖:learn add --scope

这里讲一下override问题,babel,tsconfig,package

  1. 项目构建

  2. npm script 设置build命令

  3. monorepo存在相互依赖,比如b依赖a,那就a必须先构建,所以需要以一种拓扑排序的规则进行构建,lerna已经有支持的命令。

Lerna run --stream --sort build

  1. 项目测试

  2. npm script 设置script命令

    1. 使用统一的jest测试配置 2.每个package都支持 test命令通过lerna管理。前者需要保证package同构,后者不好收集代码测试率。
  3. 版本管理

  4. 版本号更新:遵循semVer语义和conventional commit规范即可,存在feat提交,更新minor版本,存在fix提交,更新patch版本,存在breaking change提交,更新大版本。

  5. Changelog: 只要commit符合规范即可自动生成
  6. git tag: 给每个版本创建一个tag方便回滚和排查问题
  7. 发版:单独生成一个commit记录标记里程碑

  8. 发包

  9. npm publish

  10. lerna publish集成了版本管理功能

第九页:monorepo的问题

  • 库体积超大,目录结构复杂度上升
  • 需要使用维护 Monorepo 的工具,这就意味着学习成本
  • monorepo 不能很好的控制各个模块的代码权限,无法做权限隔离,维护一个模块可以看到所有模块的代码。亦是缺点。可以通过danger.js自动化定制一些人工检查,判断某人提交代码是否满足某个目录的权限。

第十页:实践挑战点

项目过大,导致每次上线构建流程过长

最佳实践:如果 Dependecies 改动,那么所有依赖 Dependecies 的项目比如 App1,App2,App3 的重新构建是必要且必须的。对于一个monorepo项目来说,开发阶段可以单独构建打包,但整个上线时,需要全量构建。

核心:增量构建

变更检测

A => B => C,如果A代码变化,需要build C时,即使C代码没有任何更改也要重新构建

不同分支版本号管理

在两个分支中,修改了不同的包,并且发布分支上的包版本,在合并至主分支时,版本冲突会变的混乱,必须小心翼翼手动处理这些版本号变更。

出于代码的分支管理考虑,monorepo 不得不采用一套复杂的分支管理机制来处理模块间的代码关系,需要考虑到各模块的开发进度,整体发布到开发环境,测试环境,以及生产环境的隔离等一系列问题。所以需要设计完善的分支管理方案,并做规约,很显然,人为操作的规约是最难的,难免误操作。这一块的解决也有很大难度。

第十一,十二页: 和其他技术点的联动-模块联邦

Webpack5 模块联邦让 Webpack 达到了线上 Runtime 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 Npm 包、构建再发布了。

我们知道 Webpack 可以通过 DLL 或者 Externals 做代码共享时 Common Chunk,但不同应用和项目间这个任务就变得困难了,我们几乎无法在项目之间做到按需热插拔。

模块联邦是 Webpack5 新内置的一个重要功能,可以让跨应用间真正做到模块共享。

如下图所示,正常的代码共享需要将依赖作为 Lib 安装到项目,进行 Webpack 打包构建再上线。对于不同项目,需要共享一个模块时,最常见的办法就是将其抽成通用依赖并分别安装在各自项目中。另外就是通过umd的方式输出到其他项目中。这样有个问题就是包体积没办法使用本地编译的优化手段。

模块联邦:直接将一个应用的包应用于另外一个应用,同时具备整体应用打包的公共依赖抽取能力。通俗来说在原本页面级构建,仓库内外置依赖、拆包的基础上,再加了一个选项,可以引用另外一个仓库的构建结果。

使用起来也很简单,本身就是一个webpack插件,有几个重要参数

  • name: 当前的联邦应用名称,作为全局标识
  • Remotes: 将需要导入的共享模块映射到当前项目中
  • exposes: 表示导出的模块,只要在此处声明的模块才可以被其他项目依赖

第十三页:微前端

微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

微前端架构具备以下几个核心价值:

  • 技术栈无关 主框架不限制接入应用的技术栈,子应用具备完全自主权
  • 独立开发、独立部署 子应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 独立运行时 每个子应用之间状态隔离,运行时状态不共享

传统的云控制台应用,几乎都会面临业务快速发展之后,单体应用进化成巨石应用的问题。为了解决产品研发之间各种耦合的问题,大部分企业也都会有自己的解决方案。

MPA 方案的优点在于 部署简单、各应用之间硬隔离,天生具备技术栈无关、独立开发、独立部署的特性。缺点则也很明显,应用之间切换会造成浏览器重刷,由于产品域名之间相互跳转,流程体验上会存在断点。

SPA 则天生具备体验上的优势,应用直接无刷新切换,能极大的保证多产品之间流程操作串联时的流程性。缺点则在于各应用技术栈之间是强耦合的。

第十四页:小结

monorepo 需要开发人员对代码熟悉,对 git 熟悉,否则几十人的团队去维护同一个 monorepo 仓库是玩不转的。著名开源项目采用这种模式,并且玩的有声有色,也许是因为他们都是非常优秀的程序员吧。对于上面提到的一系列问题,业务未来会找到好的解决方案,在两种模式间采用一种更为平衡的方式吧。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「Webpack」「工程化」 标签。