核心定位与诞生背景

  1. 定义:pnpmZoltan Kochan2016 年创建的包管理器,完全兼容 npm 生态,目标是在速度、磁盘效率、依赖确定性上全面超越传统工具;
  2. 解决的痛点:
    1. 磁盘冗余:npm/yarn 每个项目独立复制依赖,多项目重复占用大量空间;
    2. 幽灵依赖:扁平化 node_modules 导致可引用未声明依赖,引发线上问题;
    3. 安装缓慢:重复下载、解压、复制,大型项目耗时久;
    4. Monorepo 管理繁琐:原生支持不足,配置复杂;

核心原理:内容可寻址存储 + 软硬链接

  1. pnpm 的核心竞争力源于独特的文件系统设计,全局一份存储、项目链接复用;

  2. 内容可寻址存储 (CAS)

    1. 所有依赖包按内容哈希存储在全局仓库 (默认 ~/.pnpm-store)
    2. 同一版本的包只存一份;不同版本仅存差异文件 (如 lodash 新版仅改 1 个文件,只新增该文件)
    3. 物理上唯一,多项目共享,从根源消除冗余;
  3. 硬链接 (Hard Link)

    1. 项目 node_modules/.pnpm/ 下的文件是全局存储的硬链接,指向同一份磁盘数据 (相同 inode)
    2. 硬链接几乎不占额外空间,读写性能与原文件一致,Node.js 无感知;
    3. 修改硬链接会同步修改全局存储 (适合只读依赖)
  4. 符号链接 (Symlink)

    1. 项目根 node_modules/ 下的直接依赖 (如 react) 是符号链接,指向 .pnpm/react@x.x.x/node_modules/react
    2. 构建严格的依赖树:仅暴露 package.json 声明的包,禁止访问未声明依赖 (杜绝幽灵依赖)
  5. node_modules 结构 (关键)

    1. 非扁平化:依赖的依赖不提升到顶层,严格隔离;
    2. 严格可见性:代码只能 importnode_modules/ 下的包,无法直接访问 .pnpm/ 内的深层依赖;
    项目根
    └── node_modules/
        ├── .pnpm/                # 所有依赖的硬链接集合(全局存储映射)
        │   ├── react@18.2.0/
        │   │   └── node_modules/
        │   │       ├── react/    # 硬链接到全局存储
        │   │       └── scheduler/ # react 的直接依赖(硬链接)
        │   └── ...其他依赖
        ├── react/                # 符号链接 → .pnpm/react@18.2.0/node_modules/react
        └── ...其他直接依赖
    

核心优势

  1. 极致磁盘效率:

    1. 相同版本依赖全局仅存一份,多项目共享;
    2. 实测:10 个相似项目可节省 70%–90% 磁盘空间;
    3. 大型项目 (500+ 依赖)npm/yarn200MBpnpm 仅几十 MB
  2. 极速安装:

    1. 首次安装:并行下载 + 高效解析;
    2. 二次安装:仅做链接操作,无复制 / 解压,速度提升 2–3 倍;
    3. CI/CD 场景优势更明显;
  3. 严格依赖隔离 (无幽灵依赖)

    1. 仅暴露 package.json 声明的依赖,未声明包无法被引用;
    2. 避免 “代码能跑但依赖缺失” 的线上故障,提升项目健壮性;
  4. 原生 Monorepo 支持:

    1. 内置 pnpm workspace,配置简单、性能优异;
    2. 多包共享依赖、跨包引用、统一脚本管理,比 npm/yarn 更轻量高效;
    3. VueViteElement Plus 等主流项目均采用;
  5. 确定性与兼容性:

    1. 锁文件 pnpm-lock.yaml 确保跨环境依赖完全一致;
    2. 100% 兼容 npm 生态、私有源、package.json 所有字段;
    3. 支持 Node.js 版本管理 (pnpm env use)

