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
相关推荐
雨落秋垣6 小时前
在前端把图片自动转换为 WebP 格式
前端
羽沢316 小时前
一些css属性学习
前端·css·学习
二狗哈6 小时前
Cesium快速入门22:fabric自定义着色器
运维·开发语言·前端·webgl·fabric·cesium·着色器
计算衎6 小时前
FastAPI后端和VUE前端的数据交互原理详解
前端·vue.js·fastapi
黑岚樱梦6 小时前
Linux系统编程
java·开发语言·前端
xrl20126 小时前
ruoyi-vue2前端集成DMN规则引擎
前端·规则引擎·工作流·dmn
转转技术团队6 小时前
前端工程化实践:打包工具的选择与思考
前端·javascript·webpack
前端郭德纲7 小时前
React 19.2 已发布,现已上线 npm!
前端·react.js·npm
知其然亦知其所以然7 小时前
JavaScript 变量的江湖恩怨:一篇文章彻底讲清楚
前端·javascript·面试