《前端包管理工具全解:附带pnpm实现原理》

55 阅读25分钟

一、概述

1、背景

CommonJs的出现,使node环境下的JS代码可以使用模块更加细粒度的划分。一个类、一个函数、一个配置等等都可以作为一个模块,这种细粒度的划分是开发大型应用的基石。

为了解决在开发过程中遇到的常见问题,比如加密,模拟数据等等,一时间,在前端的社区涌现了大量的第三方库。这些库使用commonJS标准书写而成,非常容易使用。 然而,在下载使用这些第三方库的时候,遇到难以处理的问题:

2、面临问题

2.1、下载过程繁琐

一般都有以下过程

  • 进入官网或github主页
  • 找到并下载相应的版本
  • 拷贝到工程的目录中
  • 如果遇到有同名的库,需要修改名称

2.2、库依赖其他库处理繁琐

如果当前库依赖其他库,就需要先下载其他库,在下载目标库,并且要先把其他库放到指定的目录汇中才能使用目标库。如果依赖的库太多,那么这个处理的过程是相当麻烦的

2.3、开发环境与生产环境的库怎么区分

比如常见的开发环境的库eslint。和生存环境的库react这些不同环境的库怎么区分呢?

2.4、库更新与版本管理

在项目中更新库的版本也是极其麻烦的

以上问题,就是包管理工具需要解决的问题。大名顶顶的npm即将登场

二、包管理器

包管理器很多,本篇文章主要详解npm,因为前端几乎所有的包管理器都是基于npm的。它也被称为其他包管理的基石。

1、npm简介

npm 全称node package manager ,即node包管理器,它运行在node环境中,让开发者可以使用简单的方式完成包的查找、安装、更新、卸载、上传等操作

注意 npm运行node环境下的,而不是浏览器环境。根本原因是因为浏览器环境无法提供下载、删除、读取本地文件的功能。而nnode属于服务器环境,没有浏览器的种种限制。

npm的出现弥补了node没有包管理的缺陷。于是很快node在就安装中内置了npm,开发装装了node就等于自动装了npm。

2、npm组成

2.1、registry:入口(npm官方服务器)

  • 可以想象为一个庞大的数据库
  • 第三方库的开发者将自己的库按照npm的规范,打包上传到数据库中
  • 使用者通过统一的地址下载第三方包

2.2、官网 www.npmjs.com/

  • 查询包
  • 注册、登陆、管理个人信息

2.3、CLI:command-line interface命令接口

  • 安装npm之后使用CLI来使用npm各种功能

npmnode相辅相成,node的出现让npm火了,npm的火爆带动了大量的第三方库的发展,很多优秀的第三方库打包上传到了npm上,这些第三方库又为node带来了大量的用户

三、npm安装包

由于npm的官方服务器在国外,因此在安装包的时会出现网络不通安装包失败的问题。要解决此问题,国内可以通过设置镜像源的。通过以下指令设置

    npm config set registry https://registry.npm.taobao.org

1、安装模式

npm安装包分为两种

  • 本地安装
  • 全局安装

1.1、本地安装

使用命令 npm install 包名npm i 包名 即可完成本地安装

本地安装的包出现在当前目录下的node_modules目录中

随着开发的进展,node_modules目录会变得异常庞大,目录下的内容不适合直接传输到生产环境,因此通常使用.gitignore文件忽略该目录内容

本地安装适用于绝大部分的包,它会在当前目录及其子目录中发挥作用,通常在项目的根目录中会使用本地安装

在安装一个包的时候,npm会自动管理依赖,它会下载该包的依赖包到node_modules目录中

如果本地安装的包带有CLI,npm会将它的CLI脚本文件放在node_modules/bin 目录下,用户可使用 npx 命令名 即可直接调用。

1.2、全局安装

全局安装的包放置在一个特殊的全局目录,该目录可以通过命令 npm config get prefix查看

全局安装指令npm install -g 包名

重要全局安装的包并非所有工程可以用,它仅提供全局的CLI工具

大部分情况下都不要全局安装除非:

  • 包的版本非常稳定,很少有大的更新
  • 提供的CLI工具在各个工程中的使用频率非常高如pnpm
  • CLI工具仅为开发环境提供支持,而非部署环境
  • 全局安装的CLI不用再使用npx调用了,可以直接调用

四、包配置

