Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
eightHundreds opened this issue Jun 1, 2022 · 0 comments
Open

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

eightHundreds opened this issue Jun 1, 2022 · 0 comments

Comments

@eightHundreds
Copy link
Owner

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

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

奇舞精选

奇舞精选

微信号 gh_4ef2f1ed6b20

功能介绍 《奇舞精选》是由奇舞团维护的前端技术公众号。除周五外,每天向大家推荐一篇前端相关技术文章,每周五向大家推送汇总周刊内容。

2022-05-30 18:35 发表于北京

收录于合集

以下文章来源于 Code center ,作者​五柳

[

Code center .

程序员,五柳,希望能带给大家一些别样的知识和生活感悟,春华秋实,年年长茂。](#)

前言

相信很多关注 Monorepo 生态的同学,应该大都看过这篇文章 monorepo.tools[1],其中列举了现存的几个主流的 Monorepo 相关的工具:

  • Bazel (by Google)[2]
  • Lage (by Microsoft)[3]
  • Lerna[4]
  • Nx (by Nrwl)[5]
  • Rush (by Microsoft)[6]
  • Turborepo (by Vercel)[7]

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

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

PNPM

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

其中,比较经典的就是 Phantom dependencies(幻影依赖)。由于,默认情况下 yarnnpm 安装的依赖都是会被提升。所以,有时候你可能会遇到 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):

  • --filter <package>,安装依赖到指定的 package,不声明要安装的依赖包则默认安装 package.json 中的所有依赖
  • --prod, P,安装依赖到 dependencies
  • --dev, D,安装依赖到 devDependencies

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 个字段,各个字段分别代表的作用为:

  • changelog 设置 CHANGELOG.md 生成方式,可以设置 false 不生成,也可以设置为定义生成行为的文件地址或依赖名称,例如 Changsets 提供的 `changelog-git`[16]。其中,定义生成行为的文件固定代码模版为:

`async function getReleaseLine() {}

async function getDependencyReleaseLine() {}

export default {
  getReleaseLine,
  getDependencyReleaseLine
}
`

  • commit 设置是否把执行 changeset addchangeset publish 操作时对修改用 Git 提交
  • linked 设置共享版本的包,而不是独立版本的包,例如一个组件库中主题和单独的组件的关系,也就是修改 Version 的时候,共享的包需要同步一起更新版本
  • access 设置执行 npm publish--access 选项,通常情况下我们是公共的包,所以设置 public 即可(注意,它会被 package.json 中的 access 字段重写)
  • baseBranch 设置默认的 Git 分支,例如现在 GitHub 的默认分支应该是 main
  • updateInternalDependencies 设置互相依赖的包版本更新机制,它是一个枚举(major|minor|patch),例如设置为 minor 时,只有当依赖的包新了 minor 版本或者才会对应地更新 package.json 的 dependenciesdevDependencies 中对应依赖的版本
  • ignore 设置不需要发布的包,这些会被 Changesets 忽略

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

  • pnpm chageset 用于生成本次修改的要添加到 CHANGELOG.md 中的描述
  • pnpm changeset version 用于生成本次修改后的包的版本
  • pnpm changeset publish 用于发布包

此外,如果是在业务场景下,我们通常需要把包发到公司私有的 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 <script>。不过,turbo runlerna run 直接使用有所不同,它需要配置 turbo.json 文件,注册每个需要执行的 script 命令。

在 Turborepo 中有个 Pipelines[20] 的概念,它是由 turbo.json 文件中的 pipline 字段的配置描述,它会在执行 turbo run 命令的时候,根据对应的配置进行有序的执行缓存输出的文件

举个例子,通常情况下我们一个 Monorepo 项目中的每个包可能会有 devbuildtestclean 等 4 个命令,那么对应的 turbo.json 的配置会是这样:

{   "pipeline": {     "build": {"dependsOn": ["^build"],       "outputs": ["dist/**"]     },     "clean": {       "dependsOn": ["^clean"]     },     "test": {       "dependsOn": ["build", "lint"]     },     "dev": {       "cache": false     }   } }

可以看到,pipeline 中的每个 key 则对应着每个需要执行的 turbo run 命令的名称,其中 dependsOnoutputscache 等 3 个字段分别作用为:

  • dependsOn 表示当前命令所依赖的命令,^ 表示 dependenciesdevDependencies 的所有依赖都执行完 build,才执行 build
  • outputs 表示命令执行输出的文件缓存目录,例如我们常见的 distcoverage
  • cache 表示是否缓存,通常我们执行 dev 命令的时候会结合 watch 模式,所以这种情况下关闭掉缓存比较切合实际需求

这样一来,我们就可以使用诸如 turbo run build test 的命令,它则会按 pipeline 的配置依次执行对应的命令。

