环境与版本

  • platform => mac os
  • node => v22.2.0
  • npm => 10.8.0
  • pnpm => 9.1.3
  • yarn => 1.22.22

名词解释

monorepo

利用单一仓库来管理多个 packages 的一种策略,如早期的 lerna

workspace

由上述单仓多包催生的管理方式,workspace(工作空间) 是 npmyarnpnpm 等包管理工具提供的一种特性,用于管理多个包的依赖关系。
合理配置 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.
如文档描述,启用 pnpmworkspace 需要在项目根目录创建 pnpm-workspace.yaml

项目结构

my-monorepo
├── docs
├── apps
│   └── web
├── packages
│   ├── ui
│   ├── eslint-config
│   └── shared-utils
├── pnpm-workspace.yaml
├── .npmrc => optional
└── sdk

根目录 package.json

private: If you set “private”: true in your package.json, then npm will refuse to publish it.

私有化为 true 时,在 publishnpm 将不会处理该 package,你可以在项目的根目录配置,或在不需要被 publishworkspace 中配置它

/my-monorepo/package.json
{ "name": "my-monorepo", "private": true, "script": { "dev": "pnpm -r dev" } }

pnpm-workspace.yaml

在该项目中指定位于 my-monorepo/apps/my-monorepo/packages/ 内的直接子目录为工作区如 webui
docs 本身则为一个工作区,则不需要通配符

pnpm-workspace.yaml
packages: - "docs" - "apps/*" - "packages/*"

具体语法 - glob 通配符

pnpm-workspace.yaml

pnpm-workspace.yaml
packages: # 选择 packages 目录下的所有首层子目录的包 - 'packages/*' # 选择 components 目录下所有层级的包 - 'components/**' # 排除所有包含 test 的包 - '!**/test/**'

安装依赖

/my-monorepo/
pnpm install

在根目录中安装依赖

–workspace-root

/my-monorepo/
pnpm add <package-name> -w # or pnpm add <package-name> --workspace-root

给指定 workspace(工作空间) 安装依赖

--filterpackage.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.0pnpm 修改 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-protocoltrue

save-workspace-protocol

{
  "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

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

yarnnpm 大体一致,此处不阐述相同点。

命令

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 中的版本号保持一致即可,否则会去远程下载。