理解包的配置,先看以下几个问题

  • 拷贝工程(一般会排出node_modules目录文件)后如何还原依赖?
  • 如何区分开发依赖和生产依赖?
  • 如果自身的项目也是一个包,如何描述包的信息 基于以上问题,给包增加一个配置文件可以完美解决问题

1、配置文件

npm将每个使用npm的工程本身看作是一个包,包的信息需要通过一个名称固定的文件来描述 该文件称为配置文件且固定名称为 package.json

可以手动创建该文件,更多的时候是通过指令 npm init创建的,配置文件中包含大量的信息(非常重要)

  • name:包名称,该名称必须为英文字母,支持连接符。npm install 安装包的名称就是该字段

  • version:版本

    版本规范:主版本号.次版本号.补丁版本号

    主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量API、技术架构发生了重大变化

    次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的API

    布丁版本号:仅当解决了一些bug或进行了一些局部优化时更新

  • description:包描述

  • homepage:官网地址

  • author:包作者,必须是有效的npm账户名,书写规范是 account <mail>,例如zhangsan <zhangsan@gmail.com>不正确的书写可能会导致发包失败

  • repository:包的仓储地址,通常指git或svn地址,它是一个对象 {type:仓储类型,url:地址}

  • main:包的入口文件,使用包的人默认从该入口文件导入包内容

  • keywords:搜索关键字,发布包之后,可以通过该数组中的关键字搜索到包

使用npm init --yes或者npm init -y可以在生成配置文件时自动填充默认配置

2、通过配置文件描述依赖关系

大部分时候,仅仅开发项目,并不会把它打包发布出去,尽管如此,仍然需要package.json文件

2.1、package.json作用

配置文件最重要的作用就是记录当前工程的依赖信息 两个字段

  • dependencies 生产环境的依赖包
  • devDependencies 仅开发环境的依赖包

配置好依赖后,使用下面命令即可安装依赖

    ## 本地安装所有依赖 dependencies + devDependencies
    npm install
    ## 仅安装生产环境的依赖
    npm install --production

这样一来项目代码移植就不是问题了,只需要移植代码文件和配置文件就可以恢复依赖安装了

为了更加方便的添加依赖,npm支持在使用install命令时,加入一些额外的参数,用于将安装的依赖包保存到package.json文件中

    ## 本地包到生产环境
    npm i 包名
    npm i --save 包名
    npm i  -S 包名
    
    ## 本地包到开发环境
    npm i --save-dev 包名
    npm i -D 包名

五、包的使用

nodejs对npm支持非常良好

当使用nodejs导入模块时,如果路径不是以./ 或者../开头,则node会默认导入的模块来自node_modules目录 例如

    var _=require("lodash")

lodash寻找过程

  • 从当前目录以下寻找文件
    node_modules/loadsh.js
    node_modules/loadsh/入口文件
  • 当前目录没有找到则会回溯到上一级目录按照同样的方式寻找
  • 如果到顶级目录都没有找到,则抛出错误

上面的入口文件按照以下规则确定

  • 查看导入的package.json文件,读取main字段作为入口文件
  • 若不包含main字段,则使用inde.js作为入口文件

六、包的语意版本

说到包的语版本,不妨带入一个场景

如果小明编写了一个包A,依赖另一个包B,小明在编写代码时,包B的版本是2.4.1,小明希望使用自己包的人一定要安装包B,并且是2.4.1版本,还是希望别人安装更高的版本呢?如果更高版本,高到什么程度?

回顾之前包的版本号规则,在实际开发过程中,会思考三种情况

  • 安装依赖包的时候,次版本号和补丁版本号是可以提升的,但是主版本号不能变(因为主版本号更新极有可能是颠覆性更新)
  • 安装依赖包的时候,补丁版本号是可以提升的
  • 包版本不变(几乎遇不到)

以上情况就需要包的语义版本来解决了,语义版本的规则特别多,这里举一些常见例子

符号描述示例示例描述
大于某个版本>1.2.1大于1.2.1版本
>=大于等于某个版本>=1.2.1大于等于1.2.1版本
<小于某个版本<1.2.1小于1.2.1版本
<=小于等于某个版本<=1.2.1小于等于1.2.1版本
-介于某两个版本之间1.2.1-1.4.2介于1.2.1和1.4.2版本之间
x不固定的版本号1.3.x只要保证主版本号为1,次版本号为3即可
补丁版本号可增~1.2.3主版本号1、次版本号2补丁版本号大于等于3
次版本和补丁版本都可以增^1.2.3保证主版本号是1、次版本号大于等于2,补丁版本号大于等于3
*最新版本*始终安装最新版本

