首先,简单的看一下pnpm是什么,pnpm 在 npm 前加了一个 p,即 performance npm,既然是高性能的 npm 包管理工具,高性能体现在哪里呢,在 npm 3.0 之后,优化了之前的 modules 结构,有依赖的包,不会重复的再挂载到引用的 package 中,但即使是这样,多个项目,每个项目存在自己的 node_modules,而这其中一些相同的包,也会重复的存在于磁盘。pnpm 本着解决这一基本问题来了,用官网的话语来说,就是构造一套可寻址的磁盘存储
这里也不在过多叙述 npm 及 yarn 的包差异,找到一篇讲的不错的文章,未知大佬的差异对比,非原创
在看了几篇关于 pnpm 的文章,大部分都在说 pnpm 是基于硬链的,但也不尽然,下面这张官网的图,诠释了这一点,对于寻址的 package,是硬链,但是对于依赖的包,会创建软链
那么依赖到底被存放在哪里了,既然 package 会被放到一个可寻址并且顶层磁盘存储,那么就在磁盘顶层,在~目录下,我们会看到有两个 pnpm 的文件夹,分别是.pnpm-state 和.pnpm-store,前者是存放 lastUpdateCheck 的 JSON 文件,后者是存储所有的 packages
为什么要使用硬链呢,在其他文档中看到硬链式不要占用额外存储空间,这一点,我认为是错误的,当我 ln 创建了一个硬链接,发现 size 是一样的
敲两行 shell,来看一下硬链与软链的区别
touch tt.txt
ln tt.txt hard.txt
ln -s tt.txt soft.txt
我们改变 tt.txt,查看软链与硬链,会发现软链和硬链内容都变了,但是我们在查看文件的详情,会发现,软链的文件信息并没有更新记录,硬链与源文件同步
占用内存:硬链与源文件一致
删掉源文件,软链因为地址索引找不到而打不开,硬链依旧在
system: 硬链的存储数据和 inode 结构一致,可以达到快速检索,与正常文件一样需要占据存储空间;软链的 inode 结构一致,但是存储数据不一致,不能快速检索,但是内部存放符号链接地址 — (inode 我们可以使用 ls -l 查看 )
功能 | pnpm | Yarn | npm |
---|---|---|---|
工作空间支持(monorepo) | ✔️ | ✔️ | ✔️ |
隔离的 node_modules |
✔️ - 默认 | ✔️ | ❌ |
提升的 node_modules |
✔️ | ✔️ | ✔️ - 默认 |
Plug’n’Play | ✔️ | ✔️ - 默认 | ❌ |
零安装 | ❌ | ✔️ | ❌ |
修补依赖项 | ❌ | ✔️ | ❌ |
管理 Node.js 版本 | ✔️ | ❌ | ❌ |
有锁文件 | ✔️ - pnpm-lock.yaml |
✔️ - yarn.lock |
✔️ - package-lock.json |
支持覆盖 | ✔️ | ✔️ - 通过 resolutions | ✔️ |
内容可寻址存储 | ✔️ | ❌ | ❌ |
动态包执行 | ✔️ - 通过 pnpm dlx |
✔️ - 通过 yarn dlx |
✔️ - 通过 npx |
管理更为严格,只能使用 package.json 中的 packages
模块依赖会通过生成软链,减少内存占用
安装 package 时,不会重复安装,会检索已经存在的,安装没有的文件
package 全局寻址,打包速度快
Lerna 也是一个 npm 包管理工具,跟严格的针对 github 注册表提交,当然,也可以通过配置忽略,但是不建议这么做
与 Pnpm 一样,可以减少开发和构建环境中大量重复包的时间和空间需求,Lerna 话不多。。。。
初始化项目,duanxl/libs,默认pnpm配置
pnpm init -y
pnpm-workspace.yaml
packages:
# all packages in subdirs of packages/ and components/
- 'packages/**'
# exclude packages that are inside test directories
- '!**/tests/**'
// .npmrc
registry=http://127.0.0.1:4873/
// 开启链接namespace link 配置
link-workspace-packages = true
// 生成公共的lockfile
shared-workspace-lockfile = false
初始化lerna,lerna使用pnpm,通过设置independent独立包管理,并开启命名空间配置,为了配合pnpm
// lerna.json
{
"npmClient": "pnpm",
"packages": ["packages/*"],
"version": "independent",
"command": {
"publish": {
"conventionalCommits": true,
"message": "[skip ci] chore: release"
}
},
"useWorkspaces": true,
"ignoreChanges": ["**/node_modules/**", "**/__snapshots__/**"]
}
在package.json中,指定workspaces,指向我们的packages,作为我们的工作目录,指定publish仓库地址
"workspaces": [
"packages/*"
],
"publishConfig": {
"registry": "http://duanxl.com:4873/"
},
lerna create core 通过lerna创建package
package添加scripts,使用microbundle开箱即用编译Ts,也会自动生成.d.ts
{
"name": "@duanxl/core",
"version": "1.0.2",
"description": "我是core,核心业务库 🍺",
"author": "duanxinlei",
"homepage": "",
"license": "ISC",
"main": "src/index.ts",
"directories": {
"lib": "src",
"test": "test"
},
"files": [
"lib"
],
"publishConfig": {
"registry": "http://duanxl.com:4873/"
},
"scripts": {
"dev": "microbundle"
}
}
接下来,我们再create一个package《header》,让header依赖core
// duanxl-libs/packages/header/src/index.ts
import data from '@duanxl/core'
console.log('Header use------:',data)
在header中引入core ==> import data from ‘@duanxl/core’,但是,我们发现包引用找不到;怎么办,除了发包core,也要考虑开发环境,这时候,我们就可以用到lerna add,将package core 引用到package header中,原理的话,就是一个软链接
lerna add @duanxl/core packages/header
接下来,会发现ESM生效了
但是,我们又发现一个新的问题,如果我们私仓存在了@duanxl/core的话,引用会优先找私仓
为了解决上面的问题,我们需要删除掉header中的依赖,就会优先找本地了,这也属于lerna的弊端吧,毕竟不会轻易的publish,当然,如果依赖package变更,我们可以再次lerna add,这样的话,因为软链的原因,会找本地
再考虑一个问题,如果我们一个package要区分环境,而且要publish给其他团队用,其他团队dev、prod都可能使用,这个时候,我们就要package的别名
为了解决以上问题,我们需要用到npm 别名,例如 pnpm add prod@npm:@duanxl/core -filter @duanxl/header –registry http://duanxl.com:4873
我们通过别名,就可以解决多环境多package的问题,而不是搞多套代码(被依赖包),然后再看package header中的package.json
"dependencies": {
"@duanxl/core": "^1.0.2",
"prod": "workspace:npm:@duanxl/core@^1.0.2"
}