vite源码阅读:(二)Pnpm 与 Monorepo

210 阅读4分钟

vite 就是使用的 pmnp 方式 monorepo 仓库,因此要看懂源码,先学学 pnpm 与 monorepo

一、为什么使用 pnpm

  1. npmyarn 的问题
  • npm@3 以来,node_modules 目录扁平化,将依赖的依赖也安装在与其同级的 node_modules 中,这会导致
    • 幽灵依赖:项目中可访问 package.json 中不存在的依赖包,当依赖包的依赖更新或删除该依赖包,将导致项目报错
    • 依赖扁平化算法复杂
    • 相同的依赖包,会被复制到不同的项目中,占用磁盘空间
  1. npm 为什么采用扁平化目录结构?
  • npm@3 之前,采用树形结构,过深的依赖树会造成 Windows 的长目录路径问题
  • 当项目中不同的依赖包中有相同的依赖项,会被复制多次
  1. pnpm 又是如何解决这些问题的呢?
  • 幽灵依赖:node_modules 中只存在 package.json 的依赖
  • 安装速度快:缓存中有的依赖直接硬链接到 node_modules 中,减少大量 copy IO 操作
  • 提高磁盘利用率:通过软硬链接方式,使同版本依赖公用磁盘空间。不同版本只存储额外 diff 文件
  1. pnpm 原理
    1. store 集中管理依赖:不同项目相同版本依赖使用硬链接。不同版本依赖只增加 diff 文件
    2. 项目 node_modules 中只包含 package.json 中依赖 + .pnpm/,依赖只是软链
    3. 真实位置在虚拟存储 .pnpm/<name>@<version>/node_modules/<name> 中,与 store 中是硬链。由此可见 .pnpm 中平铺存储着所有包,解决了长路径问题,并实现了包隔离
    4. 依赖的依赖与依赖真实位置同级,并且软链至 ./pnpm 下对应位置
  2. 常用命令
    • 初始化项目:在 <workspace>/subdir 执行 pnpm init
    • 安装依赖:pnpm addpnpm i
    • 执行 script:pnpm run
      • pnpm startpnpm run start 简写
    • 执行 node_modulse/.bin 命令:类似与 npx pnpm exec
    • pnpm create
  3. 过滤:将命令限制于特定子级 --filter 或 -F
    • 语法pnpm --filter <package_selector> <command>
    • --filter <package_name>... 软件包本身及其所有依赖
    • --filter <package_name>^... 不包含本身,只是其所有依赖
    • --filter ...<package_name> 本身及依赖于它的包
    • --filter ...^<package_name> 依赖于它的包
    • --filter "./packages/**" --filter {packages/\*\*} 相对于当前工作目录匹配项目的全局模式。可以与 ... ^ 结合使用
    • pnpm --filter "{packages/**}[origin/master]" <cmd> 结合[since]在某个目录选择所有已改项目
    • --filter ...[<since>] 指定 commitbranch 以来有更改的包
    • --filter=!foo <cmd> 除 foo 包外所有
    • --filter xx --filter xx 多个 filter 取并集
    • --filter-prod <filtering_pattern> 忽略 devDependencies 中依赖
    • pnpm --filter="...[origin/master]" --changed-files-ignore-pattern="**/README.md" run build 忽略某个文件造成的变更

二、为什么使用 Monorepo 代码管理方式

  1. Monorepo 演进
  • 刀耕火种:是单仓库巨石应用
  • 光荣进化:多仓库多模块
  • 现在:单仓库多模块
  1. 有什么优缺点呢
  • 代码权限:都在一个仓库没有更细粒度划分,可以看清项目全貌,但增加了修改非 owner 代码风险
  • 工程配置:配置容易统一,代码风格相同。依赖只在顶层安装一次,节省磁盘空间
  • 开发迭代:方便调试、借助工具自动 link。仓库体积大,clone 时间长
  • 构建部署:使用一套 CI CD 流程,借助工具可以配置依赖构建优先级
  1. 社区有哪些方案
  • Monorepo 构建方案:NX、Turborepo、Rush

    • NX:

      • 缓存:本地缓存 与 远程缓存。(提供公共 API,可以本地化远程缓存)
      • 增量构建
      • 并行构建
      • 分布式构建:多台机器同时构建
    • Turborepo

      • 缓存:本地与远程。但是没有 API
      • 并行构建:开发者控制构建顺序
    • Rush

      • 解决幽灵依赖
      • 并行构建
      • 插件机制:可扩展性强
      • 项目发布:changelog 友好
  • 版本 & 依赖管理方案

    • Lerna
      • 作用
        • 方便调试、自动 link
        • 依赖提升、减少冗余
        • 检测 git 代码,自动发版、升级版本号
        • 根据 commit 信息,生成更细日志
      • 工作模式
        • 固定模式:统一升级,版本号一致
        • 独立模式:只升级有变动包
      • 常用命令
        • 初始化:lerna init
        • 创建包:lerna create
        • 添加依赖,自动 link:lerna add
        • 安装依赖:lerna bootstrap
        • 清除所有 packages 下依赖:lerna clean
        • 发包、并生成 tag:lerna publish
        • 执行 scripts 中命令:lerna run
        • 列出所有包:lerna list
        • 导入已有包:lerna import
    • pnpm + Changeset
      • 通过 pnpm-workspace.yaml 文件定义工作空间
      • workspace:协议,安装依赖时智能从 workspace 安装。不存在则报错
packages:  
# all packages in direct subdirs of packages/  
- 'packages/*'  
# all packages in subdirs of components/  
- 'components/**'  
# exclude packages that are inside test directories  
- '!**/test/**'

Pnpm + NX 最佳实践