npm Workspaces 入门指南
了解 npm workspaces 的设置、命令和限制:管理 monorepo、链接兄弟包,并判断何时需要 Turborepo 或 Nx。
npm workspaces 自 npm 7 版本起内置于 npm 中,允许你从单一根目录管理一个代码仓库(即 monorepo)中的多个包:执行一次 npm install 即可将共享依赖提升至单一根目录的 node_modules 中,并将你自己的包以符号链接的形式放置其中,从而无需 npm link 或重新发布即可解析跨包导入。如果你有一个应用加上一个共享库,或者一个组件库加上其文档站点,并且已经厌倦了 npm link、复制粘贴代码或管理多个独立仓库,这个内置功能可以消除这些摩擦——无需任何第三方工具。本指南涵盖最简配置、精确的命令标志、实际限制,以及何时需要在其之上叠加构建编排工具。
核心要点
- npm workspaces 随 npm 7+ 一同发布;当前版本为 npm 11.18.0,可通过
npm -v确认你的版本。 - 最简配置只需两个文件:根目录的
package.json(包含"private": true和"workspaces": ["packages/*"]),以及每个包各自的package.json——然后在根目录执行一次npm install即可完成所有关联。 - 若要依赖同级包,使用
"*"版本范围按名称添加;npm 在安装时会创建符号链接,因此对源码的修改可立即在所有消费方中生效,无需重新构建或重新发布。 - npm workspaces 负责解析和链接依赖,但不会按依赖顺序运行任务、缓存构建输出或计算”受影响”图谱。
- 请在 npm workspaces 之上 叠加 Turborepo 或 Nx,而非用它们替代 npm workspaces——npm 负责解析和链接包,这些工具则提供任务编排和缓存能力。
npm workspaces 是如何工作的?
npm workspaces 通过将共享依赖提升至单一根目录的 node_modules 并将你自己的包以符号链接的形式放置其中,将单一代码仓库转变为 monorepo。当你在根目录运行 npm install 时,npm 会扫描每个 workspace,在顶层统一安装第三方依赖,并根据 name 字段将每个本地包链接至 node_modules。如果你的两个包相互依赖,引用会通过该符号链接进行解析——npm CLI 将链接过程作为 npm install 的一部分自动完成,无需手动运行 npm link。
相同的 workspaces 字段和符号链接模型同样适用于 Yarn、pnpm 和 Bun,因此这一思维模型可在不同包管理器之间通用。该功能在 npm 7 中引入,任何更新的版本均可使用。
Discover how at OpenReplay.com.
npm workspaces 的最简配置是什么?
最简配置只需两个文件:一个声明包所在位置的根目录 package.json,以及每个包各自的 package.json。创建如下目录结构:
my-monorepo/
├── package.json # 根目录——private,列出 workspaces
└── packages/
├── utils/
│ └── package.json # @myorg/utils
└── app/
└── package.json # @myorg/app
根目录的 package.json 需要两个字段:
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*"]
}
"private": true 可防止你意外发布根目录,packages/* glob 告知 npm 将 packages/ 下的每个目录视为一个 workspace。为每个包指定作用域名称(如 @myorg/utils)以避免注册表命名冲突:
{
"name": "@myorg/utils",
"version": "1.0.0",
"main": "dist/index.js"
}
在根目录执行一次 npm install。根目录存在单一 lockfile,各个包内部不存在 node_modules——所有内容均提升至根目录。
添加跨包依赖
若要依赖同级包,使用 "*" 版本范围按名称添加;npm 在安装时会创建符号链接,因此对源码的修改可立即在所有消费方中生效。在 @myorg/app 中:
{
"name": "@myorg/app",
"dependencies": {
"@myorg/utils": "*"
}
}
再次在根目录运行 npm install。npm 会创建一个从 node_modules/@myorg/utils 指向 packages/utils 的符号链接,你可以像导入任何已发布模块一样导入它:
import { formatDate } from "@myorg/utils";
由于使用的是符号链接,修改 packages/utils 中的源码会立即反映到 app 中,无需重新构建或重新发布——这正是相较于 npm link 的优势所在。有一个跨工具注意事项:npm 不支持 pnpm 和 Yarn Berry 使用的 workspace: 版本协议。传入 workspace: 说明符会导致 npm 报 EUNSUPPORTEDPROTOCOL 错误,因此在使用 npm 时,请通过名称和版本范围("*")引用内部包,而非使用 workspace:*。
日常命令
这些标志容易让人混淆,因为单数和复数形式含义不同。使用 -w 向单个包添加依赖,使用 --workspaces 向每个包添加依赖;使用 -w 在单个 workspace 中运行脚本,使用 --workspaces --if-present 在所有 workspace 中运行脚本(会跳过未定义该脚本的包)。
# 向单个 workspace 安装依赖
npm install lodash -w @myorg/app
# 向单个 workspace 安装开发依赖
npm install -D vitest -w @myorg/utils
# 向所有 workspace 安装依赖
npm install eslint --workspaces
# 在单个 workspace 中运行脚本
npm run build -w @myorg/utils
# 在所有 workspace 中运行脚本,跳过未定义该脚本的包
npm run test --workspaces --if-present
-w 是 --workspace 的简写,--workspaces(或 -ws)则针对所有 workspace。在根目录统一配置脚本,使 npm run build 可以扩散执行:
{
"scripts": {
"build": "npm run build --workspaces --if-present",
"test": "npm run test --workspaces --if-present"
}
}
若要验证依赖图是否已正确链接,运行 npm ls -ws 或使用 npm query .workspace 进行查询。
局限性:npm workspaces 无法做到的事
npm workspaces 负责解析和链接依赖,但不会按依赖顺序运行任务、缓存构建输出或计算”受影响”图谱。如果你的应用导入了某个库,你必须先构建该库——当 workspace 之间存在相互依赖时,跨 workspace 运行脚本会报错,因为 npm 不按拓扑顺序执行,这一增强功能至今仍处于开放状态。请显式排序,或使用 npm-run-all:
{
"scripts": {
"build:utils": "npm run build -w @myorg/utils",
"build:app": "npm run build -w @myorg/app",
"build": "npm run build:utils && npm run build:app"
}
}
另外两个注意事项:
-
嵌套
node_modules。 当两个包依赖同一依赖的不兼容版本时,npm 会停止提升并在某个包内部安装嵌套副本。可通过根目录的overrides字段固定单一共享版本以保持依赖树扁平:{ "overrides": { "lodash": "^4.17.21" } } -
安装脚本的默认行为正在收紧。 npm v12(预计于 2026 年 7 月发布)将
allowScripts的默认值改为关闭,因此npm install将不再自动运行依赖的preinstall、install或postinstall脚本,除非显式允许。如果你的 workspace 依赖postinstall或prepare构建步骤,请提前规划审批流程——这些变更在 npm 11.16.0 或更新版本中会以警告形式提前呈现,以便你做好准备。
需要注意的是,“不原生集成 React/Vue/Vite”是功能范围的声明,而非缺陷:workspaces 在设计上与框架无关,脚手架应用不在其职责范围之内。
何时需要引入 Turborepo 或 Nx
请在 npm workspaces 之上 叠加 Turborepo 或 Nx,而非用它们替代 npm workspaces:npm 负责解析和链接你的包,而这些工具则为大型仓库提供任务编排、缓存和受影响图谱构建能力。它们是互补的分层工具。
| 关注点 | npm workspaces | Turborepo / Nx |
|---|---|---|
| 安装与链接包 | ✅ | 委托给 npm |
| 任务依赖顺序 | ❌ 手动脚本 | ✅ 拓扑排序 |
| 构建/测试缓存 | ❌ | ✅ 本地 + 远程 |
| ”受影响”构建 | ❌ | ✅ 基于变更的图谱 |
当有序脚本变得难以管理、CI 在每次变更时重新构建所有内容,或者你希望仅对某次提交涉及的包运行任务时,可以考虑引入上述工具。需要注意的是,现代 Lerna 现已由 Nx 提供支持——旧有的”npm + Lerna”建议已融入这一分层模式中。
npm workspaces 在零额外工具的情况下,大约能满足小型 monorepo 约 80% 的需求。搭建好两文件配置,配置好命令标志,排好构建顺序,仅在流水线(而非依赖解析)成为瓶颈时再引入编排工具。请在 Active LTS Node 版本上运行(Node 20 已于 2026-04-30 停止维护),并在开始前确认 npm -v 报告的版本为 7 或更新。
常见问题
npm workspaces 是每个包各有一个 lockfile,还是根目录只有一个?
npm workspaces 在仓库根目录生成单一的 package-lock.json,而非每个包各一个。在根目录执行 npm install 会统一解析所有 workspace 的依赖并记录在该 lockfile 中,各个包不会生成自己的 node_modules 目录,因为依赖会提升至根目录。这种单一 lockfile 模式正是保持所有包版本一致的原因,也是为什么始终要从根目录执行安装操作。
为什么当包之间存在相互依赖时,'npm run build --workspaces' 会失败?
失败的原因是 npm 不会按拓扑顺序(依赖顺序)运行 workspace 脚本,而是按 workspaces 的列出顺序运行,因此消费方可能在其依赖的库存在之前就开始构建,从而产生“cannot find module”或解析失败的错误。这仍是一个开放的 npm 增强请求(issue 4139)。解决方法是定义显式的有序脚本,先构建库,或者使用 npm-run-all、Turborepo 或 Nx 等工具。
使用 npm 时能否像在 pnpm 或 Yarn 中那样使用 'workspace:*' 协议?
不能。npm 不支持 pnpm 和 Yarn Berry 使用的 workspace: 版本协议,传入 workspace: 说明符会导致 npm 报 EUNSUPPORTEDPROTOCOL 错误(记录于 npm/cli issue 8845)。在使用 npm 时,请通过名称和普通版本范围(如 '@myorg/utils': '*')引用内部包;npm 会在安装时创建符号链接。如果你将 pnpm 或 Yarn 仓库迁移至 npm,需要将所有 workspace: 说明符改写为普通版本范围。
使用 workspaces 时还需要 'npm link' 吗?
不需要。npm workspaces 将链接过程作为 npm install 的一部分自动完成,根据 name 字段将每个本地包以符号链接的形式放入根目录的 node_modules 中,从而无需手动运行 npm link。一旦某个包以 '*' 版本范围将同级包列为依赖,在根目录执行一次 npm install 即可建立符号链接,对源包的修改可立即在所有消费方中生效,无需重新构建或重新发布。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k