什么是monorepo
A monorepo is single repository containing multiple distinct projects, with well-defined relationships, which means projects inside depend on each other, they share code.
Monorepo and Monolith
A good monorepo is the opposite of monolithic, and monorepos are not a silver bullet. Nothing is.
银弹(silver bullet)出自The Mythical Man-Month 中文意义好比万金油
使用pnpm管理monorepo
pnpm的node_module结构跟npm构建的不太一样, 具体见这里Flat node_modules is not the only way, 以及explain the circular symlink
优点
- install using global store
- Dependencies are symlinked to reduce complexity
- Hard linking from global store(prevent many copies of same package)
概念解释
- symlink and hardlink
- linux系统中有一个重要概念
inode
(the abbreviation for "index node"). 它是文件和目录的唯一标志符, inode存储了元数据(metadata). 元数据包含(fileType、fileSize、ownerId、read write and execute permission 、last change time···) 并且有一个唯一的inode number, 使用命令ls -i (file name|directory name)
可查看.
一般情况, 一个文件名“唯一”对应一个 inode. 但是linux允许多个文件名都硬连接到同一个inode. 这表示我们可以使用不同的文件名访问同样的内容, 对文件内容进行修改将“反映”到所有文件, 删除目标文件不影响其它拥有相同inode number
但文件名不同的hardlink访问,只有最后一个hardlink被删除, 这个inode number
才会被释放, 这种机制就被称为硬连接hardlink.
- 两张图总结: 图1
/usr/sbin/mail
和/var/qmail/bin/sendmail
都是hardlink, 图2/usr/sbin/mail
是一个symlink指向一个hardlink/var/qmail/bin/sendmail
Hard Links | Soft Links |
---|---|
It is a copy of the original file that serves as a pointer to the same file, allowing it to be accessed even if the original file is deleted or relocated. | It is a short pointer file that links a filename to a pathname. It's nothing more than a shortcut to the original file, much like the Windows OS's shortcut option. |
It has a similar inode number to the target file. | It has a different inode number. |
It is not allowed the relative path. | It allows both relative and absolute paths. |
It cannot be established outside the file system. | It may be established in the file system. |
It has an additional name for the original file that references to the target file through inode. | It is different from the original file and is an alternative for it, but it does not use inode. |
It may only link to a file. | It may link both to a directory or a file. |
It remains valid even if the target file is deleted. | It becomes invalid when the originating file is deleted. |
- 上文说到
pnpm insall
后的node_modules
目录不太一样, 实际上项目的依赖都经由软连接指向同级别目录中的.pnpm
目录里.
>$ tree -L 2
├── node_modules
│ ├── .pnpm
│ ├── @changesets
│ ├── @eslint
│ ├── @types
│ ├── @typescript-eslint
│ ├── eslint -> .pnpm/[email protected]/node_modules/eslint
│ ├── prop-types -> .pnpm/[email protected]/node_modules/prop-types
│ ├── tsup -> .pnpm/[email protected][email protected]/node_modules/tsup
│ └── typescript -> .pnpm/[email protected]/node_modules/typescript
- global store
指的是Mac/linux中/Users/<你的用户名>/.pnpm-store
路径下的公共文件. pnpm安装项目依赖的时候, 如果依赖包存在在该路径下, 直接使用
详细分析文章, 配合上图Symlink node_modules structure
workspace
workspace必须有一个pnpm-workspace.yaml
在其根目录. 以nextra的提交版本bc319706
为例.
packages:
- 'packages/*'
- 'examples/*'
工程目录结构
├── examples
│ ├── blog
│ ├── docs
│ └── swr-site
├── node_modules
├── package.json
├── packages
│ ├── nextra
│ ├── nextra-theme-blog
│ └── nextra-theme-docs
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── prettier.config.js
├── renovate.json
└── turbo.json
执行pnpm run dev --recursive
有如下输出
Scope: all 7 workspace projects
. dev$ turbo run dev
└─ Running...
│ • Packages in scope: blog, docs, nextra, nextra-theme-blog, nextra-theme-docs, swr-site
└─ Running...
正好对应examples
和packages
目录下的子工程, 接着看examples/docs
目录下的package.json
.
{
"name": "docs",
"dependencies": {
"react": "*",
"react-dom": "*",
"next": ">=13",
"nextra": "workspace:*",
"nextra-theme-docs": "workspace:*"
},
"dependenciesMeta": {
"nextra": {
"injected": true
},
"nextra-theme-docs": {
"injected": true
}
}
}
dependenciesMeta.*.injected的作用官网讲的很清楚了。
在文中的例子nextra-theme-docs
和docs
都有相同的依赖项react
react-dom
等等, 而且react
在nextra-theme-docs
中声明在peerDependencies
里面, 那么被injected的依赖包nextra将会安装宿主host package的react
版本.
另外, 关于peerDependencies
由来参见Domenic's peerDependencies blog. 简单来说该选项的作用是当NPM
解析peerDependencies
里面依赖时, 首先判断依赖是不是安装了, 安装了且版本兼容则忽略, 否则, 如果根目录存在该依赖的不同版本, 则在自身的node_modules
下面安装, 根目录不存在则会安装到根目录.
{
"name": "nextra-theme-docs",
"version": "2.0.2",
"peerDependencies": {
"next": ">=9.5.3",
"react": ">=16.13.1",
"react-dom": ">=16.13.1"
},
}
exports field in package.json
公用包编译完成打包, 推荐仅导出需要暴露出的模块, 参考写法如下
exports definition
webpack package-exports
proposal-pkg-exports
扩展文章
Everything you need to know about monorepo
Monorepo生态
介绍 Google 如何将数十亿代码通过 monorepo 方式组织的
掘金-pnpm
monorepo下模块包设计实践