1、避免还原差异

版本依赖控制始终是一个复杂问题

如果允许版本增加,可以让依赖包修复bug,但同样会带来不确定的风险,比如新的bug,如果不允许版本增加,可以获得更好的稳定性,但失去了依赖包自由优化的能力。

有的时候情况更加复杂,如果依赖包升级后,依赖发生改变,会有更多的不确定问题出现

基于此,npm在安装包的时候,会自动生成一个package-lock.json文件,该文件记录了安装包时的确切依赖关系

当工程移植时,如果移植了package-lock.json文件,恢复安装时,会按照package-lock.json文件中的确切依赖进行安装,最大限度避免了差异

2、npm的差异版本处理

如果两个包依赖同一个包的不同版本比如

graph TD
A --> C[C 1.5.0]
B-->D[C 1.2.3]

npm会处理这种情况,具体处理如下

  • 首先A和B都安装到node_modules目录下(平面依赖)
node_modules
    A
    B
  • 上面情况发现a和b都依赖不同的c,因此npm会做如下处理
node_modules
    A
        node_modules
            C1.5.0
    B
        node_modules
            C1.2.3

npm会在A和B的目下再新建一个node_modules目录,在此目录下安装对应的c包版本,所以在A中用c就出现了线在A当前目录下的node_modules中是否有c,没有再找上级

七、npm脚本

在开发的过程中,我们可能会反复使用很多的CLI命令,例如

  • 启动工程命令
  • 部署命令
  • 测试命令

这些命令纷繁复杂,根据第三方包不同命令也会一样,非常难以记忆

于是npm非常贴心的支持了脚本,只需要在package.json中配置script字段,即可配置各种脚本名称

运行方式npm run 脚本名称

不仅如此,npm还对某些常用的脚本名称进行了简化,下面脚本名称不需要使用run的:

  • start
  • stop
  • test

一些细节

  • 脚本中可以省略npx
  • start脚本有默认值:node server.js

八、运行环境配置

在开发过程中,书写的代码一般会运行在三个环境中

  • 开发环境
  • 测试环境
  • 生产环境

有些时候可能需要在node代码中根据不同的环境作出不同的处理,如何优雅的让node知道处于什么环境,是极其重要的,通常处理如下

node中有一个全局变量global(可以类比浏览器的window),该变量是一个对象,对象中的所有属性都可以使用

global有一个属性是process,该属性是一个对象,包含了当前运行node程序的计算机的很多信息,其中一个信息是env,是一个对象,包含了计算机中所有的系统变量

通常,我们通过系统变量 NODE_ENV的值,来判断node程序处于何种环境,有两种方式设置 NODE_ENV的值

  • 永久设置
  • 临时设置

一般选择临时设置

在scripts脚本中设置好了 NODE_ENV后启动程序

为了避免不同系统的设置方式的差异,可以使用第三方库cross-env对环境变量进行设置

永久修改需要修改计算机的环境变量(因此node的process存放的就是计算机启动node的相关信息),不推荐

1、node读取package.json

有的时候,可能需要在package.json中配置一些自定义的字段,这些字段需要在node中读取,在node中可以直接导入(require)一个json格式文件,它会自动将其转换为js对象

九、其他npm命令

1、安装

  • 精确安装最新版本
npm install --save-exact 包名
npm install -E 包名
  • 安装指定版本
    npm install 包名@版本

2、查询

  • 查询包安装路径
npm root [-g]
  • 查看包信息
npm view/v/info/show(都可以) 包名[子信息]
  • 查询安装包
npm list [-g] [--depth=依赖深度]

3、更新

  • 检查有哪些包需要更新
npm outdated
  • 更新包
npm update/up/upgrade [-g] [包名]

4、卸载包

npm uninstall/remove/rm/r/un/unlink [-g] 包名

5、npm配置

npm的配置会对其他命令产生或多或少的影响

安装好npm之后,最终会产生两个配置文件,一个是用户配置,一个是系统配置。当两个文件配置项有冲突的时候,用户配置会覆盖系统配置,通常,我们不关心具体的配置文件,而只关心最终生效的配置,通过下面的命令可以查询目前生效的各种配置

npm config ls [-l] [--json]
  • 获取某个配置项
npm config get 配置项
  • 设置某个配置项
npm config set 配置项=值
  • 移除某个配置项
