分享目标
- 分析 monorepo 诞生的背景及其应用的场景
- 大家能够明确项目是否需要 monorepo 及其是否能够带来实质性的收益。
现代前端工程开发的趋势和痛点
趋势
现代前端工程开发的已不再是之前的单一静态页面的开发,而是随着业务场景的多样性和复杂性在不断的演进,下面是一张来字节 web infra 团队在知乎分享的前端趋势概览图
- 第一个趋势是现代前端开发不再仅限于网页,而是涉及到的平台越来越多,比如 Web 端、Node、客户端和跨平台等。
- 第二个趋势是业务场景越来越多,复杂性也越来越大,特别是近年来也涌现了很多重前端交互的应用,比如 vscode、Figma。
- 第三个趋势就是随着业务场景的和多平台开发的出现,不可避免的使得前端团队规模在不断增大。
除了前端项目上的演变之外,前端的框架、构建工具、测试工具也在不断的更新,比如说 webpack、rollup、vite,测试方面有 jest、vitest等。那么在这些趋势下,前端项目开发会面临哪些痛点?
痛点
代码复用困难
在维护多个项目的时候,有一些逻辑很有可能会被多次用到,比如一些基础的组件、工具函数,在 polyrepo 中,需要为这些共享的代码单独维护一个仓库,之后会发布为单独的 npm 包供各个项目引用。
这样虽然能够解决代码复用的问题,但是之后这些公共依赖的升级会非常的繁琐,比如说现在所有的项目中都使用了 shared-ui 包的 1.1.0
版本,突然某个 ui 组件想要修改一些样式,就需要走下面的流程:
- 在 shared-ui 中修改样式
- 发布一个
1.1.1
版本的包 - 所有的项目都需要将 shared-ui 更新到最新版本
如果升级完之后发现有问题,这些步骤还得重复的执行,依赖 shared-ui 的库越多,这个过程花费的时间就越多,同时这个过程也会存在一定的沟通成本。
重复的项目基建
在 Polyrepo 中,各个项目之间是割裂的状态,因此每个项目都需要频繁创建 git 仓库,配置 CI、Lint 规则、构建等,而且为每个项目创建的基建后续都需要有人来维护(依赖升级)。
项目构建时间长
现代的前端项目开发已经离不开打包工具(webpack、rollup),整体开发形式为项目开发时使用模块化机制开发, 经过构建工具打包形成成品代码, 成品代码最终在不支持模块化的浏览器中执行。虽然构建打包工具为前端开发提供了便利, 但也因为在项目运行之前需要提前将代码构建成一个成品整体,这导致在本地开发时也引入了新问题。
随着项目变得越来越大,全量构建整个项目就需要花费很长的时间,在本地开发时,无论每次修改多少代码都需要重新全量构建,大大降低了整体的开发效率。
monorepo
monorepo 何时爆火?
monorepo 最早是由 Google 团队提出并实践的,在 2015 年的一次演讲中他们才对外公开他们在这块的一些实践经验,按 Google 团队的原话来说:monorepo is key to an open and collaborative culture
,简单来说 monorepo 是他们非常推崇的一种协作方式。
基本概念
monorepo 是一种架构风格,旨在将一个或多个的项目项目打散,使其部件化,这些部件能够在一个仓库下进行开发、相互依赖、共享一套基础建设,最终聚合成一个或多个产品进行交付。
在现实的项目中,monorepo 里的库的组成范围可大可小,具体分为下面三种:
- 由不同团队的不同项目组成。
- 由相同团队的不同项目组成。
- 由一个项目中的多个功能模块组成。
monorepo 的优势
便捷的代码复用
由于所有的项目放在一个仓库当中,复用起来非常方便,如果有依赖的代码变动,那么用到这个依赖的项目当中会立马感知到。并且所有的项目都是使用最新的代码,不会产生其它项目版本更新不及时的情况。
统一基础建设
所有项目复用一套标准的构建工具和开发规范,如果有新的项目接入,也可以直接复用已有的基建流程,比如 CI 流程、构建和发布流程。这样只需要很少的人来维护所有项目的基建,维护成本也大大减低。
同时所有的项目在一个仓库下也能够便于依赖的管理和升级,试想一下如果各个项目之间对于依赖的管理能够达到一致,那么同时升级整个仓库的工作量会比在几个月或几年内多次执行相同的升级过程要少得多。
增量构建
增量构建是一种构建策略,旨在只重新构建发生更改的部分,而不是全量构建整个项目,这样可以显著减少构建时间,在这种模式下 monorepo 下的库有两种表现形式:
-
可发布的库
可发布的库是指那些可以打包和发布为独立npm包,一种典型的场景就是通用的 UI 组件库,它能够在当前 monorepo 项目之外的项目中使用。
-
可构建的库
可构建的库和可发布的库类似,只不过不需要作为 npm 包进行发布,这些库会在项目构建之前提前进行编译,最终编译的产物可以直接被其他 monorepo 中的库使用。
这两种类型的库的代码在发生变化后只需要重新执行自己及其受影响库的构建命令,其他不受影响库不需要重新构建,这就达到了增量构建的目标。
功能升级
赋能
按照部门要求,部门内的平台都应该具备"航母化"能力,未来的项目交付中部门的平台会作为载体,去承载生态伙伴根据场景开发的应用程序,共同交付。比如说智能创作平台, 它能够作为载体(host)去承载其他部门(视频云、TMT)以插件(guest)形式开发的功能。
平台"航母化"除了要具备载体 的功能还需要能够对 guest 方进行赋能,进而简化 guest 方的开发,最终实现"平台即框架"。
举例: 在智能创作平台集成了其他部门的一个视频去水印的工具,这个工具需要用到实时消息通知功能, 以告知用户处理进度,而这个能力就需要平台进行封装再赋予(webpack MF的share功能)工具。
试想一下如果平台不进行 monorepo 化将核心的功能模块拆分出来,那如何对外进行 share 赋能呢?
可被集成
前端可被集成简而言之就是我们作为 guest 方,将系统集成到其他的宿主环境中,目前 TextMind 以及 OCRMind 都涉及对外以 SDK 的形式嵌入到用户系统中,比如说审查结果模块、比对结果模块。
这些功能模块在嵌入之前需要经过 webpack 等构建工具进行构建打包,在对项目进行拆分之前,构建时整个项目只有一个入口、一套 webpack 配置,那如何对这些功能模块单独进行构建呢?
monorepo的问题
很多技术方案除了有一定的优势之外,可能还会带来一定的问题,对于 monorepo 也是一样的,如果没有一套成熟、全面的 monorepo 设计方案去解决这些问题,也会给项目开发带来一些阻碍。
占用更多存储空间
对于 monorepo 项目来说,所有的代码会存放在一个仓库下,它所占用内存空间就会非常的大,当 monorepo 的数据量超出了开发人员笔记本电脑的容纳范围时,开发人员将无法获取 monorepo 项目,并且要求开发人员去增加更多的存储空间可能也是不切实际的。
为了解决这个问题,微软团队当时还专门做了一个虚拟文件系统(VFS),开发人员可以在电脑上查看所有文件列表,但是不会占用内存空间,只有当真正要去修改的时候才会从云端下载下来。但这这种解决方案需要投入非常大的人力来做。
较长的依赖安装时间
在一个 monorepo 项目中执行 yarn install 或者 npm install 时会安装整个项目的所有依赖,因此首次安装依赖的时间会和整个项目的依赖数量成正比。
幽灵依赖和依赖版本冲突
依赖管理的问题推荐两篇 rush 官网的两篇文章,里面详细分析了 monorepo 项目中依赖管理的两个问题。
项目之间不存在边界
在 monorepo 项目中库和库之间是没有边界的,可以随意相互引用,但是这是非常也是不推荐的,尤其是在跨团队的 monorepo 项目中,如果他们都可以自由地相互依赖,那么整个项目将变得难以管理,比如下面这个例子:
假设现在有两个团队,团队 B 希望在他们的项目之间去共享代码,因此他们创建了一个共享代码库 shared-b,这个库是私有的,因此他们是不希望团队 A 中的项目去依赖 shared-b,但是在 monorepo 项目中因为都是在同一个仓库下,团队 A 可以轻松的将 shared-b 添加到他们的 package.json 中,这是非常危险的。对此大多数 monorepo 工具(Nx)会允许开发者去定义库的引用边界,在项目开发阶段将这些问题暴露出来。
误区
项目能用 monorepo 就尽量用
这是错误的,盲目的 monorepo 化不仅不会给项目带来实质性的收益还会给项目增加负担,在考虑 monorepo 之前需要思考清楚几个点:
- 多个项目之间是否存在需要复用的功能模块。
- 项目是否存在赋能的场景。
- 如果要使用 monorepo 是否有一套完善的设计方案(Nx),以解决其问题。