当然,如果你想每个命令都支持单独执行,可以直接配置为 {} 即可。此外,如果要使用 turbo run 命令,还需要在 package.json 中声明 packageManage 字段为指定的包管理工具及版本,例如 "packageManager": "pnpm@6.30.0"

结语

阅读到此处,我想大家应该理解了 PNPM + Turborepo + Changesets 这个技术选型的原因以及要怎么做。当然,这个选型只是我个人的思考所得出的答案,相信也有同学仍然钟情于 Lerna,又或者喜欢 Rush 一把梭,这些观点并无对错,本质上这也是编程的魅力所在,各个轮子都有其存在的价值

最后,如果文中存在表达不当或错误的地方,欢迎各位同学评论交流~

参考资料

[1]

monorepo.tools: https://monorepo.tools/

[2]

Bazel (by Google): https://bazel.build/?utm\_source=monorepo.tools

[3]

Lage (by Microsoft): https://microsoft.github.io/lage/?utm\_source=monorepo.tools

[4]

Lerna: https://lerna.js.org/?utm\_source=monorepo.tools

[5]

Nx (by Nrwl): https://nx.dev/?utm\_source=monorepo.tools

[6]

Rush (by Microsoft): https://rushstack.io/?utm\_source=monorepo.tools

[7]

Turborepo (by Vercel): https://turborepo.org/?utm\_source=monorepo.tools

[8]

Motivation: https://pnpm.io/motivation

[9]

PNPM 内置了对 Monorepo 的支持: https://pnpm.io/workspaces

[10]

配置: https://pnpm.io/npmrc#hoist-pattern

[11]

Monorepo 的这些坑,我们帮你踩过了!: https://juejin.cn/post/6972139870231724045#heading-4

[12]

Changesets: https://github.com/changesets/changesets

[13]

Beachball: https://github.com/microsoft/beachball

[14]

Auto: https://github.com/intuit/auto

[15]

config.json: https://github.com/changesets/changesets/blob/main/docs/config-file-options.md

[16]

changelog-git: https://github.com/changesets/changesets/tree/main/packages/changelog-git

[17]

Vercel: https://github.com/vercel

[18]

Rich Harris: https://github.com/Rich-Harris

[19]

官方文档: https://turborepo.org/docs

[20]

Pipelines: https://turborepo.org/docs/features/pipelines

-END-

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

预览时标签不可点

收录于合集 #

 个

上一篇 下一篇

阅读原文

喜欢此内容的人还喜欢

一个合格的 Monorepo 应当具备哪些能力?

一个合格的 Monorepo 应当具备哪些能力?

...

魔术师卡颂

不喜欢

不看的原因

确定

  • 内容质量低
  • 不看此公众号

对接 API 接口简直太痛苦了

对接 API 接口简直太痛苦了

...

低并发编程

不喜欢

不看的原因

确定

  • 内容质量低
  • 不看此公众号

前端架构必学:探秘 HTTPS

前端架构必学:探秘 HTTPS

...

前端巅峰

不喜欢

不看的原因

确定

  • 内容质量低
  • 不看此公众号

写留言

取消

留言

我的留言

写留言

正在加载

展开我的留言

留言被精选后将公开

精选留言

写留言

写留言

正在加载

已无更多数据

发消息

关闭

写留言

提交更多

正在加载

表情

正在加载

留言

微信扫一扫
关注该公众号

知道了

微信扫一扫
使用小程序

取消 允许

取消 允许

: , 。 视频 小程序 赞 ,轻点两下取消赞 在看 ,轻点两下取消在看





Powered by Yiting & Majiang


https://mp.weixin.qq.com/s/-6OSeoJUlzI7qkkcDrYNcQ?st=A85F371900900467D81F7450A0A8E6C4CF792F7D2FAF3280331E937C71F2E55A858A72BD5975D09CCCFF2F6D4459E6335D40B61DBFB94A1A8462629A6C71F7C1D2547A310BC079EC2024EE551887439CAEA9F5D6276187D6279AC045320038385D49BB25B140565C8DB0A68E71F12CD3360B0D6749C2DB585D65C2D02FE0745CB5EA3EF287BF6499D1A7DB1E6C2A229C0CCBC73A9BEBCB37611535B2A63247653DBCD96898C855A2713842B66E19EC3A2DA10631A0D52252F87BCE8439B590EF9B38E3D54FBB635D3212D053AD43A7E4&vid=1688853094908360&cst=73D73C4DFE486D3F6780D78F36AD274AEFB38799CFBB48FBAD9BE678D037FF38830C1D08C8167CBCA5926A63E851DA00&deviceid=dbc80a2d-2717-4046-9fb7-8e88df3f7147&version=4.0.3.70108&platform=mac

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant