pnpm优化理念 - 幻影依赖、monorepo - 升级npm

pnpm 通过「内容寻址存储 + 非扁平 node_modules + workspace 本地链接

同时解决了 磁盘浪费、幻影依赖、monorepo 复用 三个核心问题。

pnpm 核心设计思想

1. 存储分层

内容寻址存储 + 硬链接 + 软链接

  1. ~./pnpm-store/ 内容寻址存储

    全局、跨项目

    tsx 复制代码
    ~/.pnpm-store/
    └── v3/
        └── files/
            └── <hash>...

    在用户主目录下(不是项目里)是跨项目的

    是全机器唯一的 content-addressable store

    以内容 hash 存储,是真正源文件所在地,确保了内容唯一性

  2. .pnpm/ 硬链接

    指向全局 store 文件的 硬链接reflink

    以及描述依赖关系的 软链接结构symlink

  3. node_module/ 软链接

    在项目根目录下,为 Node / bundler 提供传统 node_modules 解析入口

    本身是不存放文件内容的

2. 链接策略

tsx 复制代码
registry 依赖
workspace 本地包

当遇到

tsx 复制代码
{
  "dependencies": {
    "@repo/utils": "workspace:*"
  }
}

时,不去 store 实现硬链接到项目 .pnpm

而是

  1. 解析阶段

    tsx 复制代码
    @repo/utils
    └── 来源:workspace 内的 packages/utils

    绕过 npm registry 、绕过版本下载、直接认定时本地包

  2. 链接阶段

    tsx 复制代码
    apps/web/node_modules/@repo/utils
       └── symlink → ../../packages/utils

    并不是像依赖去 store → .pnpm → node_modules

    而是直接 symlink 到 workspace 包目录

    (workspace包的依赖仍然是走上面的pnpm的三层)

优点

1. 解决了幻影依赖

在依赖配置中并没有声明 但是在源码中却使用了,这是因为声明的直接依赖依赖了这个间接依赖,所以就会拉下来

根因

计算机的文件结构是树结构,但依赖是图结构

早期 npm 是将图转为树(子文件夹),所以是没有幻影依赖的问题的,但是由于存在嵌套层次深、重复包耗散的问题,

所以 npm / yarn 后面对 node_modules 进行扁平化,但是这破坏了依赖边界,导致了幽灵依赖

幽灵依赖导致问题

  1. 版本问题

    直接依赖升级导致间接依赖升级后API不兼容

  2. 依赖丢失问题

    开发依赖包A依赖了包B

    在开发后进行上传,上传后下拉方没有恢复开发依赖,只恢复了生产环境的依赖

解决办法 - pnpm

不论直接还是间接都放到统一仓库store(.pnpm)中

而项目中的 node_modules 和 npm 最开始的树形结构一样,但是存的内容不一样,pnpm中存的是 仓库store 对应内容的软链接(指针)

2. monorepo

可以对照着通过monorepo架构支持国际化学习

背景 - 现有问题

钱管家在进行跨国多地部署时,多端都需要很多依赖,搭建成本大,磁盘消耗多。为了复用多项目中都用到的部分

技术考量

主要考虑到了 pnpm 的两个核心特性

  1. .pnpm 是硬链接的全局唯一的 ~./pnpm-store,确保了不会有重复的磁盘占用

  2. workspace 支持 本地包 自动 link,且是 真实源码级别的 软链接symlink,而不是复制或模拟发布包

    tsx 复制代码
    packages/
      utils/
      ui/
    apps/
      web/

    apps/web 里写:

    tsx 复制代码
    {
      "dependencies": {
        "@repo/utils": "workspace:*"
      }
    }

    pnpm 会直接 软链接本地包 ,不走 registry 、不发 npm 也能用

    workspace 并不是共享 node_module , 每个包仍然有自己的依赖边界,但是底层 store 共享,本地包通过 workspace link

jsx 复制代码
my-monorepo/
├── package.json
├── pnpm-workspace.yaml
├── packages/
│   ├── utils/
│   │   └── package.json
│   ├── ui/
│   │   └── package.json
├── apps/
│   ├── web/
│   │   └── package.json
│   ├── admin/
│   │   └── package.json
└── tsconfig.json

packages/ 放所有可复用模块(组件库、工具库、hooks等)

apps/最终应用(web/PC端、H5、Node服务等

跨包命令

tsx 复制代码
pnpm -r build        # 所有包 build
pnpm -F web dev      # 只跑 web
pnpm -F "@repo/*" lint

步骤

  1. 配置 pnpm-workspace.yaml
jsx 复制代码
packages:
  - "packages/*"
  - "apps/*"

pnpm 会自动扫描这些目录,把每个子目录识别为 workspace 包

  1. 根packages.json(管理脚本)

package.json 通常无需 dependencies ,只写 script

jsx 复制代码
{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "dev": "pnpm -r --parallel dev",
    "build": "pnpm -r build",
    "lint": "pnpm -r lint"
  }
}
  1. 创建子包(例如utils)

在 packages/utils/package.json

jsx 复制代码
{
  "name": "@my/utils",
  "version": "1.0.0",
  "main": "src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  }
}
  1. 让 apps 引用 workspace 包

如果 apps/web 想用utils

在 apps/web/package.json

jsx 复制代码
{
  "name": "web",
  "dependencies": {
    "@my/utils": "workspace:*"
  }
}

workspace:* 表示依赖来自 workspace 不是 npm registry

  1. 安装依赖
jsx 复制代码
pnpm install

3. 速度快

并发拉包

命中缓存直接跳过下载

4. 锁文件lockfile明确

pnpm-lock.yaml 是 全局依赖图快照,不是 node_modules 的结构快照,因此更稳定、跨平台一致

5. CI 友好

  • pnpm-store 可缓存
  • pnpm-lock.yaml 稳定
  • node_modules 结构可预测

npm 升级为 pnpm

tsx 复制代码
rm -rf node_modules package-lock.json .npmrc

.npmrc

tsx 复制代码
# 严格模式,防止幻影依赖
node-linker=hoisted
shamefully-hoist=false
strict-peer-dependencies=false

# 使用国内镜像加速
registry=https://registry.npmmirror.com/

pnpm-workspace.yaml

tsx 复制代码
packages:
  - '.'
tsx 复制代码
{
  "filePath": "/Users/a86198/front-sandbox-scaffold/package.json",
  "oldString": "  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"npm run dev\",\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  },",
  "newString": "  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"pnpm run dev\",\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  },"
}
tsx 复制代码
"start": "pnpm run dev",

/*

没装 pnpm 的话得先装

tsx 复制代码
npm install -g pnpm

*/

tsx 复制代码
pnpm install
相关推荐
zhuà!5 分钟前
uv-picker在页面初始化时,设置初始值无效
前端·javascript·uv
Amumu121386 分钟前
React应用
前端·react.js·前端框架
摸鱼的春哥9 分钟前
实战:在 Docker (Windows) 中构建集成 yt-dlp 的“满血版” n8n 自动化工作流
前端·javascript·后端
小酒星小杜15 分钟前
在AI时代,技术人应该每天都要花两小时来构建一个自身的构建系统
前端·vue.js·架构
测试游记16 分钟前
基于 FastGPT 的 LangChain.js + RAG 系统实现
开发语言·前端·javascript·langchain·ecmascript
阿奇__17 分钟前
elementUI table 多列排序并保持状态样式显示正确(无需修改源码)
前端·vue.js·elementui
zhengxianyi51522 分钟前
数据大屏-单点登录ruoyi-vue-pro
前端·javascript·vue.js
我想回家种地25 分钟前
python期末复习重点
前端·javascript·python
行者9627 分钟前
Flutter适配OpenHarmony:高效数据筛选组件的设计与实现
开发语言·前端·flutter·harmonyos·鸿蒙
Serendipity-Solitude36 分钟前
HTML 五子棋实现方法
前端·html