说实话,不得不用 pnpm 的原因,还得是电脑内存不够用

4,180 阅读5分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

本文介绍的是 pnpm 管理 Monorepo 项目实践。

什么是 Monorepo

Monorepo 项目简称多包项目,一个包含多个子项目的仓库。

那为什么要放多个项目在一个仓库下呢?

是因为这些项目互相引用,相互依赖,放在一个仓库下方便管理及依赖。

所以管理一个多包项目的关键,需要实现以下 2 点:

  • 能够很方便的管理包与包之间的依赖关系
  • 能够在发布其中一个包时,自动更新依赖了该包的其他包并发布

什么是 lerna

使用最广泛成熟的 Monorepe 项目管理方案就是 lerna + yarn。

lerna 是一个优化使用 git 和 npm 管理多包存储库工作流的工具。

它具有以下功能:

  • 自动解决 packages 之间的依赖关系;
  • 通过 git 检测文件改动,自动发布;
  • 根据 git 提交记录,自动生成 CHANGELOG。

更多详细的 lerna 介绍可以见我的另外一篇博客:最详细的 lerna 中文手册

既然 lerna 这么好用也这么熟悉了,那为什么还要切换到 pnpm 呢?

有以下几个原因:

  • pnpm 内置了管理 monorepe 功能,使用起来比 lerna 简单
  • pnpm 安装比 yarn 高效,也节省电脑内存

什么是 pnpm

pnpm 介绍可以查看 pnpm 官网

pnpm 是新一代的包管理工具,相较于 npm 和 yarn,有以下 2 个优点:

节约磁盘空间并提升安装速度

节约磁盘空间:

  • pnpm 安装依赖时,依赖会被存储在硬盘中,不同项目的同一依赖都会硬链接到硬盘位置,不会额外占用磁盘空间。

  • 同一依赖包的不同版本,也只会将不同版本中有差异的文件添加到仓库中,不会下载整个包占用磁盘空间。

提升安装速度:

  • 安装依赖时,会先去硬盘位置寻找包,如果能找到,则建立硬链接,比起重新下载包或者从缓存中拷贝移动包,速度快了很多

创建非扁平化的 node_modules 文件夹

npm、yarn 为了解决同一依赖被安装多次的问题,将所有包都被提升到模块目录的根目录。

但是当依赖包有多个版本的时候,只会提升一个,其余版本的包依然会被安装多次。

另外扁平化 node_modules 时,项目可以访问到未被添加进当前项目的依赖,这样是有隐患的,因为没有显式依赖,万一有一天别的包不依赖这个包了,代码就不能跑了,因为你依赖这个包,但是现在不会被安装了。

image.png

pnpm 采用磁盘硬链接连接依赖,已经解决了依赖会被安装多次的问题。

为了避免幽灵依赖,pnpm 选择创建非扁平化的 node_modules,项目无法访问到未被添加进当前项目的依赖。

image.png

快速开始

上面已经了解到为什么选择 pnpm 了,那么下面就一起用 pnpm 来管理 Monorepo 吧。

全局安装 pnpm

nvm use 16
npm i pnpm -g

创建 Monorepo 项目

创建目录结构:

mkdir my-project
cd ./my-project
npm init -y
mkdir packages
cd ./packages
mkdir my-project-a
cd ./my-project-a
npm init -y
mkdir my-project-b
cd ./my-project-b
npm init -y

项目结构如下:

image.png

启动 pnpm 的 workspace 功能,根目录新增 pnpm-workspace.yaml,指定工作空间的目录:

packages:
  - "packages/**"

当我们配置了指定工作空间的目录后,packages 里的包互相引用时,会自动依赖本地编译的路径,方便实时调试

至此我们就解决了 Monorepre 项目的管理包与包之间的依赖关系的问题。

安装项目内依赖

限制仅允许 pnpm 安装依赖,更新 package.json:

{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}

安装 eslint 等全局依赖:

pnpm i eslint -w -D

安装子项目内独立的依赖:

cd ./packages/my-project-a
pnpm i rollup -D

发布流程

pnpm 没有提供内置的发布流程解决方案,官方推荐了两个开源的版本控制工具:

changesets 的入手学习成本更低,于是乎选择了 changesets 来管理发布流程。

安装 changesets

pnpm add -Dw @changesets/cli

初始化 changesets

pnpm changeset init

初始化后生成的 .changeset/config.json:

{
  "$schema": "https://unpkg.com/@changesets/config@2.1.1/schema.json",
  "changelog": "@changesets/cli/changelog", // changelog 生成方式
  "commit": false, // 不要让 changeset 在 publish 的时候帮我们做 git add
  "fixed": [],
  "linked": [], // 配置哪些包要共享版本
  "access": "restricted", // 公私有安全设定,内网建议 restricted ,开源使用 public
  "baseBranch": "master", // 项目主分支
  "updateInternalDependencies": "patch", // 确保某包依赖的包发生 upgrade,该包也要发生 version upgrade 的衡量单位(量级)
  "ignore": [] // 不需要变动 version 的包
}

管理 changelog

如果是开源库可以安装 @changesets/changelog-github 来管理 changelog。

安装:

pnpm add -Dw @changesets/changelog-github

更新 .changeset/config.json:

{
  "changelog": [
    "@changesets/changelog-github",
    {
      "repo": "worktile/slate-angular" // 改为你的 github 仓储
    }
  ]
}

如果不是开源库,则保持 "changelog": "@changesets/cli/changelog"

生成 changesets

npx changeset

选择要发布的包:

image.png

选择发布的类型:

image.png

填写发布备注:

image.png

确认发布:

image.png

生成临时文件:

image.png

更新版本

更新版本前可以先把开发区的改动提交上去。

git add .
git commit -m 'feat: msg'
git push

更新版本:

npx changeset version

image.png

自动生成 CHANGELOG.md 并更新 package.json 中的版本,同时如果子项目间有相互依赖,也会更新依赖版本

image.png

image.png

发布版本

发布至 npm:

npx changeset publish

至此我们就解决了 Monorepre 项目的在发布其中一个包时,自动更新依赖了该包的其他包并发布的问题。

小结

pnpm 是不是比 yarn + lerna 更香?节省磁盘空间,安装依赖更快,内置 Monorepo 功能。

说实话,不得不用 pnpm 的原因,还得是电脑内存不够用,用 npm、yarn安装依赖时,20个项目,40G node_modules 的内存就没了。

所以,赶紧转 pnpm 吧。

项目地址:github.com/jiaozitang/…

希望能对你有所帮助,感谢阅读~

别忘了点个赞鼓励一下我哦,笔芯 ❤️

参考资料