MiaoMiao'S Dev Life
  • 首页
  • 技术专栏
首页 / 前端技术 / 前端工程化 / monorepos 方案实践 – 基于动态表单类项目

monorepos 方案实践 – 基于动态表单类项目

前端工程化 • 4 years ago

一 前言

在实际工作中,经常会出现需要同时维护多个相互依赖项目的情况。比如 C 项目是一个底层的项目,被抽成了一个 NPM 包。而 A 项目和 B 项目都同时依赖 C 项目的代码。随着 A 项目的开发,可能要不时修改底层 C 项目的代码。那如何优雅的解决各项目之间互相依赖的问题?当 C 项目升级发布之后,如何更好的处理 A,B 项目的版本同步,也是一个让人头疼的问题。

针对上述的问题,下面介绍的 monorepos 可以为我们提供一种管理多项目的全新解决思路:

二 什么是 monorepos?

monorepos 是一种源码管理的方式,它在一个 Git 仓库中管理所有的相关项目。很多知名互联网公司(如 Google,Facebook,Microsoft)和开源项目(如 Babel,React,Vue,Angular,Jest)都使用这种方式管理它们的代码。

与 monorepos 相对应的一个概念是 multirepos。这种方式是为每个独立的项目都创建一个 Git 仓库,多个项目就会存在多个相互独立的 Git 仓库。(这也是我们公司之前唯一使用的方式)

三 monorepos 和 multirepos 对比

从上一节的内容可以看出,monorepos 和 multirepos 主要的不同点在于如何管理和组织一系列相关的项目。那不同的组织和管理方式究竟会带来哪些不一样的地方,我们通过分析 monorepos 和 multirepos 各自的优劣将会得到一些答案。

3.1 monorepos 优缺点

优点:

  1. 代码具有更高的可维护性,代码重用变的简单:所有项目相关的代码都在一个仓库里,相对于 multirepos,更容易发现和抽象出代码中共用的逻辑。
  2. 依赖管理变的简单:由于所有项目都是在同一仓库中,无需跨仓库引用,利用一些工具,很容易处理好不同子项目之间的依赖。
  3. 统一项目的工程配置和公共依赖:把多个项目用到的公共依赖(如 eslint, babel, style lint 等)和相关的工程配置(husky,commitlint)抽象到全局,这样不用每个项目都单独配置。
  4. 所有相关项目统一入口,统一 README 文件:免去查找相关仓库的时间,在进行项目交接时也不会遗漏相关信息。

缺点:

  1. 权限管理稍显麻烦:如果需要精细控制项目中每个 package 的权限,会比较麻烦。
  2. 构建时间会比 multirepos长:由于所有的项目都在同一个仓库中,所以构建的时间会比单独的 multirepos更长一些。
  3. 对新人来说,有一定的学习成本和适应过程。

3.2 multirepos 优缺点

优点:

  1. 按照模块划分代码仓库,每个模块体积较为合理。
  2. 由于不同模块是不同仓库,所以权限管理较 monorepos 更易于实现。

缺点:

  1. 调试相对麻烦:multirepos 一般使用 npm link 调试依赖项目的代码,如果依赖多了,手动管理 link 也是一件麻烦的事情。
  2. 版本管理需要手动升级依赖。
  3. 复用代码和相关配置比较困难:脚手架能解决一部分问题,但是如果有升级的话就比较麻烦

3.3 如何选择

不管是 monorepos 和 multireops 都有各自适用的场景。那到底选择什么样的方式,也需要根据你的实际场景来做决定。

这里有几个可以参考的指标,我们可以通过下列几个指标来问问自己,是否真的需要使用 monorepos。

  1. 正在维护的这一组 packages 之间是否有相关性?比如都是同一业务线下的相似项目,又或者都是一些工具类的库或插件?
  2. 这些 packages 之间是否相互依赖?比如有一个核心的 package, 这个 packages 被其他 package 所依赖,并且需要在核心 package 版本发生变化后,升级依赖方的版本。
  3. 这些 packages 之间是否有较多共同依赖?

四 monorepos 实践

创建 monorepos 业内有多种方案,这里只介绍其中比较流行的两种方案 Yarn workspaces 和 lerna.

4.1 Yarn WorkSpaces

Yarn 从 1.0 版开始支持 Workspaces(工作区)。Workspaces 能更好的统一管理有多个项目的仓库,既可在每个项目下使用独立的 package.json 管理依赖,又可便利的享受一条 yarn 命令安装或者升级所有依赖等。更重要的是可以使多个项目共享同一个 node_modules 目录,提升开发效率和降低磁盘空间占用。

关于如何使用 yarn workspaces 创建 monorepos 可参见文章。

4.2 Lerna

Lerna 是一个管理多个 npm 模块的工具,是 Babel 自己用来维护自己的 Monorepo 并开源出的一个项目。它主要解决了多个包互相依赖,且发布需要手动维护多个包的问题。

关于如何使用 Lerna 创建 monorepos 可参见文章。

4.3 如何选择

