前端常见包管理工具:yarn、npm、pnpm,本文主要理解三种包管理工具的优劣与pnpm的原理与使用。
官网:
npm中文官网:http://npm.p2hp.com/
yarn官网:https://classic.yarnpkg.com/en/docs/install#mac-stable
pnpm官网:https://pnpm.io/zh/
虽然嵌套结构层次清晰,但会导致下载重复依赖包、文件引入路径长和未知bug
node_modules
└─ a
| ├─ index.js
| ├─ package.json
| └─ node_modules
| └─ b
| ├─ index.js
| └─ package.json
└─ c
├─ index.js
├─ package.json
└─ node_modules
└─ b
├─ index.js
└─ package.json
node_modules 结构是可预测和干净的,因为 node_modules 中的每个依赖项都有自己的 node_modules 以及所包含依赖关系的 package.json 文件。
依赖无法被共用:比如 React 有一些内部变量,在两个不同包引入的 React 不是同一个模块实例,因此无法共享内部变量,导致一些不可预知的 bug。
依赖层级太深,导致文件路径过长。
下载大量重复依赖:如上述node_modules目录,b包会在a包、c包两者的node_modules中安装,即重复安装。
扁平化结构解决了依赖重复下载、层级太深的问题,但会导致某项package.json没有注册的包,开发者也能引入
yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的JS包管理工具;
yarn 是为了弥补早期npm的一些缺陷而出现的;
早期的npm存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列的问题;
虽然从npm5版本开始,进行了很多的升级和改进,但是依然很多人喜欢使用yarn;
yarn生成的依赖关系文件为yarn.lock而npm为package-lock.json,内部格式也不相同
node_modules
├─ a
| ├─ index.js
| └─ package.json
└─ b
| ├─ index.js
| └─ package.json
└─ c
├─ index.js
└─ package.json
依赖目录平铺,解决层级太深问题
减少重复依赖下载,相同依赖只提升某一版本(仍有大量重复依赖的下载)
Phatom dependencies(幽灵依赖):使用 npm 或 Yarn Classic 安装依赖项时,所有包都被提升到node_modules的根目录。 因此,项目可以访问到未被添加进当前项目的依赖。
扁平化算法本身的复杂性很高,耗时较长。
依赖结构的不确定:如上述a、c依赖的b包版本不同,那么b包哪个版本会被提升呢?
答案是:都有可能。取决于 a 和 c 在 package.json中的位置,如果 c声明在前面,那么就是前面的结构([email protected]被提升),否则是后面的结构([email protected]被提升)。这就是为什么会产生依赖结构的不确定问题,也是 lock 文件诞生的原因,无论是package-lock.json(npm 5.x才出现)还是yarn.lock,都是为了保证 install 之后都产生确定的node_modules结构。尽管如此,npm/yarn 本身还是存在扁平化算法复杂和package 非法访问的问题,影响性能和安全。
pnpm:我们可以理解成是performant npm缩写;
快速:pnpm 比其他包管理器快2倍
高效:node_modules中的文件链接自特定的内容寻址存储库
支持monorepos:pnpm内置支持单仓多包
严格:pnpm默认创建了一个非平铺的node_modules,因此代码无法访问任意包
使用 npm 时,依赖每次被不同的项目使用,都会重复安装一次。 而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以:
如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有100个文件,而它的新版本只改变了其中1个文件。那么 pnpm update 时只会向存储中心额外添加1个新文件,而不会因为仅仅一个文件的改变复制整新版本包的内容。
所有文件都会存储在硬盘上的某一位置。 当软件包被被安装时,包里的文件会硬链接到这一位置,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。
因此,您在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多!
使用 npm 安装某个项目时这个项目可能会依赖其他项目
使用 npm 或 Yarn Classic 安装依赖项时,所有包都被提升到node_modules的根目录。 因此,项目可以访问到未被添加进当前项目的依赖。
默认情况下,pnpm 使用软链的方式将项目的直接依赖添加进模块文件夹的根目录。
pnpm使用符号链接和硬链接来构建node_modules目录,在这之前先让我们了解一下操作系统的相关知识,及文件系统、硬链接、符号(软)链接。
硬盘的最小存储单位叫「扇区」(sector,每个扇区存储512bit)
操作系统一次读一个扇区效率太低,所以会一次读多个扇区
8个扇区组成一个块,最常见的大小为4kb
块是文件存取的最小单元,文件数据都存储在块中
存储文件元信息,比如文件的创建者、文件的创建日期、文件大小等
Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。
打开文件时,系统首先找到文件的inode号码,然后通过inode号码获取inode信息。然后根据inode中的文件数据所在block读取数据。
在Linux系统中,内核为每一个新创建的文件分配一个inode,每个文件都有一个惟一的inode号,我们可以将inode简单理解成一个指针,它永远指向本文件的具体存储位置。文件属性保存在inode里,在访问文件时,inode被复制到内存,从而实现文件的快速访问,系统是通过inode来定位每一个文件。
在操作系统中,文件实际上是一个指针,只不过它指向的不是内存地址,而是一个外部存储地址(这里的外部存储可以是硬盘、U盘等)
新建helloWord.js文件并添加数据:实际上我们只是将该文件的指针指向某片扇区,我们对文件操作时也只是修改该扇区中的数据。
当我们删除helloWord.js时,实际上我们只是将指针删除了,因此我们发现文件无论多大我们都可以快速删除,而清空废纸篓时就很慢。其实我们的U盘、硬盘里的文件虽然说看起来已经删除了,但是其实数据恢复公司是可以恢复的,因为数据还是存在的,只要删除文件后再没有存储其它文件就可以恢复,所以真正删除一个文件就是疯狂存疯狂删。
我们通常的 复制粘贴 是将该文件指针指向的内容进行复制,然后开辟一片新的扇区进行储存,而不是指向同一片扇区,使用我们修改当前文件不会影响其他文件,我们也可以通过硬链接来实现这一操作,详细看下文。
硬链接实际上是一个指针,指向源文件的inode,系统并不为它重新分配inode。
硬链接不会建产新的inode,硬链接不管有多少个,都指向的是同一个inode节点,只是新建一个hard link会把结点链接数增加,只要结点的链接数不是0,文件就一直存在,不管你删除的是源文件还是链接的文件。只要有一个存在,文件就存在(其实就是引用计数的概念)。
当你修改源文件的时候,其他的文件都会做同步的修改。
硬链接的概念来自于 Unix 操作系统,它是指将一个文件A指针复制到另一个文件B指针中,文件B就是文件A的硬链接。
通过硬链接,不会产生额外的磁盘占用,并且两个文件都能找到相同的磁盘内容。硬链接的数量没有限制,可以为同一个文件产生多个硬链接。
硬链接文件有两个限制
不允许给目录(文件夹)创建硬链接
只允许在同一文件系统中的文件之间才能创建链接
符号链接最直观的解释:相当于Windows系统的快捷方式,是一个独立文件(拥有独立的inode,与源文件inode无关),实际上是特殊文件的一种, 该文件的内容是源文件的路径指针,另一个文件的位置信息,通过该链接可以访问到源文件。
所以删除软链接文件对源文件无影响,但是删除源文件,软链接文件就会找不到要指向的文件(可以类比Windows上快捷方式,你点击快捷方式可以访问某个文件,但是删除快捷方式,对源文件无任何影响)。
硬链接仅能链接文件,而符号链接可以链接目录;
具有相同inode节点号的多个文件互为硬链接文件;
硬链接不占用磁盘空间;
删除硬链接文件或者删除源文件任意之一,文件实体并未被删除;
只有删除了源文件和所有对应的硬链接文件,文件实体才会被删除;
硬链接文件是文件的另一个入口;
可以通过给文件设置硬链接文件来防止重要文件被误删;
创建硬链接命令 ln 源文件名 生成硬链接文件名(大家可以通过该命令自己测试软硬链接);
硬链接文件是普通文件,可以用rm删除;
对于静态文件(没有进程正在调用),当硬链接数为0时文件就被删除。注意:如果有进程正在调用,则无法删除或者即使文件名被删除但空间不会释放。
软链接类似windows系统的快捷方式;
软链接里面存放的是源文件的路径,指向源文件;
软链接占用的空间只是存储路径所占用的极小空间;
删除源文件,软链接依然存在,但无法访问源文件内容;
软链接失效时一般是白字红底闪烁;
创建软链接命令 ln -s 源文件名 生成软链接文件名;
软链接和源文件是不同的文件,文件类型也不同,inode号也不同;
软链接的文件类型是“l”,可以用rm删除。
由于符号链接指向的是另一个文件或目录,当node执行符号链接下的JS文件时,会使用原始路径。比方说:我在E盘装了CFHD,在桌面创建了CFHD快捷方式,相当于是符号链接,双击快捷方式运行游戏,在运行游戏的时候是按照CFHD原始路径(E盘路径)运行的。
快捷方式类似于符号链接,是windows系统早期就支持的链接方式。它不仅仅是一个指向其他文件或目录的指针,其中还包含了各种信息:如权限、兼容性启动方式等其他各种属性,由于快捷方式是windows系统独有的,在跨平台的应用中一般不会使用。
硬链接是一个实实在在的文件,node不对其做任何特殊处理,也无法区别对待,实际上,node根本无从知晓该文件是不是一个硬链接。
由于符号链接指向的是另一个文件或目录,当node执行符号链接下的JS文件时,会使用原始路径。比方说:我在E盘装了CFHD,在桌面创建了CFHD快捷方式,相当于是符号链接,双击快捷方式运行游戏,在运行游戏的时候是按照CFHD原始路径(E盘路径)运行的。
我们分别执行 npm i axios 与 pnpm add axios 对比
可以看出axios与axios习惯依赖全部都是放在同一级的,使用就会出现上文中描述的幽灵依赖。
可以看到node_modules变得清晰了很多,只有我们想要安装的目标依赖vue和.pnpm文件夹,这就很好的杜绝了上文中描述的幽灵依赖,我们还可以看到。
axios相关的依赖和依赖的依赖都被放在了.pnpm 中
我们打开pnpm的node_modules所在目录可以看见左下角有个小箭头,包括上张图片axios文件最后也有小箭头,
我们打开axios的详情可以看到他只是 .pnpm/axios的替身而已,及也就是axios的软链接
我们点开pnpm/node_modules/.pnpm/axios可以看见axios相关的依赖,巧妙的是相关依赖也都是软链接,链接到.pnpm下对应的文件
而 .pnpm 下一级的文件都是没有小箭头的,.pnpm是虚拟存储目录,这一级的文件是从pnpm全局的store硬链接生成的文件,在mac中该文件夹通常在User/username/Library/pnpm/store或User/username/.pnpm-store中
通过上面的图文描述我们再来看pnpm的 官网首页 => 创建非扁平化的 node_modules 文件夹 章节下的这个图片我相信大家都可以看懂
以下为常用命令更多请看官网 CLl命令 章节
// 用于安装项目所有packagepnpm install // 默认会从 npm registry 安装最新的 package-name。pnpm add package-name // 删除某个package 别名: rm, uninstall, un 添加 -g 从全局删除一个packagepnpm remove package-name // 移除不需要的packages,及没有项目引用的(不建议经常使用,因为依赖越多按照速度可能越快)pnpm prune
请看该章节:删除所有node_modules和npm、yarn的package-lock配置文件
主要解决多个项目可能按照重复package,导致占有磁盘空间
解决项目可以访问没有安装的依赖,及安装某些package时该package依赖的package我们没有主动按照也可以使用
创建非扁平化的node_modules
通过软硬链接结合实现更快的安装速度