pnpm 是怎么做到的
这就要涉及文件系统中两个概念:硬链接、软链接;
硬链接
在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index),在 Linux 中,允许多个文件名指向同一索引节点,一般这种连接就是硬链接。
硬链接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬链接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的链接。只删除一个链接并不影响索引节点本身和其它的链接,只有当最后一个链接被删除后,文件的数据块及目录的链接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬链接文件均被删除。
语法:ln filename [linkname ]
- 硬链接的新建是为同一inode号添加文件名 (本质是在目录条目里为inode号增添一个文件名映射,指向同一个inode表数据,因此数据相同)
- 新建硬链接,链接数增加(链接数实质就是 inode号 对应文件名的个数;当 inode 号映射的文件名不存在时,此 inode号就会被系统回收重用)
- 硬链接文件和原文件之间数据共享,但又互相独立;(修改其中任意一个文件的数据,其他的文件数据都会改变,删除硬链接文件则对应的链接数会减少,如果是最后一个链接数则直接删除文件。)
- 不能跨分区和跨设备创建硬链接
- 不能对目录创建硬链接 (目录最多有三个硬链接,目录本身,目录下的 . ,子目录下的 …)
示例: 以下命令建议在 Linux 虚拟机中或 MacOS 中操作:
1. 新建 poetryFile 文件 $ touch poetryFile 2. 给文件输入内容'人生得意须尽欢' $ echo '人生得意须尽欢'>poetryFile $ cat poetryFile // 查看内容 3. 通过ln 在同文件路径下建立硬链接文件 hardPoetryFile $ ln poetryFile hardPoetryFile 4. 修改被硬链接的文件,加上'莫使金樽空对月' $ echo '莫使金樽空对月'>>hardPoetryFile $ cat poetryFile
软链接
有点像是 window 中的快捷方式,它本身也是一个文件,只不过保存的是它指向的文件的全路径,访问时将通过它访问所指向的文件路径以打开指定文件,所以当删除源文件时,打开它将报错指示无相关路径。
语法:ln -s filename [linkname ]
- 软链接实质是新建一个文件快捷方式,存放的数据是原文件的文件名,文件数据大小是原文件名字的字节数;访问时通过文件名指向到原文件数据
- 软链接支持跨分区
- 可以创建目录软链接
- 软链接文件依赖于原始文件 ;删除原始文件,软链接文件会失效示例: 在 poetryFile 的基础上我们新建一个软链接
$ ln -s poetryFile softPoetryFile // 新建软链接文件 $ cat softPoetryFile // 查看文件内容 人生得意须尽欢,莫使金樽空对月 $ echo '天生我材必有用'>>softPoertFile // 修改软链接内容 $ cat softPoertFile // softPoertFile内容变化 人生得意须尽欢,莫使金樽空对月; 天生我材必有用
在文件当前所在目录通过 ls -hl 查看信息,可以发现硬链接文件和源文件各种信息均一致,可以说是一样的文件,而软链接文件则只是存储了指向信息,所以可以看到明显的差别。
total 16 -rw-r--r--@ 2 zhoumingjie staff 66B 2 9 10:24 hardPoetryFile -rw-r--r--@ 2 zhoumingjie staff 66B 2 9 10:24 poetryFile lrwxr-xr-x 1 zhoumingjie staff 10B 2 9 10:21 softPoetryFile -> poetryFile
pnpm对硬链接,软链接的运用
当使用 npm 或 Yarn 时,如果你有100个项目使用了某个依赖(dependency),就会有100份该依赖的副本保存在硬盘上。而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以: 如果你用到了某依赖项的不同版本,那么只会将有差异的文件添加到仓库。 例如,如果某个包有100个文件,而它的新版本只改变了其中1个文件。那么 pnpm update 时只会向存储中心额外添加1个新文件,而不会因为仅仅一个文件的改变复制整新版本包的内容。 所有文件都会存储在硬盘上的某一位置。 当软件包被被安装时,包里的文件会 硬链接 到这一位置,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。因此,您在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多! 摘自: pnpm.io/zh/motivati…官方介绍示意图:
在pnpm 出现之前,npm 和 yarn 为了提升装包效率以及复用率,采用了扁平化对策略,也就是所有的依赖包都装到根目录下,这样会导致node_modules 下的包和 package.json 中定义的存在很大出入,这会引起幽灵依赖( 幽灵依赖” 指的是项目中使用了一些没有被定义在其package.json 文件中的包。)
如图:
虽然我只需要express 这一个包,但是experss 所依赖的包都被平铺到了根目录,这将导致我可以直接使用这些没在 package.json 中定义的包,虽然项目可以运行,但是将是个潜在的隐患。而如果使用 pnpm, 它将使用软链接来解决这个问题:
可以看到,node_modules 下结构和我们期望的几乎一样了,很简洁,之前的那些包被放到了 .pnpm 里,其实这里的 express 就是个软链接,执行 ls -hl
查看其详细信息, 可以看到它指向了 .pnpm 下的express 文件,所以说 pnpm 的软链接就是将 node_modules 里的文件软链接到对应的 .pnpm/[package_name]@version/node_modules/[package_name] 中。
total 0 lrwxr-xr-x 1 zhoumingjie staff 41B 2 9 13:29 express -> .pnpm/express@4.17.2/node_modules/express lrwxr-xr-x 1 zhoumingjie staff 47B 2 9 13:29 mime-types -> .pnpm/mime-types@2.1.34/node_modules/mime-types
而 .pnpm 中的文件则是一个对源文件的硬链接,我来验证下,首先找到 pnpm-test 项目下找到 express 的文件路径,通过终端指令 stat -s index.js
读取其详细信息,发现其 index.js inode节点为 st_ino=5206568 并且有8个相同的硬链接,然后建一个新项目,并安装express,发现 其节点inode 仍然是st_ino=5206568,但是硬链接数量增加到了 9 个,可以看出express 下的index.js 实际上是被复用的。
那么,pnpm 下载的源文件到底在哪里呢,以 macOS 为例,会默认安装到当前用户的根目录下,是一个隐藏文件,你也可以通过修改配置来更换其位置 pnpm config set store-dir /path/to/.pnpm-store
总结
pnpm == p(performant) + npm,代表高性能的 npm,我觉得目前可用性已经很好了,虽然可能迁移中会遇到一些问题比如幽灵依赖,但是还是值得升级的。特别是新项目,无脑用起来吧。