我们先简单的对比下 Yarn Workspace 和 Lerna 两种方案:

相同点:

  • 都可以独立的创建 monorepos。
  • packages 之间的相互依赖都可以使用 syslink 链接到本地,也可以直接安装已经发布到指定 npm 源中的版本。
  • packages 的共同依赖都可以提升到根目录,避免重复安装,lerna 默认情况下不会提升,需要在 bootstrap 的时候显示指定 --hoist 参数

不同点:

  • yarn workspaces 没有提供统一的版本管理与发布。workspaces 不具备本地依赖之间语义版本的自动管理,包的统一发布等。需要进入到各个包内进行手动版本更新或者发布。

从以上的对比中,我不难发现,yarn workspaces 进行 monorepos 的管理时,其方案是不完备的,比如进行 library(提供不同的 NPM 包给外界使用)类型的 monorepos 管理时只使用 yarn workspaces 是不够的。

所以在管理 library 类型的 monorepos 项目时(如果是非 library 类型的项目,用 yarn workspaces 管理就够了),推荐的方案是 yarn workspaces + lerna 组合,yarn 的 workspaces 是一个偏底层的能力,而 lerna 本身也仅是利用 npm 或 yarn 提供的能力来工作的,所以我们可以结合使用 lerna 和 yarn workspaces 为 monorepos 提供全方位的能力。(这也是 yarn 官方的使用方式)

五 项目实践

5.0 项目背景

这里就拿笔者之前开发过的一个表单项目为例,它从业务上抽象出3层,分别是表单核心渲染逻辑层,基于核心渲染逻辑的 UI 层,以及实际应用表单的应用层。其中核心层和 UI 层最终提供的是相关的 NPM 包,应用层是部署的项目(包含后台编辑和移动端渲染)。

各个模块之间的依赖关系如下:

表单架构

这3层的项目之间存在互相的依赖,且都有一些可以共用的部分,且都属于表单相关的项目。结合前面的说明,该项目就非常适合用 monorepos 来管理相关的代码。

下面重点讲一些配置的关键点,其他配置的内容可翻阅参考文献中的文章进行查询。

5.1 项目组织结构

项目结构如下图:

CleanShot%202021-06-15%20at%2017.01.36@2x

所有相关项目统一在 packages 目录中维护。根据业务需求分为 3 个目录,core 是表单的核心层,ui-layer 是 UI 渲染层,application-layer是应用层,包括表单配置后台和表单渲染 h5。

5.2 Yarn Workspaces 配置

我们使用 yarn workspaces 主要做两件事情

  1. 解决项目间互相依赖的关系
  2. 减少项目重复的工程化配置和通用包的重复安装

yarn workspace 的配置如下:

(1) 所有项目都放在 packages 目录下,如上图所示.

(2)在项目根目录里的 package.json 文件中,设置 workspaces 属性,属性值为之前创建的目录(当然这里的路径可以写任何你想要的),这样 packages 下所有的模块都成为了该项目的子模块。

{
  "name": "xxx",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "packages/**"
  ]
}

(3) 同样,在 package.json 文件中,设置 private 属性为 true,这样做是为了避免我们误操作将仓库发布。

进行完以上配置之后,只需要在根目录中执行 yarn install 后,您会发现在项目根目录中出现了 node_modules 目录,该目录拥有所有子项目共用的 npm 包。
比如多个项目中同时使用了 vue 这个包,yarn workspaces 会把 vue 这个包作为公共包放在根目录的 node_modules 下,其他子项目中则通过软链的形式链接这个包。如果是某个子项目中独有的包,则只会下载到这个子项目的 node_modules中。

同样的,yarn workspaces 也会自动处理项目间的依赖关系。比如 a 项目依赖 b 这个包,yarn workspces 会自动把本地的 b 作为 a 的依赖(需要 a/package.json 中设置的 b 版本号和本地 b 的版本号一致,如不一致则会从 npm 源中拉取)。

执行完上述命令后,就完成了一个工程里不同项目间的互相依赖和公共包的提升,即可以轻松在一个项目里调试互相依赖的不同模块,又能统一管理依赖的公共包,从而提升开发效率。

5.3 Lerna 配置

我们前面说过,项目中使用 yarn workspaces 是用来处理同个工程中依赖相关问题,而 lerna 则是专注来处理发布包的依赖问题.

lerna 可以解决如下常见的发布包依赖问题:

  • 当一个子项目更新后,我们只能手动追踪依赖该项目的其他子项目,并升级其版本。

本项目中 Lerna 的配置如下:

{
  "npmClient": "yarn",
  "useWorkspaces": true,
  "packages": [
    "packages/*"
  ],
  "version": "independent"
}

lerna 可以指定 npm 客户端,这里使用的是 yarn。同时我们也可以让 Lerna 追踪我们 workspaces 设置的目录,这样我们就依旧保留了之前 workspaces 的所有特性(子项目引用和通用包提升)。

这里再重点讲下version,它表示 lerna 的两种工作模式:Independent mode 和 Fixed/Locked mode。

