脚手架开发之多包管理(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)。
  • 优势
    • 安装速度最快:依赖复用率极高。
    • 磁盘空间优化:相同依赖只存储一份。
    • 依赖安全:杜绝隐式依赖访问。

往期年度总结

往期文章

专栏文章

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

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

相关推荐
Fantastic_sj20 分钟前
CSS-in-JS 动态主题切换与首屏渲染优化
前端·javascript·css
鹦鹉00723 分钟前
SpringAOP实现
java·服务器·前端·spring
崎岖Qiu1 小时前
【JVM篇11】:分代回收与GC回收范围的分类详解
java·jvm·后端·面试
再学一点就睡4 小时前
手写 Promise 静态方法:从原理到实现
前端·javascript·面试
再学一点就睡4 小时前
前端必会:Promise 全解析,从原理到实战
前端·javascript·面试
前端工作日常5 小时前
我理解的eslint配置
前端·eslint
前端工作日常5 小时前
项目价值判断的核心标准
前端·程序员
90后的晨仔6 小时前
理解 Vue 的列表渲染:从传统 DOM 到响应式世界的演进
前端·vue.js
OEC小胖胖6 小时前
性能优化(一):时间分片(Time Slicing):让你的应用在高负载下“永不卡顿”的秘密
前端·javascript·性能优化·web
烛阴6 小时前
ABS - Rhomb
前端·webgl