安装与基础使用

  1. 安装 (推荐方式)

    # 1. Node.js 16.13+ 内置 corepack(推荐)
    corepack enable
    corepack prepare pnpm@latest --activate
    
    # 2. npm 全局安装
    npm install -g pnpm
    
    # 3. macOS/Linux(Homebrew)
    brew install pnpm
    
    # 4. Windows(Chocolatey)
    choco install pnpm
    
    # 验证
    pnpm -v
    
  2. 常用命令 (与 npm 高度兼容)

    功能 pnpm 命令 对应 npm 命令
    初始化项目 pnpm init npm init
    安装所有依赖 pnpm install / pnpm i npm install
    安装生产依赖 pnpm add npm install
    安装开发依赖 pnpm add -D npm install -D
    全局安装 pnpm add -g npm install -g
    卸载依赖 pnpm remove npm uninstall
    运行脚本 pnpm run dev / pnpm dev npm run dev
    查看依赖树 pnpm list npm list
    清理缓存 pnpm store prune npm cache clean --force

pnpm vs npm vs yarn(核心对比)

维度 npm Yarn pnpm
磁盘占用 (多项目重复) (扁平仍冗余) 极低 (全局共享)
安装速度 (串行 / 早期) (并行) 最快 (链接为主)
幽灵依赖 严重 (扁平) 存在 (严格隔离)
Monorepo 一般 原生强大
锁文件 package-lock.json yarn.lock pnpm-lock.yaml
node_modules 扁平 扁平 非扁平 (链接结构)
生态兼容 100% 99% 95%+ (主流兼容)

Monorepo 配置案例

整体目录结构

  1. 搭建一个包含 3 个子包的 Monorepo 项目:

    1. @shop/ui:公共 UI 组件库 (供其他包复用)
    2. @shop/api:接口请求封装 (供业务包复用)
    3. @shop/web:电商前端主应用 (依赖 ui 和 api 包)
  2. 目录结构:

    shop-monorepo/          # 根目录
    ├── package.json        # 根项目配置(公共脚本、依赖)
    ├── pnpm-workspace.yaml # pnpm workspace 核心配置
    ├── .npmrc              # pnpm 私有配置(可选)
    └── packages/           # 所有子包目录
        ├── ui/             # UI 组件库子包
        │   ├── package.json
        │   └── src/
        │       └── index.ts
        ├── api/            # 接口封装子包
        │   ├── package.json
        │   └── src/
        │       └── index.ts
        └── web/            # 前端主应用
            ├── package.json
            └── src/
                └── main.ts
    