lerna 的默认模式是 Fixed/Locked mode ,在这种模式下,实际上 lerna 是把工程当作一个整体来对待。每次发布 packges,都是全量发布,无论是否修改。但是在 Independent mode 下,lerna 会配合 Git,检查文件变动,只发布有改动的packge。我们这里使用的是 independent 模式, 因为我们项目里多个包的版本是保持独立的。

在需要发布相关包的时候,只需要执行 lerna publish 命令。 你就可以根据cmd中的提示,一步步的发布packges了。而实际上在执行该条命令的时候,lerna会做很多的工作,具体如下:

  • Run the equivalent of lerna updated to determine which packages need to be published.
  • If necessary, increment the version key in lerna.json.
  • Update the package.json of all updated packages to their new versions.
  • Update all dependencies of the updated packages with the new versions, specified with a caret (^).
  • Create a new git commit and tag for the new version.
  • Publish updated packages to npm.

5.4 全局工程配置

前面说了, yarn workspaces 可以把通用的配置放在根目录下,之后可以在子项目中引用全局的配置,免去重复配置的麻烦。

这里就以 eslint 的配置为例,我们在根项目中写一份配置文件:

module.exports = {
  root: true,

  env: {
    node: true
  },
  ...
}

我们只需要在子项目中进行引用即可,不需要再重复配置。

module.exports = {
  extends: [
    "../../../.eslintrc.js"
  ]
}

其他的配置,如 babel, stylelint 也是一样,这里就不一一赘述。另外再说一点关于 monorepo 项目发布部署的问题,monorepo 项目发布和 multirepos 项目的发布方式还是略有不同,如果你想通过 gitlab ci/cd 进行相关发布部署的话,可以参考这篇文章。

以上就是使用 monorepos 组织代码时关键的一些配置情况,目前只用到了 yarn workspaces 和 lerna 的一些基础功能,随着对 monorepos 的不断深入使用,如果能用上一些更高阶的配置功能,之后也会持续更新到当前文档中。

六 小结

在本篇文章中,我们共同了解了什么是 monorepos 和 multirepos,以及 monorepos 和 multirepos 的优劣对比,并且一起看了如何在一个真实的项目中使用 monorepos。

monorepos 和 multirepos 各有适用的场景,如何选择也需要根据实际场景来决定。有一些参考的指标可以借鉴,如 packages 之间是否有相关性,是否有相互依赖等?

如果大家在阅读文章和实践 monorepos 方案中有任何问题,可在评论区进行讨论。

七 参考资料

  • On Monorepos and the Deployment With GitLab CI/CD
  • All in one:项目级 monorepo 策略最佳实践
  • 使用 MonoRepo 管理前端项目
  • Mono Repo vs Multi Repo: Deep Dive Into The Neverending Debate
  • What is monorepo?
  • Everything you should know about Monorepo
  • 基于lerna和yarn workspace的monorepo工作流
  • NodeJS:Lerna —— Monorepo 的最佳实践
  • 使用Monorepo管理前端微前端项目
动态表单
Like (0)
Comments (0)
Back
Leave a comment
Related Posts
动态表单的设计思想及实现策略
随着各个企业数字化转型的不断推进,越来越多的企业开始重视业务流程自动化,其中表单处理是一个重要和复杂的环节。传统的表单处理方式通常需要开发人员编写大量的代码,而“动态表单”可以在无需开发人员介入的情况下,帮助企业快速地生成符合业务需求的表单页面,提高业务效率和减少出错率,成为数字化转型中的一种重要技术方案。 ...
2 years ago
整洁架构在小程序中的应用
公司的核心小程序项目经过过去将近 1 年半时间的发展,业务流程已经基本完成了从 0 到 1 的建设。于此同时由于前期业务发展很快,开发时间紧,任务重,很多代码在没有经过前期思考就写入了代码库中,造成当前项目代码逐渐腐化。 ...
2 years ago
跨端业务组件库方案调研-基于 h5 和 小程序
前段时间在公司推了一套跨端组件库的方案,趁着这段时间稍微有点空把调研的过程记录下来。 ...
3 years ago
© 2016-2025 All Rights Reserved⋅Developed by nicetheme⋅浙ICP备19034990号
  • 首页
  • 技术专栏
Search (0)
    Load more...
    Contents
    • 一 前言
    • 二 什么是 monorepos?
    • 三 monorepos 和 multirepos 对比
      • 3.1 monorepos 优缺点
      • 3.2 multirepos 优缺点
      • 3.3 如何选择
    • 四 monorepos 实践
      • 4.1 Yarn WorkSpaces
      • 4.2 Lerna
      • 4.3 如何选择
    • 五 项目实践
      • 5.0 项目背景
      • 5.1 项目组织结构
      • 5.2 Yarn Workspaces 配置
      • 5.3 Lerna 配置
      • 5.4 全局工程配置
    • 六 小结
    • 七 参考资料
    Tags
    css docker es6 git html js typescript vue 动态表单 整洁架构