脚手架开发之多包管理(npm, yarn, pnpm workspaces)

workspaces主要是在仓库根目录建立虚拟依赖树,智能管理子包间的拓扑关系与共享依赖。

当项目演变为 Monorepo 结构时,传统依赖管理面临三重挑战:

  • 依赖重复 :多个子项目共用的相同包时将在各自 node_modules 重复安装。
  • 本地包引用复杂 :本地包(packages/utils)需手动 npm link或者发布到npm中才能被其他本地包(packages/app)使用。
  • 批量操作低效 :为所有子包执行 npm install 耗时极长。

原生包管理工具支持的workspaces功能

npm

为了方便我们一般通过npm cli提供的命令来创建子项目。通过npm init -h查看具体使用规则。注意操作那个子包,需要确定好运行当前命令和当前子包根目录的路径关系。

创建多个包,在那个文件夹下创建,就需要参考哪个目录创建目录文件

js 复制代码
npm init -w ./packages/a -w ./packages/b -y

如果修改子包package.json中的name,需要在父包中运行命令来更新node_modules中的本地包。然后可以手动删除之前映射的子包。

js 复制代码
npm install -ws

对于Monorepo项目来说,如果想要在特定子包中安装第三方包,就需要指定子包的文件名。安装本地子包也是同样的命令。

js 复制代码
// npm install 第三方包名/本地子包名 -w 本地包文件名
npm install lodash -w packages/a
npm install @zhang-glitch/b -w packages/a

运行子包命令

js 复制代码
// npm run <command-name> -w  本地包文件名 // 运行单个子包命令
// npm run <command-name> -ws // 运行全部子包命令,如果某个子包无当前命令,则报错
npm run runIndexA -w packages/a

npm run runIndex -ws

发布包,需要运行以下命令。

js 复制代码
// npm publish -ws // 发布所有包
// npm publish -w 包文件名 // 发布特定包

npm publish -w packages/a

如果发布出现下述问题,我们还需要再package.json中配置

js 复制代码
  "publishConfig": {
    "access": "public"
  }

yarn

v1.x classic

  • 实验性支持 :需在 package.json 中声明 "workspaces" 字段启用。
  • 依赖提升 :所有子包的依赖会提升到根目录的 node_modules(hoisting)。
  • 跨包链接 :自动处理 workspace: 协议依赖的软链接。

yarn没有提供和npm一样的cli来直接创建子包,所以我们需要手动创建配置。

bash 复制代码
#  在父包目录下package.json中输入子包目录
{
  "workspaces": ["packages/*"],
  "private": true
}


# 创建子包
mkdir -p packages/a packages/b

# 初始化子包
cd packages/a && yarn init -y

# 安装本地包到node_modules
yarn install

# 查看创建的子包
yarn workspaces list

注意父项目必须是私有项目。

如果修改子包package.json中的name,需要在父包中运行命令来更新node_modules中的本地包。。

bash 复制代码
yarn install

如果我们想在本地包中使用另一个本地包,需要运行以下命令。注意workspace-name都是子包名称,而不是子包目录,和npm安装方式不一样。

bash 复制代码
// yarn workspace <workspace-name> add <workspace-name>/第三方包

yarn workspace a add @zhang-glitch/b
yarn workspace @zhang-glitch/b add loadsh

运行命令子包命令

bash 复制代码
# yarn workspaces run <command-name> // 运行所有子包命令,如果某个子包没有改命令则报错
yarn workspaces run runIndex

# yarn workspace <package-name> <command-name> // 运行单个子包命令
yarn workspace a runIndex

发布包,注意发布的包名必须唯一。

bash 复制代码
# 原生未提供发布所有子包的命令
# yarn workspace 子包名称 publish // 发布特定包 

yarn workspace @zhang-glitch/yarn-classic-a-demo publish

如果发布出现下述问题,我们还需要再package.json中配置

js 复制代码
  "publishConfig": {
    "access": "public"
  }

发布成功