npm config delete 配置项

十、npm发布包

1、准备工作

  • 如果有设置淘宝镜像源,移除淘宝镜像源
npm config delete registry
  • npm官网注册账号,并且完成邮箱认证

  • 本地使用npm cli进行登陆

//登陆命令
npm login
//推出登陆
npm logout
//查看当前登陆的账号
npm whoami
  • 创建一个包工程,使用npm init初始化

-开发确定版本,完成后使用npm publish发布

十一、yarn

1、简介

yarn 是由Facebook、Google、Exponent和Tilde联合推出了一个新的JS包管理工具,它仍然使用npm和registry,不过提供了全新CLI来对包进行管理

过去,yarn的出现极大的抢夺了npm的市场,甚至有人戏称,npm只剩下一个registry了。之所以会出现这种情况,是因为在过去,npm存在下面的问题

  • 依赖目录嵌套深,过去npm的依赖是嵌套的,这在windows系统上是一个极大的问题,由于众所周知的原因,windows系统无法支持太深的目录(因为windows的路径最多只能支持256个字符)
  • 下载速度慢,由于嵌套层次的问题,所以npm对包的下载只能是串行的,即前一个包下载完成后才会下载下一个包,导致带宽资源没有完全利用,多版本相同包会被重复下载
  • 控制台输出繁杂,在npm安装包的时候,每安装一个依赖,就会输出依赖的详细信息,导致一次安装有大量信息输出到控制台。遇到错误极难排查
  • 工程移植问题,由于npm的版本依赖可以是模糊的,可能会导致工程移植后,依赖的确切版本不一致(以前npm没有package-lock.json文件)

针对上述问题,yarn从诞生就解决了(过去npm有这些问题,但是现在也解决了),它用到了以下手段

  • 使用扁平化的目录结构
  • 并行下载
  • 使用本地缓存(本地会有一个全局的缓存文件)
  • 控制台仅输出关键信息
  • 使用 yarn-lock文件记录确切依赖

不仅如此,yarn还优化了以下内容

  • 增强了某些功能强大的命令
  • 让既有的命令更加的语音化
  • 本地安装的CLI工具可以使yarn直接启动
  • 将全局安装的目录在本地当作一个普通的工程,生成package.json文件,便于全局安装移植

yarn的出现给npm带来的巨大的压力,很快npm也学习了yarn先进的理念,不断的对自身进行优化,到了npm6版本几乎完全解决了上面的问题

  • 目录扁平化
  • 并行下载
  • 本地缓存
  • 使用pack-lock记录依赖
  • 增加大量的命名别名
  • 内置了npx,可以启动本地CLI工具
  • 极大简化了控制台的输出

2、总结

npm6之后,yarn和npm非常接近了,甚至没有差别了,很多用户又回到了npm

十二、yarn的核心命令

1、初始化

yarn init [--yes/-y] [--exact/-E]

2、安装

添加指定包

yarn [global] add 包名称 [--dev/-D]

安装package.json中的所有依赖

yarn install [--production/--prod]

3、脚本和本地CLI

运行脚本

yarn run

start、stop、test可以省略run

4、查询

  • 比如查看bin目录:
yarn [global] bin
  • 查询包信息:
yarn info 包名 [子字段]
  • 列举已安装的依赖
yarn [global] list [--depth=依赖深度]

5、更新

  • 列举需要更新的包
yarn outdated
  • 更新包
yarn [global] upgrade [包名]

6、卸载

yarn remove 包名

十三、yarn一些实用命令

在终端命令上,yarn不仅仅是对一些npm的命令做了改名,还增加了一些原本没有的命令,这些命令在某些时候使用起来非常方便

  • yarn check

使用yarn check 命令,可以验证package.json文件的依赖记录和lock文件是否一致

这对于防止篡改非常有用

  • yarn audit

使用yarn audit可以检查本地安装的包有哪些已知的漏洞,并且以表格的方式输出,漏洞级别分为几种

漏洞名称级别
INFO信息级别
LOW低级别
MODERATE中级别
HIGH高级别
CRITICAL关键级别
  • yarn why

使用yarn why命令可以在控制台打印出来为什么安装了这个包,哪些包会用它

十四、pnpm

pnpm 是一种新起的包管理工具,目前比较流行的包管理工具,它主要有以下优势

  • 安装效率高于npm和yarn
  • 极其简洁的nonde_modules目录
  • 避免开发时使用间接依赖的问题
  • 极大降低磁盘空间的占用

