包管理工具
常见错误:
1、pnpm 的lock 文件是 pnpm-lock.yml, ❌ 不是pnpm.yml
参考文章:JavaScript package managers compared: npm, Yarn, or pnpm?
看到另一篇主要讲 pnpm 实现的博客,也可以了解下 2022年了,你还没用pnpm吗?
背景:在工作遇到
- (node14)npm install + npm run build 会打包静态资源失败
- yarn + yarn build 成功
:::tip 之后尝试过切换到 node 16 ,npm i + npm run build 可以成功 :::
在我看来,yarn build
和 npm run build
都是执行 package.json 里面的 build 指令串,那么差异就落在了 install 依赖的时候,yarn 和 npm 是有差异的。
- 我看到工程里面,存在 yarn.lock 文件,而不存在 package-lock.json 文件,认为 yarn 安装的时候,lock 文件限制了包版本不是 package 中的最高版本,所以不会报错;
- 但是我同事认为,是 yarn 和 npm 安装依赖的方式不同 -> 也就是 引入文件存储的路径不同,导致打包的时候报一些引入文件的错误(I need evidence)
我在网上查 yarn 和 npm 有什么区别,说的最多的是 speed,yarn 比 npm4.0 快很多,但是现在 npm 的版本已经升很高了,一些语义化指令也都支持了,速度也会快一些(还有些疑问:具体快多少?在 yarn 出现后 npm 是不是也做了优化?)
同时我也很想知道两种打包方式,引入包的路径究竟是不是有差异的,我遇到的这个问题,究竟是不是包路径的问题?
贴一下我用到的 node 版本 + 对应的 npm 版本:
在此基础上,我开始啃国外的文章(国内真的好千篇一律啊啊啊啊啊),参考文章写于 2022.02,还算比较新的。
以下为关键信息笔记
当今包管理器领域存在三个主要参与者:npm、Yarn(v1、v2)、pnpm(高性能 npm)
共同点
功能对等;npm 和 Yarn 都将依赖安装在一个平面的 node_modules 中(pnpm不同,引入了一些新概念)
差异点
主要是安装速度、存储消耗、和项目工作流结匹配程度
简要了解每个工具出现的原因
-
npm
解决的问题是,当时项目依赖是手动下载管理的(手动管理年代)
-
Yarn v1(又名 Yarn Classic)
是基于 npm 出现的,并行化操作 加快了安装过程,针对的是 npm 早期版本的主要痛点
- pnpm 是 npm 的替代品,解决 npm 和 Yarn 在跨项目使用的 依赖项冗余存储
- (npm 和 yarn v1使用的是相同的依赖解析方法,都是 使用提升来扁平化 node_modules),pnpm 引入「内容可寻址存储」,生成一个嵌套 node_modules,保存在全局存储 ~/.pnpm-store/ 中,实现了依赖的每一个版本仅在该文件中进行一次物理存储 -> 构成单一来源,节省磁盘空间
-
Yarn v2(又名 Yarn Berry)主要创新是 即插即用(PnP方法,一个解决 node_modules 的策略)
缺点:需要更新,不兼容 v1(添加一个配置开启 node_modules plugin 才可以使用传统的 node_modules 方法)
-
它 不创建 node_modules,而是生成一个带有依赖查找表的文件 .pnp.cjs(执行更快,因为单一文件快于嵌套的文件夹结构)
-
此外在 .yarn/cache/ 文件夹中,每个包都是 zip 文件,占用磁盘空间较少
-
每种工具的安装
-
npm
和 Node.js 捆绑,没有额外操作步骤
-
Yarn Classic 和 Yarn Berry
npm i -g yarn
或者corepack prepare yarn@3.1.1 --activate
Corepack - 在 Node > v16.9.0 后不必单独安装,只需要激活,已经预安装垫片;旧版本
npm install -g corepack
-
pnpm
npm i -g pnpm
或者corepack prepare pnpm@6.24.2 --activate
(和 Yarn v1/v2 一样,在高版本 Node 中激活即可)
项目结构
所有工具都存储了重要的元信息在 package.json,而且根目录下的配置文件可以设置依赖解析的方法
-
npm:执行
npm i
,生成 package-lock.json + node_modules;其中 .npmrc 可以放在根目录作为配置文件. ├── node_modules/ ├── .npmrc ├── package-lock.json └── package.json
-
Yarn Classic:执行
yarn
,创建 yarn.lock + node_modules;其中 .yarnrc 可以配置(也支持 .npmrc);.yarn 文件可选,用于缓存(cache/) + 存储当前 yarn 版本(releases/)
. ├── .yarn/ │ ├── cache/ │ └── releases/ │ └── yarn-1.22.17.cjs ├── node_modules/ ├── .yarnrc ├── package.json └── yarn.lock
-
Yarn Berry:不支持 .npmrc 和 .yarnrc,需要一个 .yarnrc.yml 配置文件
1、为了实现传统模式,配置一个 nodeLinker 为 node-modules
# .yarnrc.yml nodeLinker: node-modules # or pnpm
执行
yarn
,生成 node_modules + yarn.lock (新形式的 lock 文件,和 Yarn v1 不兼容). ├── .yarn/ │ ├── cache/ │ └── releases/ │ └── yarn-3.1.1.cjs ├── node_modules/ ├── .yarnrc.yml ├── package.json └── yarn.lock
2、配置为新的模式 - PnP
# .yarnrc.yml nodeLinker: pnp pnpMode: loose # or strict(默认模式为 strict)
执行
yarn
生成 .yarn/cache + .yarn/unplugged + .pnp.cjs + yarn.lock;其中 .yarn/sdk 提供 IDE 支持. ├── .yarn/ │ ├── cache/ │ ├── releases/ │ │ └── yarn-3.1.1.cjs │ ├── sdk/ │ └── unplugged/ ├── .pnp.cjs ├── .pnp.loader.mjs ├── .yarnrc.yml ├── package.json └── yarn.lock
-
pnpm:执行
pnpm i
,生成 pnp-lock.yml + node_modules (内容完全和 npm 不一样,因为 pnpm 用的是内容可寻址存储);支持 .npmrc 的配置文件. ├── node_modules/ │ └── .pnpm/ ├── .npmrc ├── package.json └── pnpm-lock.yml
lockfile 和依赖存储
lockfile 出现在 npm > v5 (package-lock.json);pnpm(pnpm-lock.yaml);Yarn v2(新形式的 yarn.lock);
npm 、 Yarn Classic、pnpm 用的都是将依赖存储于 node_modules,pnpm 用了更高效的办法(内容可寻址存储),Yarn Berry - PnP 没有用 node_modules,而是用 .yarn/cache/ 和
.pnp.cjs 存储了依赖的 zip 文件
CLI 指令
Action | npm | Yarn Classic | Yarn Berry | pnpm |
---|---|---|---|---|
安装全部 package.json |
npm install alias: i , add |
yarn install or yarn |
like Classic | pnpm install alias: i |
更新到最新版本 | npm update react@latest |
yarn upgrade react --latest |
yarn up react |
pnpm up -L react |
新增依赖 | npm i react |
yarn add react |
like Classic | pnpm add react |
卸载依赖,同时从 package.json 移除 |
npm uninstall react alias: remove , rm , r , un , unlink |
yarn remove react |
like Classic | pnpm remove react alias: rm , un , uninstall |
为了安全性考虑,Yarn Berry 和 pnpm 只允许执行在 package.json 或者 /.bin 文件中声明的二进制文件。
配置文件
包括 package.json 和指定的配置文件
-
npm - .npmrc
# .npmrc # 可以配置私有表,重用已经安装过的共享库 @doppelmutzi:registry=https://gitlab.doppelmutzi.com/api/v4/projects/41/packages/npm/ # 指定依赖源 registry=https://registry.npmmirror.com/
-
Yarn Classic - .yarnrc
# .yarnrc # 可以指定特定的 Yarn 版本 yarn-path: .yarn/releases/yarn-3.1.1.cjs
-
Yarn Berry - .yarnrc.yml
# .yarnrc.yml # 注意属性命名变了 yarnPath: .yarn/releases/yarn-3.1.1.cjs # 可以通过插件 yarn-plugin-import 扩展 plugins: - path: .yarn/plugins/@yarnpkg/plugin-semver-up.cjs spec: "https://raw.githubusercontent.com/tophat/yarn-plugin-semver-up/master/bundles/%40yarnpkg/plugin-semver-up.js" # 用于解决 PnP 模式下的不兼容问题 packageExtensions: "styled-components@*": dependencies: react-is: "*"
-
pnpm - .npmrc,和 npm 类似(工作区支持多包,如果需要 monorepo,需要在 pnpm-workspace.yaml 中配置)
# pnpm-workspace.yaml packages: - 'packages/**'
Monorepo 支持情况(配置工作区)
-
npm > v7 支持工作区,大部分指令支持配置是否需要支持多包工作区
⚠️ npm v8 当前并不支持高级过滤,也不支持并行制从多个与工作区相关的命令
# Installing all dependencies for all workspaces $ npm i --workspaces. # 针对一个包执行 $ npm run test --workspace=hooks # 针对多个包执行 $ npm run test --workspace=hooks --workspace=utils # run against all $ npm run test --workspaces # ignore all packages missing test $ npm run test --workspaces --if-present
-
Yarn Classic
在 2017.08 之前,需要用第三方软件(例如 Lerna)支持多包项目,2017.08 之后发布了在工作空间对 monorepo 进行一流的支持。
# Installing all dependencies for all workspaces $ yarn # 显示依赖树 $ yarn workspaces info # 仅对一个包运行 start 命令 $ yarn workspace awesome-package start # 添加 Webpack to package $ yarn workspace awesome-package add -D webpack # 添加 React to all packages $ yarn add react -W
-
Yarn Berry
基于 Yarn v1 ,一开始 Yarn v2 就以工作区为特色。相比于 Yarn v1, Yarn v2 明确定义依赖项(dependencies
or
devDependencies)必须是此 monorepo 中的包之一# 在安装包时复用来自其他工作区的版本 $ yarn add --interactive # 跨所有工作区更新包 $ yarn up # 仅为单个工作区安装依赖项 $ yarn workspaces focus # 在所有工作区上运行命令 $ yarn workspaces foreach
-
pnpm
和 Yarn v2 相似实现了 monorepo,大部分指令接受一些选项
--recursive
(-r
)、--filter
# 修剪所有工作区 $ pnpm -r exec -- rm -rf node_modules && rm pnpm-lock.yaml # 对范围为 @doppelmutzi 的所有工作区运行所有测试 $ pnpm recursive run test --filter @doppelmutzi/
性能和磁盘空间效率
结论: Yarn Berry(PnP模式)安装速度最快
:::tips 我们常说 yarn 比 npm 快,其实我们如果不是用 yarn v2,那么不如使用 npm ,速度要快,当然包大小还是 yarn 有优势 :::
安全功能
-
npm - 太宽松,正在解决安全 gaps,尤其是落后于 Yarn 的安全问题
-
Yarn - v1 和 v2 从一开始就从 yarn.lock 校验包完整性,在安装期间检测到没有在 package.json 中声明的变量,则会中止
Yarn v2 没有传统的 node_modules 方面的安全问题,有更高的指令执行的安全性
-
pnpm - 在执行前也会用 checksum(校验和) 验证完整性
相比于 npm 和 Yarn v1 使用提升 node_modules 带来的安全问题,pnpm 生成的嵌套的 node_modules 消除了非法依赖访问的风险,因为这保证了只能访问 package.json 中声明的依赖项
一些热门项目在用什么包管理器呢?
- npm - Svelte、Preact、Express.js、Meteor、Apollo Server
- Yarn Classic - React、Angular、Ember、Next.js、Gatsby、Nuxt、Create React App、webpack-cli、Emotion
- Yarn Berry - Jest (with node_modules)、Storybook (with node_modules)、Babel (with node_modules)、Redux Toolkit (with node_modules)
- pnpm - Vue 3、Browserlist、Prisma、SvelteKit
总结(关键点):
- npm > v8时,yarn v1 没有 npm 好用
- 配置文件 .npmrc + lockfile:package-lock.json
- yarn v1
- 并行化安装,快于早期的 npm
- 和 npm 使用相同的依赖解析方法 -通过依赖 提升,来扁平化 node_modules
- 配置文件 .yarnrc,也支持 .npmrc + lockfilke:yarn.lock
- yarn v2(PnP模式)
- 不生成 node_modules ,【即插即用】生成 依赖查找表 .pnp.cjs,包以 zip 文件在缓存中
- 配置文件:不支持 .npmrc 和 .yarnrc,而是 .yarnrc.yml + yanr.lock(和 yarn v1 的不兼容)
- pnpm
- 生成嵌套的 node_modules,内容可寻址存储,只需要一次物理存储
- 在 Node v16.9.0 后不必单独安装(pnpm、yarn v1/v2 都只需要激活)
- 配置文件:.npmrc + pnpm-lock.yml
回答博客开始的疑问:npm v8 和 yarn v1 其实在存储依赖上没有大的差异,之所以在 Node14 版本(npm v6)安装失败,应该是 npm v7 时支持了 yarn.lock,使项目可以按照 lockfile 安装指定版本