v2.x+ breey

如果是v1.x版本,且Node.js >=16.10,可以使用corepack prepare yarn@stable --activate来更新全局yarn版本,或者使用yarn set version berry来升级yarn版本。如果安装不成功可以通知系统代理。

js 复制代码
# 设置 HTTP 代理
yarn config set proxy http://127.0.0.1:7890

# 设置 HTTPS 代理
yarn config set https-proxy http://127.0.0.1:7890

yarn没有提供和npm一样的cli来直接创建子包,所以我们需要手动创建配置。

bash 复制代码
#  在父包目录下package.json中输入子包目录
{
  "workspaces": ["packages/*"]
}


# 创建子包
mkdir -p packages/a packages/b

# 初始化子包
cd packages/a && yarn init -y

# 安装本地包到node_modules
yarn install

# 查看创建的子包
yarn workspaces list

如果运行yarn install报这个错误,表示当前目录不是workspaces项目的根目录(即当前目录之前目录中也存在package.json,.yarnrc.yml文件)

删除后即可运行成功,并在根目录生成node_modlues,将本地子包安装进去。 如果修改子包package.json中的name,需要在父包中运行命令来更新node_modules中的本地包。。

bash 复制代码
yarn install

如果想要在特定子包中安装第三方包,就需要指定子包包名。安装本地子包也是同样的命令。

bash 复制代码
yarn workspace 本地子包名 add 另一个本地包名/三方包

yarn workspace a add @zhang-glitch/yarn-berry-b-demo@workspace:*
yarn workspace a add lodash

如果按照上面的步骤执行后,再运行yarn workspace a add @zhang-glitch/berry-b-demo@workspace:*是不成功的,具体解决方式可以看这里即删除子包中的yarn.lock文件即可链接成功。

运行命令子包命令

bash 复制代码
# yarn workspaces foreach --all -p run <command-name> // 运行所有子包命令,如果某个子包没有该命令不会报错
 yarn workspaces foreach --all -p run runIndex

# yarn workspace <package-name> <command-name> // 运行单个子包命令
yarn workspace a runIndex

发布包,注意发布的包名必须唯一。

bash 复制代码
# 发布所有子包的命令
yarn workspaces foreach --all -p npm publish
# 发布特定包 
# yarn workspace 子包名称 npm publish /  yarn --cwd 包路径(packages/b) npm publish

yarn workspace @zhang-glitch/yarn-berry-b-demo npm publish
yarn --cwd packages/b npm publish

发布时,我们还需要在package.json中配置

js 复制代码
  "publishConfig": {
    "access": "public"
  }

发布成功

pnpm

pnpm没有提供和npm一样的cli来直接创建子包,所以我们需要手动创建配置。

bash 复制代码
#  在父包目录下pnpm-workspace.yaml中输入子包目录
packages:
  - 'packages/*'       # 匹配所有子包


# 创建子包
mkdir -p packages/a packages/b

# 初始化子包
cd packages/a && pnpm init

# 安装本地包到node_modules
pnpm install

如果修改子包package.json中的name,需要在父包中运行命令来更新node_modules中的本地包。。

bash 复制代码
pnpm install

如果想要在特定子包中安装第三方包,就需要指定子包包名。安装本地子包也是同样的命令。

bash 复制代码
# pnpm add 另一个本地包名/三方包 --filter 包名

pnpm add lodash --filter a
pnpm add axios --filter @zhang-glitch/pnpm-b-demo
pnpm add @zhang-glitch/pnpm-b-demo@workspace:* --filter a

运行命令子包命令

bash 复制代码
# pnpm run --recursive <command-name> // 运行所有子包命令,如果某个子包没有该命令不会报错
  pnpm run --recursive runIndex

# pnpm run --filter 包名 <command-name>  // 运行单个子包命令
pnpm run --filter a runIndex

发布包,注意发布的包名必须唯一。

