什么是多包管理,该如何实现?

1,099 阅读4分钟

背景: monorepo多包管理

随着前端工程日益复杂,某些业务或者工具库通常涉及到很多个仓库,那么时间一长,多个仓库开发弊端日益显露,由此出现了一种新的项目管理方式——Monorepo

monorepo思想

Monorepo 不是一个新的概念,在软件工程领域,它已经有着十多年的历史了。把多个项目放在一个仓库里面,相对立的是传统的 MultiRepo 模式,即每个项目对应一个单独的仓库来分散管理。

一般 Monorepo 的目录如下所示,在 packages 存放多个子项目,并且每个子项目都有自己的package.json:

├── packages
|   ├── pkg1
|   |   ├── package.json
|   ├── pkg2
|   |   ├── package.json
├── package.json

MultiRepo 之痛

传统的方式MultiRepo中,每个项目都对应单独的一个代码仓库。这种方式有诸多的弊端:

1.代码复用

在维护多个项目的时候,有一些逻辑很有可能会被多次用到,比如一些基础的组件、工具函数,或者一些配置。为了开发的便捷性以及降低维护成本,可以将这些公共的逻辑代码抽取出来,作为一个 npm 包进行发布,一旦需要改动,只需要改动一份代码,然后 publish 就行了。

但这真的就完美解决了么?举个例子,比如你引入了 1.1.0 版本的 A 包,某个工具函数出现问题了,你需要做这些事情:

    1. 去修改一个工具函数的代码
    1. 发布1.1.1版本的新包
    1. 项目中安装新版本的 A。

可能只是改了一行代码,需要走这么多流程。然而开发阶段是很难保证不出 bug 的,如果有个按钮需要改个样式,又需要把上面的流程重新走一遍......停下来想想,这些重复的步骤真的是必须的吗?我们只是想复用一下代码,为什么每次修改代码都这么复杂?

上述的问题是 MultiRepo普遍存在的问题,因为不同的仓库工作区的割裂,导致复用代码的成本很高,开发调试的流程繁琐,甚至在基础库频繁改动的情况下让人感到很抓狂,体验很差。

2.版本管理

MultiRepo 的开发方式下,很容易出现这种依赖更新不及时的情况。这又是一个痛点。

3.项目基建

由于在 MultiRepo 当中,各个项目的工作流是割裂的,因此每个项目需要单独配置开发环境、配置 CI 流程、配置部署发布流程等等,甚至每个项目都有自己单独的一套脚手架工具。

其实,很容易发现这些项目里的很多基建的逻辑都是重复的,如果是 10 个项目,就需要维护 10 份基建的流程,逻辑重复不说,各个项目间存在构建、部署和发布的规范不能统一的情况,这样维护起来就更加麻烦了。

Monorepo 的好处

首先是工作流的一致性,由于所有的项目放在一个仓库当中,复用起来非常方便,如果有依赖的代码变动,那么用到这个依赖的项目当中会立马感知到。并且所有的项目都是使用最新的代码,不会产生其它项目版本更新不及时的情况。

其次是项目基建成本的降低,所有项目复用一套标准的工具和规范,无需切换开发环境,如果有新的项目接入,也可以直接复用已有的基建流程,比如 CI 流程、构建和发布流程。这样只需要很少的人来维护所有项目的基建,维护成本也大大减低。

团队协作也更加容易,一方面大家都在一个仓库开发,能够方便地共享和复用代码,方便检索项目源码,另一方面,git commit 的历史记录也支持以功能为单位进行提交,之前对于某个功能的提交,需要改好几个仓库,提交多个 commit,现在只需要提交一次,简化了 commit 记录,方便协作。

Monorepo 的落地

lerna

yarn

Yarn Workspacepackage.json文件中添加以下内容。从现在开始,我们将这个目录称为“workspace root”

// package.json:
{
    "private": true,
    "workspaces": [
        "workspace-a",
        "workspace-b"
    ]
}

请注意,private: true 是必需的!工作区并不意味着要发布,所以我们增加了这个安全措施,以确保没有任何东西可以意外暴露它们。

在创建此文件后,创建两个名为workspace-a和workspace-b的新子文件夹。在其中的每个文件中,使用以下内容创建另一个package.json文件:

// workspace-a/package.json:
{
    "name": "workspace-a",
    "version": "1.0.0",

    "dependencies": {
        "cross-env": "5.0.5"
    }
}
// workspace-b/package.json:
{
    "name": "workspace-b",
    "version": "1.0.0",

    "dependencies": {
        "cross-env": "5.0.5",
        "workspace-a": "1.0.0"
    }
}

最后,在某个地方运行yarn install,最好在工作区的根目录下运行。如果一切正常,你现在应该有一个类似的文件层次结构:

/package.json
/yarn.lock

/node_modules
/node_modules/cross-env
/node_modules/workspace-a -> /workspace-a

/workspace-a/package.json
/workspace-b/package.json

pnpm