1、安装与使用

全局安装pnpm

npm install -g pnpm

使用命令就是把平时使用的npm指令换成pnpm,如果需要执行安装在本地的CLI,可以使用pnpmx

npm与pnpm安装的依赖也不同

npm会把所有的依赖包括间接依赖都放到node_modules中,而pnpm不会把间接依赖直接放到node_modules中

2、pnpm原理

  • 同yarn和npm一样,pnpm仍然使用缓存来保存已经安装的包,以及使用pnpm-lock.yaml来记录详细的依赖版本

  • 不同于yarn和npm,pnpm使用符号链接和硬链接的做法来放置依赖,从而规避了从缓存中拷贝文件的时间,使得安装和卸载的速度更快,注意pnpm的缓存文件夹在工程目录的根路径,比如windows电脑,a工程在D盘,pnpm的缓存文件就在D盘跟路径中

yarn和npm都没有解决一个问题,就是多文件拷贝问题

比如

a工程 安装 lodash

a工程第一次安装之后会生产缓存npm、yarn 在node安装的目录 ,pnpm在工程目录根盘

npm和yarn 处理方式
b工程 安装 lodash 
如果b工程用到的lodash与工程a一样、则使用缓存,不用再去网络上下载了,直接拷贝过来
c工程 同理
d工程 同理
...
如上就会出现一个典型的问题,随着工程越来越多,相同的文件也会越来越多,这是不合理的,同时拷贝文件也需要消耗时间

pnpm使用了符号和硬链接解决这个问题
符号和硬链接简单理解就是使用了快捷方式

pnpm不再直接拷贝文件,而是直接使用符号和硬链接去处理
b工程 安装 lodash 只需要建立链接
c工程 安装 lodash 只需要建立链接
...
速度和安装性都快了不少
由于都是建立链接,不需要操作文件,同时建立链接的方式还能规避windows路径过长的问题,因此pnpm又把依赖变成树形结构了,由于变成了属性结构又规避了间接依赖饮用问题
  • 由于使用了符号链接和硬链接,pnpm可以规避windows操作系统路径过长的问题,因此它选择使用树形依赖管理,也因为如此项目中只能使用直接依赖,而不能使用间接依赖。

3、注意事项

由于pnpm会改动node_modules目录结构,使得每个包只能使用直接依赖,而不能使用间接依赖,因此如果使用pnpm安装的包中包含间接依赖就会出问题

十五、pnpm原理

1、概念

要彻底理解pnpm是怎么做的,需要一些操作系统知识

2、文件本质

在操作系统中,文件实际上是一个指针,不过它指向的不是内存地址,而是外部存储地址(比如硬盘、u盘、甚至是网络...)

graph TD
文件test.txt --> B("磁盘|具体数据")

当删除文件时,删除的实际上是指针,因此即便是删除大文件也是非常快的,因此生活中如果文件删除时间短,利用一些软件可以恢复。

3、文件复制

如果复制一个文件

  • 首先该复制文件的内容区
  • 产生一个新的指针指向复制的内容去
  • 完成复制
graph TD
文件test.txt --> B("磁盘|具体数据")
B-->|复制|C("具体数据")
新的test.txt-->|新的指针|C

4、硬链接 hard link

硬链接的概念来自于Unix操作系统,它是指将一个文件A指针复制到另一个文件B指针中,文件B就是文件A的硬链接

graph TD
A("文件 A") --> C("磁盘|具体数据")
B("新的文件B")-->C
A-->|产生硬链接|B

通过硬链接,不会产生额外的磁盘占用,并且两个文件都能找到相同的磁盘,同时两个文件指针互不影响,删除文件A指针并不会影响文件B指针

硬链接的数量没有限制,可以为同一个文件产生多个硬链接

windows Vista操作系统开始,支持了创建硬链接的操作,在cmd中使用以下命令可以

mklink /h 链接名称 目标文件

由于文件夹(目录)不存在文件内容,所以目录不能创建硬链接,通常windows操作系统也不能跨盘符操作硬链接

5、符号链接(软链接)

符号链接又称为软链接,如果为某个文件或者文件夹A创建符号B,则B指向A。

graph TD
A("文件 A") --> C("磁盘|具体数据")
A-->|产生软链接|B
B-->|指向A|A

由上图就可知道,如果删除了文件地址A,则文件B就失效了。

在windows系统中创建软链接的方式

mklink /d 链接名称 目标文件

