2022年了,你还没用pnpm吗? |
您所在的位置:网站首页 › store文件夹 › 2022年了,你还没用pnpm吗? |
关于软件包的管理工具,大家比较熟知的是 npm 和 Yarn,今天给大家介绍一个新的包管理工具pnpm。 通过此文,您将学习到pnpm的以下知识: 基础使用、常用命令等 依赖包是如何管理和存储的 workspace协议,支持monorepo 对比npm、Yarn,有什么优势 如何将npm、Yarn转为pnpm阅读此文之前,需要了解的知识: 内容可寻址存储(CAS) 软链接(符号链接) 硬链接 简介节约磁盘空间并提升安装速度 pnpm代表performant npm(高性能的npm),同npm和Yarn,都属于Javascript包管理安装工具,它较npm和Yarn在性能上得到很大提升,被称为快速的,节省磁盘空间的包管理工具。 当使用 npm 或 Yarn 时,如果你有 100 个项目使用了某个依赖(dependency),就会有 100 份该依赖的副本保存在硬盘上,而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以: 如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有100个文件,而它的新版本只改变了其中1个文件。那么 pnpm update 时只会向存储中心额外添加1个新文件,而不会因为仅仅一个文件的改变复制整新版本包的内容。 所有文件都会存储在硬盘上的某一位置。 当软件包被安装时,包里的文件会硬链接到这一位置上对应的文件,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。因此,您在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多! 摘自官网:pnpm.io/zh/motivati… 基本使用 安装与使用安装 通过npm安装,也可以在官网查看其他安装方式 npm install -g pnpm 复制代码通过下述命令查看已安装的pnpm的版本 pnpm -v 复制代码使用 初始化,生成package.json文件 pnpm init 复制代码 安装依赖 pnpm install xxx 复制代码 运行package.json中定义的scripts脚本,启动服务即可 pnpm run xxx 复制代码示例:创建一个vue3项目 通过pnpm create使用vite套件新建一个vue3的项目,直达vue官方链接 # 使用pnpm create 启动套件(vite,只有存在的套件才可以)创建模板项目 pnpm create vite -- --template vue cd pnpm install pnpm dev 复制代码通过上述操作,我们学到了pnpm项目的初始化、安装依赖、启动服务等,可以运行起来,感受一下它和npm运行速度的差异。 常用命令官网查看更多命令 设置源 查看源:pnpm config get registry 切换源:pnpm config set registry 初始化 初始化package.json:pnpm init注意:pnpm init只能一键快速生成package.json文件,如果要一步一步填写每个属性的值生成package.json文件,则需要通过npm init生成,如果要一键快速生成,需要增加-y参数npm init -y来生成。 管理依赖 安装依赖包到 dependencies :pnpm add 安装依赖包到devDependencies:pnpm add -D 安装依赖包到optionalDependencies:pnpm add -O 全局安装依赖包:pnpm add -g xxx 安装项目全部依赖:pnpm install,别名pnpm i 更新依赖包:pnpm update,别名pnpm up 删除依赖包:pnpm remove,别名pnpm rm/uninstall/un 查看依赖 查看本地安装的依赖:pnpm list,别名pnpm ls 查看全局安装的依赖:pnpm list --global,别名pnpm ls --g 检查过期的依赖:pnpm outdated 运行脚本 运行自定义脚本:pnpm run xxx,别名pnpm xxx 运行test测试脚本:pnpm test 启动套件创建项目: pnpm create 运行start启动命令:pnpm start 发布依赖包 发布依赖包:pnpm publish管理node环境 可实现nvm、n等node版本管理工具,安装并切换node.js版本的功能。 本地安装并使用:pnpm env use 全局安装并使用:pnpm env use --global pnpm基于符号链接来创建非扁平化的node_modules 对比npm和pnpm安装的node_modules: npmpnpm那pnpm怎么管理这些依赖包的呢?带着这一问题,我们继续探究。 详细看一下pnpm生成的node_modules目录如下: ▾ node_modules ▸ .bin ▸ .pnpm ▸ @vitejs ->符号链接 ▸ vite ->符号链接 ▸ vue ->符号链接 .modules.yaml 复制代码node_modules 中只有一个 .pnpm 的文件夹以及三个符号链接@vitejs/plugin-vue、 vite 和 vue。 这是因为我们的项目只安装了@vitejs/plugin-vue、 vite 和 vue三个依赖,pnpm使用符号链接的方式将项目的直接依赖添加到node_modules的根目录下,也就是说node_modules目录下只有我们项目中依赖的dependencies、devDependencies和一个.pnpm目录。 以vite依赖包举例,看一下vite依赖包和.pnpm目录里都有些什么: 展开vite依赖包,我们会有两个疑问: vite是一个符号链接,那它的实际位置在哪里? 依赖的其他次级依赖在哪里?vite的实际位置 .pnpm称为虚拟存储目录,以平铺的形式储存着所有的项目依赖包,每个依赖包都可以通过.pnpm/@/node_modules/路径找到实际位置。 即直接依赖的vite包 符号链接到路径:.pnpm/[email protected]/node_modules/vite,vite包中的每个文件都是来自内容可寻址存储的硬链接。 .pnpm/[email protected]/node_modules/vite ▾ node_modules ▾ .pnpm ... ▾ [email protected] ▾ node_modules ▾ vite -> 符号链接到这个位置 ... ▾ src client package.json -> 包中的每个文件都硬链接到pnpm store中的对应文件,即/vite ▸ vite -> 符号链接到.pnpm/[email protected]/node_modules/vite .modules.yaml 复制代码vite的次级依赖 观察上面的目录结构,发现/node_modules/.pnpm/[email protected]/node_modules/vite目录下还是没有次级依赖的node_modules。 pnpm 的 node_modules设计 ,包的依赖项与依赖包的实际位置位于同一目录级别。 所以 vite 的次级依赖包不在 .pnpm/[email protected]/node_modules/vite/node_modules/, 而是在 .pnpm/[email protected]/node_modules/,与vite实际位置位于同一目录级别。 ▾ node_modules ▾ .pnpm ... ▾ [email protected] -> esbuild依赖包的实际位置 ▾ node_modules ▸ esbuild ▾ [email protected] ▾ node_modules ▸ esbuild -> (依赖包)符号链接到.pnpm/[email protected]/node_modules/esbuild,次级依赖包与依赖包实际位置同级 ▸ postcss ... ▸ reslove ... ▸ rollup ... ▾ vite -> vite依赖包的实际位置 ... ▾ src client package.json # 依赖esbuild、postcss、resolve、rollup ▸ vite -> 符号链接到.pnpm/[email protected]/node_modules/vite .modules.yaml 复制代码这里的esbuild等次级依赖包又是一个符号链接,仍符合刚才的逻辑,实际位置在.pnpm/[email protected]/node_modules/esbuild,包内的每个文件再硬链接到pnpm store中的对应文件。 我们再通过官网提供的依赖图,再辅助理解一下node_modules依赖包之间的关系。 项目依赖了[email protected]版本,bar依赖了[email protected]版本,node_modules下只有直接依赖包[email protected]符号链接和.pnpm目录。 Node.js解析时,[email protected]就会符号链接到实际位置.pnpm/[email protected]/node_modules/bar,包中的文件(并非包文件夹)都硬链接到.pnpm store中的对应文件,[email protected]做为bar的依赖,与bar的实际位置处于同一层级,符号链接指向实际位置.pnpm/[email protected]/node_modules/foo,包中的文件再硬链接至.pnpm store。 关于peerDependencies是怎么处理依赖的,可以看官网这篇文章 总结:pnpm使用符号链接Symbolic link(软链接)来创建依赖项的嵌套结构,将项目的直接依赖符号链接到node_modules的根目录,直接依赖的实际位置在.pnpm/@/node_modules/,依赖包中的每个文件再硬链接(Hard link)到.pnpm store。 包存储storepnpm store:pnpm资源在磁盘上的存储位置 一般store在Mac/Linux系统中,默认会设置到{home dir}>/.pnpm-store/v3;windows下会设置到当前盘的根目录下,比如C(C:\.pnpm-store\v3)、D盘(D:\.pnpm-store\v3)。 可以通过执行pnpm store path命令查看store存储目录的路径 进入store存储路径,查看存储的内容如下: files/xx/xxx以文件夹进行分类,每个文件夹内包含重新编码命名后的文件,依赖包硬链接到此处对应的文件。 在项目中执行pnpm install的时候,依赖包存在于store中,直接创建依赖包硬链接到store中,如果不存在,则从远程下载后存储在store中,并从项目的node_modules依赖包中创建硬链接到store中。 上图中提示包从Content-addressable store硬链接到Virtual store,以及Content-addressable store和Virtual store的作用位置。 内容寻址存储 — Content-addressable store(CAS)它是一种存储信息的方式,根据内容而不是位置进行检索信息的存储方式,被用于高速存储和检索的固定内容,如存储。这里的CAS作用于/Users//.pnpm-store/v3目录。 虚拟存储 — Virtual store指向存储的链接的目录,所有直接和间接依赖项都链接到此目录中,项目当中的.pnpm目录node_modules/.pnpm。 因为这样的处理机制,每次安装依赖的时候,如果是相同的依赖,有好多项目都用到这个依赖,那么这个依赖实际上最优情况(即版本相同)只用安装一次。如果依赖包存在于pnpm store中,则从store目录里面去hard-link,避免了二次安装带来的时间消耗,如果不存在的话,就会去下载并存储在store中。 如果是 npm 或 Yarn,那么这个依赖在多个项目中使用,在每次安装的时候都会被重新下载一次。 对比发现pnpm install安装速度相当之快!必须给个大大的赞! ❓紧接着会有人问,那一直往store里存储依赖包,store会不会越来越大? 官方提供了一个命令:pnpm store prune,从存储中删除未引用的包。 未引用的包是系统上的任何项目中都未使用的包。 在大多数安装操作之后,包有可能会变为未引用状态。 官方举例:在 pnpm install 期间,包 [email protected] 被更新为 [email protected]。 pnpm 将在存储中保留 [email protected] ,因为它不会自动除去包。 如果包 [email protected] 没有被其他任何项目使用,它将变为未引用。 运行 pnpm store prune 将会把 [email protected] 从存储中删除 。 运行 pnpm store prune 是不会影响项目的。 如果以后需要安装已经被删除的包,pnpm 将重新下载他们。建议清理不要太频繁,以防在切换分支等时pnpm需要重新下载所有删除的包,减慢安装过程。 pnpm store的其他命令 pnpm store status:查看store中已修改的包,如果包的内容与拆包时时相同的话,返回退出代码0。 pnpm store add:只把包加入存储中,且没有修改存储外的任何项目或文件 pnpm store prune:删除存储中未被引用的包 pnpm跟npm和Yarn一样,内置了对单一存储库monorepo的支持,只需要在项目根目录下创建 pnpm-workspace.yaml 文件,定义workspace的根目录。 例如: pnpm-workspace.yaml packages: # all packages in subdirs of packages/ and components/ - 'packages/**' - 'components/**' # exclude packages that are inside test directories - '!**/test/**' 复制代码Workspace协议(workspace:) workspace:工作空间 默认情况下,如果可用的 packages 与已声明的可用范围相匹配,pnpm 将从工作空间链接这些 packages。 例如,如果 bar 中有 "foo":"^1.0.0" 的这个依赖项,则 [email protected] 链接到 bar。 但是,如果 bar 的依赖项中有 "foo": "2.0.0",而 [email protected] 在工作空间中并不存在,则将从 npm registry 安装 [email protected] ,这种行为带来了一些不确定性。 pnpm 支持 workspace 协议(写法:workspace: )。 当使用此协议时,pnpm 将拒绝解析除本地 workspace 包含的 package 之外的任何内容。 因此,如果您设置为 "foo": "workspace:2.0.0" 时,安装将会失败,因为 "[email protected]" 不存在于此 workspace 中。 接下来,我们使用vue代码库来理解一下Workspace协议: 代码库地址:github.com/vuejs/core 根目录可以看到有这两个文件pnpm-lock.yaml、pnpm-workspace.yaml, 其中lock文件为pnpm install时生成的lock文件,space文件则为monorepo仓库中必须需要的定义工作空间目录的文件。 点开pnpm-workspace.yaml文件:github.com/vuejs/core/…我们看到文件内容为: packages: - 'packages/*' 复制代码也就表示core/packages/*这个目录下面所有的文件为workspace的内容。 点开package.json文件:github.com/vuejs/core/…我们看到用到本地workspace包的都标注了workspace:*协议,这样依赖的就一直是本地的包,而不是从npm registry安装的包。 我们验证一下到底依赖的是不是本地包clone代码库到本地,pnpm install安装依赖 查看core/node_modules/文件夹,发现package.json文件中依赖的@vue/xxx、vue包都已符号链接的形式存在,如下图: 按workspace:*协议,打开packages/reactivity文件夹,做一个测试,在index.js文件中加入console.log('test'),如下图: 这时候再打开node_modules/@vue/reactivity/index.js文件,可以发现刚才在packages里面改的内容,显示在了node_modules目录下的包里。 打开磁盘上存储(pnpm store path)的依赖包,并没有上面新增的console.log,上面的改动只影响了本地依赖包,而不是远程install下载后存储在磁盘上的包,也就是说符合workspace:协议引入的依赖包就是本地的workspace目录(即core/packages)下的包。 别名引用 假如工作区有一个名为 foo 的包,可以通过这样引用 "foo": "workspace:",如果是其它别名,可以这么引用:"bar": "workspace:foo@*"。 相对引用 假如packages下有同层级的foo、bar,其中bar依赖于foo,则可以写作"bar": "workspace:../foo"。 发布workspace包 当workspace包打包发布时,将会动态替换这些workspace:依赖。 假设我们的 workspace 中有 a、 b、 c、 d 并且它们的版本都是 1.5.0,如下: { "dependencies": { "a": "workspace:*", "b": "workspace:~", "c": "workspace:^", "d": "workspace:^1.5.0" } } 复制代码将会被转化为: { "dependencies": { "a": "1.5.0", "b": "~1.5.0", "c": "^1.5.0", "d": "^1.5.0" } } 复制代码现在很多很受欢迎的开源项目都适用了pnpm的工作空间功能,感兴趣的可以前往官网查看哦! 对比npm、Yarn性能对比 在pnpm官网上,提供了一个benchmarks基准测试图表,它展示了npm、pnpm、Yarn、Yarn pnp在install、update等场景下的耗时: 通过上图,可以看出pnpm的运行速度基本上是npm的两倍,运行速度排名pnpm > Yarn > npm。 功能对比 官网链接 通过上图可以看出pnpm独有的两个功能: 管理Node.js版本(pnpm env use --global xxx) 内容可寻址存储(CAS)竞争 YarnYarn 在 v3.1 添加了 pnpm 链接器。 因此 Yarn 可以创建一个类似于 pnpm 创建的 node_modules 目录结构。 此外,Yarn 团队计划实现内容可寻址存储,以提高磁盘空间效率。 npmnpm 团队决定也采用 pnpm 使用的符号链接的 node_modules 目录结构(相关 RFC)。 npm或Yarn 转 pnpm可参考vue代码库的这一次升级commit log 操作步骤: 全局安装pnpm npm install -g pnpm 复制代码 删除npm或yarn生成的node_modules # 项目目录下运行或手动物理删除 rm -rf node_modules 复制代码 pnpm import从其他软件包管理器的lock 文件生成 pnpm-lock.yaml,再执行pnpm install --frozen-lockfile(相当于npm ci)生成依赖,防止没有lock文件意外升级依赖包,导致项目出错 # 生成`pnpm-lock.yaml` pnpm import # 安装依赖 pnpm install --frozen-lockfile 复制代码 删除npm或yarn生成的lock文件 # 删除package-lock.json rm -rf package-lock.json # 删除yarn.lock rm -rf yarn.lock 复制代码 项目中的npm命令等修改为pnpm,包括README文档、运行命令等可参考代码库,本人亲测,已成功升级 卸载pnpm卸载全局安装的包 通过pnpm ls --g查看全局安装的包,只有通过pnpm install/add xxx --global安装的包才为全局包哦! 移除pnpm cli 通过独立脚本安装的,可以通过rm -rf $PNPM_HOME进行移除(谨慎:删除前确定好删除的内容) 使用npm安装的pnpm,可以通过npm rm -g pnpm进行移除删除全局内容可寻址存储 rm -rf $(pnpm store path) 如果您不在主磁盘中使用 pnpm ,您必须在使用 pnpm 的每一个磁盘中运行上述命令。 因为 pnpm 会为每一个磁盘创建一个专用的存储空间。 一些疑问还有一些问题,需要进一步的验证和考究: webpack打包的时候,pnpm依赖包之间引用是怎么处理的? 手动修改pnpm store中的包的内容,其他引用地方是不是影响了? 删除项目文件夹,pnpm prune的机制是什么,能否正确处理? 结语pnpm是高性能的npm,通过内容可寻址存储(CAS)、符号链接(Symbolic Link)、硬链接(Hard Link)等管理依赖包,达到多项目之间依赖共享,减少安装时间,也非常的好上手,通过npm install -g pnpm安装,pnpm install安装依赖即可。 又get了一个新的知识,有不对的和想要探讨的,欢迎评论留言哦! 参考文档 pnpm.io/zh/motivati… juejin.cn/post/705334… juejin.cn/post/700179… |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |