monorepo 是什么
monorepo 是把多个项目的所有代码放到一个 git 仓库中进行管理,多个项目中会有共享的代码则可以分包引用。整个项目就是有 root 管理的 dependencies 加上多个 packages,每个 package 也可以在自己的作用域引入自己的 dependencies。
项目结构如下:
.
├── node_modules
├── package.json
├── packages
│ ├── web
│ ├── item2
│ └── utils
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── readme.md
├── .gitignore
├── LICENSE
└── tsconfig.json
packages 文件夹中的就是原本每个独立的项目(下文称之为 package )了,现在放在一起用 workspace 去管理。最外层路径称之为 root。在 root 的 package.json 中的 deps 是所有子 package 共用的。
pnpm 是什么
Fast, disk space efficient package manager
pnpm 是新一代 node 软件包管理器。它由 npm/yarn 衍生而来,但却解决了 npm/yarn 内部潜在的 bug,并且极大了地优化了性能,扩展了使用场景。
pnpm 相比 yarn,npm 安装包更快速,对包的依赖管理更偏平,对磁盘占用也更有优势,减小了占用体积。
pnpm官方文档:pnpm - 速度快、节省磁盘空间的软件包管理器
为什么要使用 monorepo
使用 monorepo 的模式可以把原本一个项目的多个模块或者多个项目拆分成多个 packages,在 packages 之间相互引用,也可以单独发布成包,极大地解决了项目之间代码无法重用的痛点。同时可以在项目打包或者编译操作时使用一套基础配置,如eslint、commitlint、CI/CD等,来管理所有的 packages。
开始
那我们开始使用那pnpm,来搭建一套基础的monorepo项目吧。
首先需要安装 pnpm,就不用多说了,根据官网操作即可。然后 init 一个项目。
在 root 目录新建 pnpm-workspace.yaml,内容如下
packages:
# all packages in subdirs of packages/ and components/
- 'packages/**'
我们所有的 packages 都放在 packages 目录下。
用 pnpm 安装全局共用的包,比如 react, react-dom。
pnpm install react react-dom -w
注意这里使用 -w 表示把包安装在 root 下,该包会放置在 <root>/node_modules 下。当然也可以把把安装在所有 packages 中,使用 -r 代替 -w。你必须使用其中一个参数。例如把 dayjs 装入 packages/web 下,packages/web 中的 package.json name 为 @test/web。需要执行:
pnpm i dayjs -r --filter @project/item1
使用 --filter 后面接子 package 的 name 表示只把安装的新包装入这个 package 中。
接下来,我们在 packages 中新建以下几个目录。
├── packages
│ ├── web
│ ├── item2
│ └── utils
然后每个都执行 npm init ,假设每个 package 的 name 依次为 @project/web @project/item2 @project/utils。这里的命名建议根据每个模块的作用或业务属性来命名,可以方便我们理解整体项目。
// packages/utils
{
"name": "@project/utils", // <-----
"version": "1.0.0",
"description": "",
"main": "index.ts",
"author": "zhang",
"license": "MIT",
"dependencies": {}
}
以 utils 为例,入口文件为 index.ts,首先建立这个文件。写入如下内容。
export const foo = (item1: number, item2: number) => item1 - item2
然后,执行
pnpm i @project/utils -r --filter @project/item2
之后,打开 packages/item2/package.json 发现 dependencies 中多了一行。
{
"name": "@project/item2",
"version": "1.0.0",
"description": "",
"main": "./index.tsx",
"scripts": {},
"author": "zhang",
"license": "MIT",
"dependencies": {
"@project/utils": "workspace:^1.0.0" // <--------
}
}
由于是 workspace 管理的,所有有一个前缀 workspace。接下来则可以从 package/item2 中直接引入这个包了。
import { foo } from '@project/utils'
当然,如果用 vscode 的自动导入
import { foo } from '../utils'
其实只要用 vscode 打开单独项目的文件夹,当成一个项目来写就行了。这也就暂时剥离 monorepo 这个概念了。
那么接下来的 package/web 就是整个项目的整体了。放置原来项目中的所有 src 下的代码。而一些原本通用的代码就从 src 下提取成包放在了 packages 下了。实现了对一些公共内容的抽离,每个模块更专注于自己的部分。
结束
通过上面的内容,主要是介绍了如何使用pnpm来搭建一个monorepo的项目,通过这种新型的代码结构,可以帮助更好的将之前分散的代码仓库更加集中,同时也不会增加它们之间的耦合性,还可以抽离公共内容,减少重复。 但是目前我主要是通过项目的类别来划分,将相同类型的项目搭建成一个monorepo项目,我个人认为是比较适合的,如果将任意项目都集成起来,并没有体现出它的优势,同时也导致项目过于庞大复杂。对于如何划分一个monorepo的项目,欢迎大家一起讨论。