bash 复制代码
# 发布所有子包的命令
# pnpm publish --recursive/-r
# 发布特定包 
# pnpm publish --filter 子包名

yarn workspace @zhang-glitch/yarn-classic-a-demo publish

如果发布出现下述问题,我们还需要再package.json中配置

js 复制代码
  "publishConfig": {
    "access": "public"
  }

发布成功

npm, yarn, pnpm workspaces区别

特性 npm Yarn (v1/Classic) Yarn Berry (v2+) pnpm
配置文件 package.json package.json package.json + .yarnrc.yml pnpm-workspace.yaml
依赖提升 ✅ (根目录 hoisting) ✅ (智能 hoisting) ❌ (默认 PnP 无 node_modules) ❌ (隔离依赖)
跨包依赖链接 file: 协议 workspace: 协议 workspace: 协议 workspace: 协议
安装性能 较慢 快 (v1) / 极快 (Berry) 极快 (零安装) 最快 (硬链接复用)
磁盘占用 中等 最低 (PnP) (全局存储复用)
依赖隔离 ❌ (易幽灵依赖) ⚠️ (可配置) ✅ (PnP 严格解析) ✅ (默认隔离)
Monorepo 工具链 需配合 Lerna 内置 + Lerna 兼容 内置 + 插件扩展 内置 + 兼容 Lerna

npm Workspaces

  • 启用版本:npm ≥ v7.0
  • 核心机制
    • 顶层 package.json 中定义 "workspaces" 字段(支持数组或 packages/* 通配符)。
    • 依赖提升到根级 node_modules(hoisting),子包共享依赖。
  • 特色
    • 内置支持:无需额外工具,原生集成。
    • 依赖安装npm install 自动安装所有工作区依赖。
    • 命令执行npm run <command> -w <package>--workspaces(全包执行)。
  • 局限
    • 依赖提升问题:可能引发版本冲突(不同子包需同一依赖的不同版本时)。
    • 符号链接:子包通过符号链接(symlink)连接到根目录,但依赖解析仍以根目录为准。
    • 性能:安装速度较慢,磁盘占用较高。

Yarn Workspaces

  • 启用版本:Yarn v1(Classic)及 Yarn ≥ v2(Berry)
  • 核心机制
    • 类似 npm,根目录 package.json 中定义 "workspaces"
    • 依赖优化:更智能的 hoisting 策略,支持选择性提升。
  • 特色
    • 依赖协议 :支持 workspace: 协议(如 "dependency": "workspace:package-a"),直接链接本地包。
    • 跨包依赖:自动处理子包之间的依赖关系,避免发布前手动构建。
    • 高效安装:依赖去重和缓存机制优于 npm。
    • Yarn Berry (v2+)
      • 零安装 :依赖缓存到 .yarn/cache(可提交至 Git)。
      • Plug'n'Play (PnP) :跳过 node_modules,直接解析缓存依赖,提升速度和安全性。

pnpm Workspaces

  • 启用机制 :根目录 pnpm-workspace.yaml 定义包路径。
  • 核心特色
    • 内容可寻址存储 :依赖存储在全局 ~/.pnpm-store,硬链接到各项目,节省磁盘。
    • 隔离性 :默认使用 symlink + 硬链接,子包依赖不提升到根目录,避免幽灵依赖。
    • 严格模式 :子包只能访问显式声明的依赖(通过 .npmrc 设置 node-linker=isolated)。
  • 优势
    • 安装速度最快:依赖复用率极高。
    • 磁盘空间优化:相同依赖只存储一份。
    • 依赖安全:杜绝隐式依赖访问。

往期年度总结

往期文章

专栏文章

🔥如果此文对你有帮助的话,欢迎💗关注 、👍点赞 、⭐收藏✍️评论, 支持一下博主~

公众号:全栈追逐者,不定期的更新内容,关注不错过哦!

相关推荐
To_OC2 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic2 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy2 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志3 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble4 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯4 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜5 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司5 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森6 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http
IT_陈寒7 小时前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端