文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

现代 Monorepo 工程技术选型,聊聊我的思考

2024-12-01 14:19

关注

相应地,在这篇文章中也对各类工具进行了一一介绍。并且,我相信每个看过这篇文章的同学,都会留下这么个疑问: 这么多 Monorepo Tool,我要如何进行选型?

这里,我给出的答案是 PNPM + Turborepo + Changesets。那么,又为什么是这 3 者呢?下面,我将会分别围绕这 3 个技术展开,来一一解答这个选型的原因以及怎么做。

PNPM

PNPM 的动机( Motivation [8] ),如它在官方文档介绍的所说: “Saving disk space and boosting installation speed”,节省磁盘空间和提高安装速度 。除开这个动机描述的显著优点外, PNPM 内置了对 Monorepo 的支持 [9] ,并解决了很多令人诟病的问题。

其中,比较经典的就是 Phantom dependencies(幻影依赖)。由于,默认情况下 yarn 、 npm 安装的依赖都是会被提升。所以,有时候你可能会遇到 Monorepo 项目中的某个包中的 package.json 没有安装这个依赖,结果实际代码中却使用了这个依赖...

虽说,PNPM 可以解决这个问题,但是, 默认情况 下 PNPM 安装的依赖也是会被提升的。如果,需要 PNPM 禁止依赖提升,我们可以通过在 Monorepo 项目工作区下的 .npmrc 文件中 配置 [10] ,例如只提升 lodash :

hoist-pattern[]=*lodash*

当然,还有一些其他的问题,有兴趣的同学可以看 ELab 团队写的这篇文章 《Monorepo 的这些坑,我们帮你踩过了!》 [11] 。

那么,在简单解答了为什么用 PNPM 后,下面我们来看一下要怎么用?

Workspace 配置

要使用 PNPM 的 Monorepo 很简单,只需要在 Monorepo 项目的工作区下新建 pnpm-workspace.yaml 文件并配置:

packages:
- 'packages/**'

接下来,则是记忆常用依赖和多包任务执行相关的命令。由于,我们的技术选型中有 Turborepo,它会负责多包任务的执行。所以,这里只需要记忆 常用依赖相关的命令 。

常用依赖相关命令

pnpm i

在 PNPM 中,安装依赖可以用 pnpm i 来完成。在 Monorepo 的场景下,默认情况下 pnpm i 会安装所有的依赖(包括 packages/* )。此外, pnpm i 还需要用到 3 个选项(Option):

pnpm remove

在 PNPM 中,删除在 package.json 中的某个依赖,可以用 pnpm remove 完成。它的选项(Option)使用和 pnpm i 大同小异。其中,不同地是当我们在工作区想要删除 packages 中所有包的 package.json 中的某个依赖的时候,需要使用 -r ,例如移除所有包中的 lodash :

pnpm remove lodash -r

当然,可能还有同学有一些其他的诉求,有兴趣的同学可以移步文档了解,这里不做展开。

Changesets

经常维护开源项目的同学都知道的一点,每次包(Package)的发布,需要修改 package.json 的 version 字段,以及同步更新一下本次发布修改的 CHANGELOG.md。

这么一来,就会凸显一个问题,每次发布都需要手动地去更新 version 、更新 CHANGELOG.md,未免 有点繁琐 。并且,用过 Lerna 的同学,应该都知道 Lerna 内置了对这块的支持。

但是,无论是 PNPM 又或者是下面要说的 Turborepo 都不支持这块,所以 2 者的官方文档都给大家推荐了用于支持这块能力的工具,例如 Changesets [12] 、 Beachball [13] 、 Auto [14] 等。

那么,这里我们要介绍的就是 Changesets。下面,我们来看一下在前面建好的 PNPM 的 Monorepo 项目中如何使用 Changesets。首先,需要执行在 Monorepo 项目的工作区下,执行如下 2 个命令:

pnpm i -DW @changesets/cli
pnpm changeset init

前者是安装 Changesets 的 CLI,后者是初始化 .changeset 文件夹以及对应的文件:

.changeset
-- config.json
|__ README.md

这里,我们来看一下 config.json [15] 文件:

{
"$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "restricted",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": []
}

除开 $schema 这个不需要修改的字段, config.json 文件中列了 7 个字段,各个字段分别代表的作用为:

async function getReleaseLine() {}
async function getDependencyReleaseLine() {}
export default {
getReleaseLine,
getDependencyReleaseLine
}

在初始化 .changeset 文件夹后,就可以正常使用 changeset 相关的命令,主要是这 3 个命令:

此外,如果是在业务场景下,我们通常需要把包发到公司 私有的 NPM Registry ,而这有很多种配置方式。但是, 需要注意 的是 Changesets 只支持在每个包中声明 publicConfig.registry 或者配置 process.env.npm_config_registry ,对应的代码会是这样:

// https://github.com/changesets/changesets/blob/main/packages/cli/src/commands/publish/npm-utils.ts
function getCorrectRegistry(packageJson?: PackageJSON): string {
const registry =
packageJson?.publishConfig?.registry ?? process.env.npm_config_registry;
return !registry || registry === "https://registry.yarnpkg.com"
? "https://registry.npmjs.org"
: registry;
}

可以看到,如果在前面说的这 2 种情况下获取不到 registry 的话,Changesets 都是按公共的 Registry 去查找或者发布包的。

Turborepo

说起 Turborepo,可能大家会有点陌生。但是,对于 Vercel [17] 我想大家都知道(毕竟 Rich Harris [18] 、Sebastian Markbåge 等都加入了),Turbrepo 则是 Vercel 旗下的一个开源项目。Turborepo 是用于为 JavaScript/TypeScript 的 Monorepo 提供一个极快的构建系统,简单地理解就是用 Turborepo 来执行 Monorepo 项目的中构建(或者其他)任务会 非常快 !

关于 Turborepo 其他优势,其 官方文档 [19] 写的很详尽,有兴趣的同学可以自行了解~

所以,你可以理解成 快 是选择 Turborepo 负责 Monorepo 项目多包任务执行的原因。而在 Turborepo 中执行多包任务是通过 turbo run