环境与版本
platform=>mac osnode=>v22.2.0npm=>10.8.0pnpm=>9.1.3yarn=>1.22.22
名词解释
利用单一仓库来管理多个 packages 的一种策略,如早期的 lerna。
workspace
由上述单仓多包催生的管理方式,workspace(工作空间) 是 npm、yarn、pnpm 等包管理工具提供的一种特性,用于管理多个包的依赖关系。
合理配置 workspace 后,包之间互相依赖不需要使用 npm link,将在 install 时中处理
在 pnpm 中使用 workspace
A workspace must have a pnpm-workspace.yaml file in its root. A workspace also may have an .npmrc in its root.
如文档描述,启用 pnpm 的 workspace 需要在项目根目录创建 pnpm-workspace.yaml
项目结构
my-monorepo
├── docs
├── apps
│ └── web
├── packages
│ ├── ui
│ ├── eslint-config
│ └── shared-utils
├── pnpm-workspace.yaml
├── .npmrc => optional
└── sdk
根目录 package.json
/my-monorepo/package.json
{ "name": "my-monorepo", "private": true, "script": { "dev": "pnpm -r dev" } }
pnpm-workspace.yaml
在该项目中指定位于 my-monorepo/apps/ 和 my-monorepo/packages/ 内的直接子目录为工作区如 web、ui等
而 docs 本身则为一个工作区,则不需要通配符
pnpm-workspace.yaml
packages: - "docs" - "apps/*" - "packages/*"
具体语法 - glob 通配符
pnpm-workspace.yaml
packages: # 选择 packages 目录下的所有首层子目录的包 - 'packages/*' # 选择 components 目录下所有层级的包 - 'components/**' # 排除所有包含 test 的包 - '!**/test/**'
安装依赖
/my-monorepo/
pnpm install
在根目录中安装依赖
/my-monorepo/
pnpm add <package-name> -w # or pnpm add <package-name> --workspace-root
给指定 workspace(工作空间) 安装依赖
--filter 为 package.json name
/my-monorepo/
pnpm add <package-name> --filter <workspace-name> # or pnpm add lodash --filter docs
更新依赖
更新根目录依赖,看执行路径
pnpm update <package-name> [-w]
更新指定 workspace 依赖
pnpm update <package-name> --filter <workspace-name>
# or
pnpm update lodash --filter docs
卸载依赖
pnpm uninstall <package-name> [-w]
# or
pnpm uninstall <package-name> --filter <workspace-name>
# or
pnpm uninstall lodash --filter docs
执行脚本
执行 workspace 中的脚本
pnpm dev --filter docs
执行所有 workspace 中脚本
pnpm -r dev
# or
$ pnpm --recursive dev
或者在根目录的 package.json 中配置
/my-monorepo/package.json
{ "name": "my-monorepo", "script": { "docs:dev": "pnpm dev --filter docs" } }
直接执行
pnpm docs:dev
安装内部 workspace 依赖
pnpm add <package-name> --filter <workspace-name>
# or
pnpm add web --filter docs
- 请注意你当前的
pnpm版本,在9.0后pnpm修改link-workspace-packages的默认值为false。该属性开启后,你在安装依赖时优先在本地链接,而不是从registry(远程) 中下载。 - 所以在这个版本你若需要使用命令安装一个
新的workspace中的依赖需要在.npmrc中启用link-workspace-packages - 当然主动在
package.json中声明的依赖不受影响,如web: "workspace:*",pnpm还是会自动处理,这种不确定性的执行结果可能是导致pnpm在该版本中禁用了该值
https://github.com/pnpm/pnpm/issues/7954#issuecomment-2062830615
9.x pnpm link-workspace-packages
8.x pnpm link-workspace-packages
在 .npmrc 中
.npmrc
link-workspace-packages = true
或临时启用
pnpm add <package-name> --filter <workspace-name> --link-workspace-packages=true
# or
pnpm add web --filter docs --link-workspace-packages=true
执行结果
/my-monorepo/packages/docs/package.json
{ "name": "docs", "dependencies": { "web": "workspace:^" } }
什么是 workspace:^ - semver 版本
你可能好奇 workspace:^ 是怎么生成的,后面 pnpm 是怎么如何转化的?
当你将依赖项添加到 package.json 中时,pnpm 根据 .npmrc 或命令行中的 save-workspace-protocol 字段来决定是否使用 workspace: 协议,并根据 save-prefix 字段来决定版本的前缀(semver)
例如,save-prefix 为 "~" ,save-workspace-protocol 为 true 时
{
"name": "docs",
"dependencies": {
"web": "workspace:~1.0.0"
}
}
转化
{
"dependencies": {
"foo": "workspace:*",
"bar": "workspace:~",
"qar": "workspace:^",
"zoo": "workspace:^1.5.0"
}
}
{
"dependencies": {
"foo": "1.5.0",
"bar": "~1.5.0",
"qar": "^1.5.0",
"zoo": "^1.5.0"
}
}
总结
常用命令
# 安装依赖
$ pnpm install
# 给指定 workspace 安装依赖
$ pnpm add <package-name> --filter <workspace-name>
# 卸载依赖
$ pnpm uninstall <package-name> --filter <workspace-name>
# 更新依赖
$ pnpm update <package-name> --filter <workspace-name>
# 给根目录安装依赖 - -w 为安装 -workspace-root
$ pnpm add <package-name> -<D>w
# 内部包的互相引用 - 前提 .npmrc 中配置 link-workspace-packages = true
# 若未配置需手动
$ pnpm add <package-name> --filter <workspace-name>
# 执行 workspace 中的脚本, 或者在根目录的 package.json 中配置
$ pnpm dev --filter docs
# 执行所有 workspace 中的脚本
$ pnpm -r dev
# or
$ pnpm --recursive dev
# 删除 workspace 中所有的 node_modules
rm -rf node_modules && pnpm -r exec -- rm -rf node_modules
在 npm 中使用 workspace
项目结构
my-monorepo
├── docs
├── apps
│ └── web
├── packages
│ ├── ui
│ ├── eslint-config
│ └── shared-utils
├── package.json
└── sdk
在 package.json 中配置 workspace
路径语法与 pnpm 一致 => 具体语法 - glob 通配符
{
"name": "my-monorepo",
"workspaces": [
"apps/*",
"packages/*"
]
}
命令初始化子包
根据提供的路径创建 workspace(路径不存在,则创建),并在根目录的 package.json 中添加 workspace 路径(若已经配置相关路径,则不会)
npm init -w ./newFolder/hooks -y
# or
npm init -w ./packages/hooks -y
为 workspace 添加、更新、移除依赖
npm install lodash -w docs
npm uninstall lodash -w docs
npm update lodash -w docs
# or
npm install lodash --workspace=docs
依赖内部的 workspace
与 pnpm 不同的是 npm 是直观的版号,当然你需要修改 semver 规则,可参考 save-prefix
npm install ui -w docs
/my-monorepo/packages/docs/package.json
{ "name": "docs", "dependencies": { "ui": "^1.0.0" } }
执行脚本
npm run dev -w docs
# run many
npm run dev -w docs -w ui
# or
npm run dev --workspace=docs
npm run test --workspace=docs --workspace=ui
批量执行 workspace 内的脚本
--if-present 避免 workspace 中不含当前脚本的报错
npm run dev
# or
npm run dev --if-present
在根目录 package.json 中运行 workspace 中的脚本
在此处配置一些常用的脚本命令
/my-monorepo/package.json
{ "name": "my-monorepo", "script": { "dev": "npm run dev", "docs:dev": "npm run dev -w docs" } }
总结
# 新增子包
npm init -w ./packages/docs -y
# 为子包添加依赖
npm install lodash -w docs
# 运行子包的dev脚本
npm run dev -w docs
# 运行所有 workspace dev脚本
npm run dev
在 yarn 中使用 workspace
yarn 与 npm 大体一致,此处不阐述相同点。
命令
yarn workspaces info - 查询 workspace 之间依赖关系
yarn workspaces info
执行结果:
{
"docs": {
"location": "docs",
"workspaceDependencies": [
"eslint-config",
"typescript-config",
"ui"
],
"mismatchedWorkspaceDependencies": []
},
"web": {
"location": "web",
"workspaceDependencies": [
"eslint-config",
"typescript-config"
],
"mismatchedWorkspaceDependencies": []
}
}
yarn workspace <package-name> <command> - 给 workspace 安装依赖
# 添加依赖
yarn workspace docs add lodash
# 移除依赖
yarn workspace docs remove lodash
yarn add <package-name> -W - 根目录安装依赖
yarn add lodash -w
yarn workspace <package-name> add <package-name@version> - 安装内部依赖
yarn workspace docs add ui@1.0.0
# or
yarn workspace docs add ui@^1.0.0
此处必须加上版本号,你可以手动加上 semver 前缀,但需要与 package.json 中的版本号保持一致即可,否则会去远程下载。
