核心配置步骤

  1. 初始化根项目:

    # 1. 创建根目录并进入
    mkdir shop-monorepo && cd shop-monorepo
    
    # 2. 初始化 pnpm 项目(生成 package.json)
    pnpm init -y
    
    # 3. 创建 packages 目录(存放所有子包)
    mkdir -p packages/{ui,api,web}
    
  2. 配置 pnpm-workspace.yaml (核心)

    1. 在根目录创建 pnpm-workspace.yaml,指定需要管理的子包路径;
    2. 作用:pnpm 会识别这些目录为 workspace 子包,支持跨包依赖、统一脚本执行等;
    # pnpm-workspace.yaml
    packages:
      # 匹配 packages 下所有子目录(核心)
      - 'packages/*'
      # 可选:排除不需要管理的目录(比如测试目录)
      - '!packages/**/test'
      # 可选:如果有单独的 docs 包,也可加入
      # - 'docs'
    
  3. 配置根 package.json

    1. 根项目主要用于:
      • 声明所有子包共享的开发依赖 (如 TypeScript、ESLint 等)
      • 定义全局脚本 (如一键构建所有子包)
    2. 关键说明:
      • "private": true:必须设置,否则 pnpm 会禁止发布 Monorepo 根包;
      • pnpm -r-r 表示递归执行 (所有子包)
      • pnpm --filter <包名>–filter 表示过滤指定子包执行脚本;
    // package.json(根目录)
    {
      "name": "shop-monorepo",
      "private": true, // 关键:标记为私有包,避免发布到 npm
      "version": "1.0.0",
      "scripts": {
        // 全局脚本:安装所有子包依赖(根目录执行 pnpm install 即可)
        "install:all": "pnpm install",
        // 全局脚本:构建所有子包(按依赖顺序执行)
        "build:all": "pnpm -r build",
        // 全局脚本:只构建 ui 包
        "build:ui": "pnpm --filter @shop/ui build",
        // 全局脚本:启动 web 应用
        "dev:web": "pnpm --filter @shop/web dev",
        // 全局脚本:清理所有 node_modules
        "clean": "pnpm -r exec rm -rf node_modules && rm -rf node_modules"
      },
      // 所有子包共享的开发依赖(只装在根目录,节省磁盘)
      "devDependencies": {
        "typescript": "^5.3.3",
        "eslint": "^8.56.0",
        "@types/node": "^20.11.5"
      },
      // 可选:依赖覆盖(强制所有子包使用指定版本的依赖)
      "pnpm": {
        "overrides": {
          "axios": "^1.6.7" // 比如强制所有子包用 1.6.7 版本的 axios
        }
      }
    }
    
  4. 配置子包 package.jsonworkspace:* 表示引用 workspace 内同版本的子包,pnpm 会自动解析为本地链接,无需发布到 npm 即可使用;

    // packages/ui/package.json
    {
      "name": "@shop/ui", // 包名:推荐用作用域包(@组织名/包名)
      "version": "1.0.0",
      "main": "./src/index.ts", // 入口文件
      "scripts": {
        "build": "tsc" // 假设用 TypeScript 构建
      },
      // ui 包的自有依赖(会装在 ui 目录的 node_modules)
      "dependencies": {
        "vue": "^3.4.15"
      }
    }
    
    // packages/api/package.json
    {
      "name": "@shop/api",
      "version": "1.0.0",
      "main": "./src/index.ts",
      "scripts": {
        "build": "tsc"
      },
      "dependencies": {
        "axios": "^1.6.7"
      }
    }
    
    // packages/web/package.json
    {
      "name": "@shop/web",
      "version": "1.0.0",
      "main": "./src/main.ts",
      "scripts": {
        "dev": "vite", // 假设用 Vite 启动
        "build": "vite build"
      },
      "dependencies": {
        // 依赖 workspace 内的 ui 包(核心:跨包引用)
        "@shop/ui": "workspace:*",
        // 依赖 workspace 内的 api 包
        "@shop/api": "workspace:*",
        // 其他外部依赖
        "vue-router": "^4.2.5"
      },
      "devDependencies": {
        "vite": "^5.0.12",
        "@vitejs/plugin-vue": "^5.0.3"
      }
    }
    
  5. 可选配置:.npmrc (优化体验),在根目录创建 .npmrc,添加以下配置优化 Monorepo 体验:

    # .npmrc
    # 强制所有 workspace 内的依赖使用 workspace 版本(避免意外安装 npm 上的包)
    workspace-concurrency=16
    # 启用严格的 peer 依赖检查
    strict-peer-dependencies=false
    # 自动安装 peer 依赖(适合 UI 库场景)
    auto-install-peers=true
    # 子包的 node_modules 放在根目录的 node_modules/.pnpm 下(节省磁盘)
    shared-workspace-lockfile=true
    # 禁止发布 workspace 内的私有包
    publish-private=false
    

子包代码示例(极简版)

  1. @shop/ui 代码:

    // packages/ui/src/index.ts
    export const Button = (text: string) => {
      return `<button class="shop-btn">${text}</button>`;
    };
    
    export const Card = (content: string) => {
      return `<div class="shop-card">${content}</div>`;
    };
    
  2. @shop/api 代码:

    // packages/api/src/index.ts
    import axios from 'axios';
    
    const api = axios.create({
      baseURL: 'https://api.shop.com',
      timeout: 5000
    });
    
    // 封装接口
    export const getGoodsList = () => api.get('/goods/list');
    export const getUserInfo = () => api.get('/user/info');
    
  3. @shop/web 代码:

    // packages/web/src/main.ts
    // 引用 workspace 内的 ui 包
    import { Button, Card } from '@shop/ui';
    // 引用 workspace 内的 api 包
    import { getGoodsList } from '@shop/api';
    
    // 使用 UI 组件
    console.log(Button('加入购物车'));
    console.log(Card('商品详情'));
    
    // 调用接口
    getGoodsList().then(res => {
      console.log('商品列表:', res.data);
    });
    