6、软链接与硬链接的区别

  • 硬链接仅能链接文件,而符号链接可以链接目录
  • 硬链接在链路完成后仅和文件内容关联和之前的链接文件(文件地址)没有任何关系。而符号链接始终和之前链接的文件(文件地址)关联,和文件内容不直接关联

7、node环境对硬链接和符号链接的处理

  • 硬链接:硬链接是一个实实在在的文件,node不对其做任何特殊处理
  • 符号链接:由于符号链接指向的是另一个文件或者目录,当node执行符号链接下的js文件时,会使用原始路径

8、pnpm原理

有了以上知识,pnpm原理就好理解了

npm使用符号链接和硬链接来构建node_modules目录 下面用一个例子来说明它的构建流程

package a
    index.js ---> 文件内容
                   require("b")
                   
package b
    index.js ---> 文件内容
    

假设工程proj,直接依赖a,则安装时,pnpm会做下面的处理

  • 查询依赖关系,得到最终要安装的包:a和b
  • 查看a和b是否已经有缓存,如果没有下载到缓存中,如果有进入下一步
  • 创建node_modules目录,并对目录进行初始化
proj工程
    node_modules          //proj工程的npm包安装目录
        .pnpm             //pnpm的包管理目录,该目录不会被node读取到
            node_modules  //非工程直接依赖包保存在这里,比如b
            registry.npm.taobao.org        //所有包具体版本和代码分支文件
                aa的目录
                    1.0.0版本
                        node_modules       //包a的所有依赖以及自身
                            a              //包a的代码目录
                bb的目录
                    1.0.0版本
                        node_modules       //包b的所有依赖以及自身
                            b              //包b的代码目录
     index.js -->文件内容
                 require("a")
  • 从缓存的对应包中使用硬链接放置文件到相应的包代码目录中
proj工程
    node_modules          //proj工程的npm包安装目录
        .pnpm             //pnpm的包管理目录,该目录不会被node读取到
            node_modules  //非工程直接依赖包保存在这里,比如b
            registry.npm.taobao.org        //所有包具体版本和代码分支文件
                aa的目录
                    1.0.0版本
                        node_modules       //包a的所有依赖以及自身
                            a              //包a的代码目录
                                index.js   //来自缓存的硬链接
                bb的目录
                    1.0.0版本
                        node_modules       //包b的所有依赖以及自身
                            b              //包b的代码目录
                                index.js   //来自缓存的硬链接
     index.js -->文件内容
                 require("a")
  • 使用符号链接,将每个包的直接依赖放置到自己目录中
proj工程
    node_modules          //proj工程的npm包安装目录
        .pnpm             //pnpm的包管理目录,该目录不会被node读取到
            node_modules  //非工程直接依赖包保存在这里,比如b
            registry.npm.taobao.org        //所有包具体版本和代码分支文件
                aa的目录
                    1.0.0版本
                        node_modules       //包a的所有依赖以及自身
                            a              //包a的代码目录
                                index.js   //来自缓存的硬链接
                            b              //符号链接指向下面的b
                bb的目录
                    1.0.0版本
                        node_modules       //包b的所有依赖以及自身
                            b              //包b的代码目录
                                index.js   //来自缓存的硬链接
     index.js -->文件内容
                 require("a")

这样做的目的,是为了保证a的代码在执行过程中可以读取到它的直接依赖

  • 新版本的pnpm为了解决一些书写不规范的包(读取间接依赖的问题),又将所有的工程非直接依赖使用符号链接加入到了.pnpm/node_modules

  • 在工程的node_modules目录中使用符号链接,放置直接依赖

proj工程
    node_modules          //proj工程的npm包安装目录
        .pnpm             //pnpm的包管理目录,该目录不会被node读取到
            node_modules  //非工程直接依赖包保存在这里,比如b
            registry.npm.taobao.org        //所有包具体版本和代码分支文件
                aa的目录
                    1.0.0版本
                        node_modules       //包a的所有依赖以及自身
                            a              //包a的代码目录
                                index.js   //来自缓存的硬链接
                            b              //符号链接指向下面的b
                bb的目录
                    1.0.0版本
                        node_modules       //包b的所有依赖以及自身
                            b              //包b的代码目录
                                index.js   //来自缓存的硬链接
     a                                     //符号链接指向1.0.0版本的a
     index.js -->文件内容
                 require("a")

以上就是有关npm包管理相关的所有知识点了