常用命令(核心操作)

  1. 安装所有依赖:

    1. 所有命令均在根目录执行;
    2. 效果:
      • 根目录的开发依赖 (如 TS、ESLint) 安装在根 node_modules
      • 子包的自有依赖安装在 node_modules/.pnpm (全局共享)
      • @shop/web 依赖的 @shop/ui@shop/api 会被链接到本地,无需发布;
    # 安装根项目 + 所有子包的依赖(自动解析跨包依赖)
    pnpm install
    
  2. 给指定子包安装依赖:

    # 给 @shop/web 安装外部依赖(如 pinia)
    pnpm add pinia --filter @shop/web
    
    # 给 @shop/ui 安装开发依赖(如 @types/vue)
    pnpm add @types/vue -D --filter @shop/ui
    
    # 给所有子包安装共享依赖(如 lodash)
    pnpm add lodash -r --filter "./packages/*"
    
  3. 执行子包脚本:

    # 启动 web 应用
    pnpm dev:web
    
    # 构建 ui 包
    pnpm build:ui
    
    # 构建所有子包(按依赖顺序执行:先 ui/api,再 web)
    pnpm build:all
    
    # 执行所有子包的 test 脚本
    pnpm -r test
    
  4. 发布子包 (可选)

    # 发布 @shop/ui 包(需先登录 npm)
    pnpm publish --filter @shop/ui
    

常见问题解决

  1. 跨包引用提示 “找不到模块”

    1. 确保子包的 package.jsonmain/module 字段指向正确的入口文件;
    2. 确保子包已执行构建 (如 pnpm build:ui),生成编译后的文件;
    3. 检查 pnpm-workspace.yaml 路径是否正确;
  2. 安装依赖时提示 “循环依赖”

    1. Monorepo 中避免子包之间循环依赖 (如 ui 依赖 api,api 又依赖 ui)
    2. 可将公共逻辑抽离为新的子包 (如 @shop/utils)
  3. 发布子包时提示 “私有包无法发布”

    1. 检查子包的 package.json 是否有 “private”: true,发布前需移除;
    2. 确保根包的 “private”: true 保留 (根包不发布)

常见问题与迁移

  1. 工具兼容性:

    1. 少数旧工具 (如某些 Webpack 插件) 不支持符号链接,需配置 node-linker=hoisted 临时兼容;
    2. VS Code 安装 pnpm 插件优化体验;
  2. npm/yarn 迁移:

    1. 删除 node_modules/package-lock.json/yarn.lock
    2. 运行 pnpm install,自动生成 pnpm-lock.yaml
    3. 脚本命令无缝替换 (npm run → pnpm run)
  3. 幽灵依赖排查:

    1. 运行 pnpm why <pkg> 查看依赖来源;
    2. 未声明依赖直接引用会报错,需显式添加到 package.json

总结与适用场景

  1. 为什么选择 pnpm

    1. 个人开发:节省磁盘、安装快、无依赖陷阱;
    2. 团队 / 企业:Monorepo 友好、依赖一致、CI/CD 高效;
    3. 开源项目:VueViteNuxt 等主流框架默认使用,社区生态成熟;
  2. 一句话结论:pnpm 以内容寻址 + 软硬链接为核心,在速度、空间、严格性上实现最优解,是现代前端工程化的首选包管理器;

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 核心定位与诞生背景
  2. 2. 核心原理:内容可寻址存储 + 软硬链接
  3. 3. 核心优势
  4. 4. 安装与基础使用
  5. 5. pnpm vs npm vs yarn(核心对比)
  6. 6. Monorepo 配置案例
    1. 6.1. 整体目录结构
    2. 6.2. 核心配置步骤
    3. 6.3. 子包代码示例(极简版)
    4. 6.4. 常用命令(核心操作)
    5. 6.5. 常见问题解决
  7. 7. 常见问题与迁移
  8. 8. 总结与适用场景