0. 前言
在前端开发团队中,经常存在多个新老项目同时开发的情况,这些项目他们的 node 版本可能不一样,项目中所使用的包管理器也不一样。比如一些老的项目可能使用低版本 node 并使用 npm 安装项目依赖,而一些新的项目则会使用高版本 node 与 pnpm/yarn 来安装项目依赖。
如果 node 版本不统一,带来的问题可能就是整个项目无法运行;而如果包管理器不统一,带来的则可能是 package-lock.json
、yarn.lock
、pnpm-lock.yaml
在每个成员电脑上都不一致的灾难。
之所以是灾难,因为很多项目本地明明运行的好好的,然而在其他人电脑上或者线上却无法正常运行,这都很有可能是因为 lock 文件没有对其导致安装了错误版本的依赖包!
为了解决这个问题,本文将向你展示如何使用 volta 来管理 node 版本,并用 corepack 来管理包管理器,团队只需要准备相同的环境,对当前的项目进行简单改造,就可以实现在每个项目中自动切换 node 版本与包管理器版本!
1. Volta
Volta 官网描述为一款无障碍的 JavaScript 工具管理器,与 nvm、n 类似的,可以用来管理 Node 版本,但同时又有以下优势:
- 可以根据项目
package.json
声明的 volta 配置来根据不同的项目自动切换 node 版本; - volta 也可以直接管理 pnpm 与 yarn(不推荐,建议如果使用高版本的node,使用后面的 corepack 来管理包管理器);
- 当使用 volta 安装的 node 来执行全局安装时,切换 node 版本后,全局安装的包仍然存在,并且其运行时所依赖的 node 版本会被固定为安装该包时使用的 node 版本;
1.1 安装
volta 安装只需要执行一行命令:
curl https://get.volta.sh | bash
之后便可以使用 volta 来安装不通版本的 node:
volta install node@18
若想要切换 node 版本,也只需使用 volta install
指令即可
你可以使用 volta list all
查看当前 volta 所已经安装的内容:
⚡️ User toolchain:
Node runtimes:
v14.21.3 (default)
v16.20.1
v18.16.1
v20.3.1
Package managers:
Packages:
serve@14.2.0 (default)
binary tools: serve
platform:
runtime: node@14.21.3
package manager: npm@built-in
这些内容分为三部分:
- Node runtimes:你已经安装的 node 版本,显示 default 即为你当前全局使用的 node 版本;
- Package managers:你已经安装的包管理器(这一部分的管理与 corepack 冲突,后面再具体讨论);
- Packages:使用
npm install --global
安装的全局包,volta 会在此列出,并固定其运行时版本;
1.2 使用 volta 管理项目依赖的 node
假如你之前使用的是 nvm,如果你想要为不通的项目来规范 node 版本,那么需要在项目目录通过创建 .nvmrc
写入 node 版本,并要求开发者在进入项目时使用 nvm use
来对其当前使用的 node 版本。这个操作如果想要自动化,就需要编写一个自动化脚本来实现(参考)。
但是对于 volta 来说,这个步骤是及其简单并且完全自动化的。当要求全团队使用 volta 后,可以直接在项目目录(package.json 的目录下)使用如下指令,将当前项目所使用的 node 版本固定为 node v18 的 LTS 版本:
volta pin node@18
之后,你就会发现项目的 package.json 中被添加上一段:
"volta": {
"node": "18.16.1"
}
之后当用户进入到当前项目中时,volta 就会检测到 package.json 中声明的 node 版本,并且切换至该 node 版本。
1.3 volta 卸载不使用的 node 和包管理器
当你尝试使用 volta uninstall yarn
或者 volta uninstall node
时,会出现暂无法支持卸载的提示:
error: Uninstalling node is not supported yet.
因此需要到 ~/.volta/tools/image
手动删除已经下载的 npm 或者 yarn 等,参考。
1.4 volta 的原理
volta 的原理并不复杂,本质上就是通过覆写二进制文件的执行指令到 volta 的处理程序中,然后 volta 就可以调用正确的 node 版本,或者调用全局安装的二进制文件并为其指定 node 版本。
我们可以使用 which node
来查看以下 node 的执行位置,会发现其执行位置为用户目录下的 .volta/bin/node
中。
我们在来查看一下全局安装的包,比如我们使用 npm install serve -g
安装的 serve
指令,调用 which serve
可以发现其执行位置同样为 ~/.volta/bin
目录。
我们切换到该目录后,就可以查看到这些可执行文件:
node npm npx serve volta volta-migrate volta-shim
可以发现,我们通过全局安装指令安装的二进制文件会被放到这个位置,node 和 npm 的执行文件也在这个位置。但实际上另有玄机,我们可以使用 ls -l
查看一下文件的详细信息:
total 37008
lrwxr-xr-x 1 username staff 39B Jul 4 14:49 node -> /Users/username/.volta/bin/volta-shim
lrwxr-xr-x 1 username staff 39B Jul 4 14:49 npm -> /Users/username/.volta/bin/volta-shim
lrwxr-xr-x 1 username staff 39B Jul 4 14:49 npx -> /Users/username/.volta/bin/volta-shim
lrwxr-xr-x 1 username staff 39B Jul 5 19:12 serve -> /Users/username/.volta/bin/volta-shim
-rwxr-xr-x 1 username staff 7.3M Jan 25 05:38 volta
-rwxr-xr-x 1 username staff 4.4M Jan 25 05:37 volta-migrate
-rwxr-xr-x 1 username staff 6.4M Jan 25 05:37 volta-shim
这些可执行的二进制文件都被使用连接符软连接到了 volta-shim
这个可执行文件上去。
因此 volta 背后自动切换 node 版本以及管理全局包的魔法实际上就是:当使用 Volta 时,npm、node以及其他全局包的这些二进制文件被重定向到 .volta/bin
目录下的对应可执行文件,而这些可执行文件实际上都是 .volta/bin/volta-shim
所创建的软连接。 volta-shim 本身是一个特殊的脚本,他主要做了如下这些事情:
- 如果是 node、npm、yarn 等指令,监测当前工作控件是否被
volta pin
指定了 node 版本和包管理器的版本,如果是,则将指令定向到目标版本; - 如果是 serve 这种由 npm 安装的可执行文件,则定向到该可执行文件,并且使用安装该可执行文件时的 node 版本 来执行该指令。
2. Corepack
注意:corepack 只适用于 node@16.9.0 以上的版本,如果你的团队 node 版本低于此版本,同时项目又使用了 yarn/pnpm 来进行包管理,那么使用 corepack 可能存在问题,可以考虑使用 volta 的包管理器的管理功能。如果非要使用,可以参考 3.3 节中的方案。
corepack 是 node 官方出的一个管理 node 包管理器的管理器,其已经内置于 node@20 版本中,与 npm 一样作为被默认安装的指令工具,但默认没有被启用。
其诞生背景是因为 npm 本身的不思进取,导致社区出现了 yarn、pnpm 这些更优秀的包管理器工具,然而这些包管理器工具在不同的项目中又有可能使用不同的版本,因此规范项目中使用的包管理器也和规范项目所使用的 node 版本一样重要。
毕竟在团队中,谁都不想 lock 文件在每个人的电脑上都有各自的版本!
2.1 安装
如果你的团队使用的是 node@20,那么 corepack 指令则已经被默认安装。如果你是用的是其他版本的 node(最低 node@16.9.0),则需要手动执行 npm install corepack -g
来安装。
安装后的 corepack 默认是被禁用的,如果你想要启用 corepack 则需要先卸载全局安装的包管理器:
npm uninstall yarn -g
npm uninstall pnpm -g
然后执行:
corepack enable
corepack prepare --all
首先,corepack 会被启用,在你的 /usr/local/bin/
目录下创建 pnpm、pnpx、yarn、yarnpkg 这几个可执行文件,让你可以使用 pnpm 和 yarn 的指令。实际上,这些指令也是一个软连接,会连接到 corepack 的处理程序(其实与 volta 的原理类似),以让 corepack 可以使用正确的包管理器版本;
之后使用 corepack prepare --all
指令会下载最新的 pnpm 和 yarn 的稳定版,此时你就可以正常在全局使用 yarn 和 pnpm 了。
如果你想切换全局安装的 pnpm 和 yarn,可以使用下面的指令:
# 将全局使用 pnpm 版本切换到 pnpm@6.35.1
corepack prepare pnpm@6.35.1 --activate
注意:corepack 指定包管理器版本的时候必须使用 @x.y.x 来明确三位版本号
2.2 使用 corepack 管理项目使用的包管理器
与 volta 类似的,corepack 也是通过识别项目 package.json 中声明的配置来自动切换包管理器的,但需要手动添加:
{
"packageManager": "pnpm@8.6.6"
}
这样,就成功指定了项目使用的 pnpm 版本为 8.6.6
,当我们使用 pnpm
指令的时候,corepack 会自动安装对应版本,并调用改版本。
此外,如果读取到有效的 packageManger 配置,corepack 还会阻止用户使用错误的包管理器来安装,比如如果用户在上面配置的项目中使用 yarn install
,那么就会出现报错:
Usage Error: This project is configured to use pnpm
$ yarn ...
corepack 默认不会托管 npm,也不建议这么做(因为 node 版本与 npm 版本已经强绑定了),因此不会拦截 npm install
的指令
3. Volta 与 Corepack 结合使用
3.1 为什么非要使用 Corepack ?
当你看到这里可能会有疑问,volta 本身是支持同时管理 node 与包管理器,但为什么还要使用 corepack?那么我只能说 volta 本身的包管理是有缺陷的,而 corepack 是未来。
使用 corepack 已经被 node 官方视为一个 规范,已经集成在 Node 的最新发行版本中,目前完整的支持 yarn 和 pnpm 的切换,yarn 和 pnpm 在初始化项目的时候也会将 packageManager
写入到 package.json 中,corepack 必定是一个趋势。所以对于包管理器的管理来说,应当尽量使用 Corepack 而非 Volta。
综上,一个比较推荐的做法是:
- 使用 volta 管理 node,以及全局安装的 npm 包
- 使用 corepack 管理包管理器
3.2 Volta 与 Corepack 之间的冲突问题
由于 volta 和 corepack 对于包管理器的管理都是基于 shim 的,也就是说他们都会拦截 yarn 以及 pnpm 指令,因此两个管理器之间存在冲突,你可以能会遇到以下问题:
- volta 安装了高版本 node,但是 corepack 指令无法使用;
- 使用 volta 管理的 node 进行全局安装 corepack,在使用 corepack 管理的 yarn/pnpm 执行 package.json 中的脚本时,node 版本也会被限制为 volta 锁定 corepack 时的版本;
- 通过其他途径安装了 corepack,但就是无法使用 corepack 管理的 yarn/pnpm,当使用 yarn 指令时,会出现 volta 的警告;
要想解决这个问题,一定要遵循如下的原则:
- 不要使用 volta 管理的 node 来全局安装 corepack
- 让 corepack 的 shim 覆盖掉 volta 的 shim
以下以 MacOS 和 Ubuntu 来演示一下如何同时安装 volta 与 corepack 并解决其冲突:
MacOS 的安装流程
前置条件:最好先卸载设备上所有的包管理器以及 node
首先,如果安装了 volta,按照以下步骤 卸载 volta:
rm -rf ~/.volta
然后使用 homebrew 安装 corepack:
brew install corepack
由于 corepack 依赖 node,brew 会在你的电脑上安装 node@20,不过没关系,后续我们使用 volta 管理 node,安装的这个 node 不会被使用,也不会影响我们使用 volta 切换 node 版本
测试一下 corepack 指令,并将其启用:
corepack enable
corepack prepare --all
安装 volta:
curl https://get.volta.sh | bash
使用 volta 安装 node:
volta install node
安装完 node 后可以检查一下 pnpm/yarn 指令是否被 volta 拦截:
which yarn
# 如果输出以下路径,说明正在使用 corepack 管理 yarn
/usr/local/bin/yarn
后续就不要使用 volta 安装任何包管理器,也不要执行 npm install yarn -g
或者 npm install pnpm -g
,否则 corepack 可能失效。
如果 corepack 失效,同时使用which yarn
显示的 yarn 的执行文件在~/.volta/bin/yarn
的位置,可以使用corepack enable --install-directory ~/.volta/bin
来强行将 coreapck 的 shim 覆盖到 volta 的 shim 上。
Linux 的安装流程
同样的,在安装 corepack 前需要卸载 volta:
rm -rf ~/.volta
由于 Ubuntu 等 Linux 系统没有 homebrew,同时系统的安装源中也没有 corepack,所以我们要通过安装 node@20 的方式来安装 corepack,输入以下指令来添加 node@20 的安装源,并安装 node(其他 Linux 系统可以查看 这里):
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - &&\
sudo apt-get install -y nodejs
测试一下 corepack 指令,并将其启用:
corepack enable
corepack prepare --all
安装 volta:
curl https://get.volta.sh | bash
使用 volta 安装 node:
volta install node
与 MacOS 不同的是,当你重启终端后会发现 corepack 失效了,具体的表现为,当你使用 yarn -v
时会出现如下提示:
Volta error: Yarn is not available.
Use `volta install yarn` to select a default version (see `volta help install` for more info).
可以使用 which yarn
来查看 yarn 的执行路径变为了:
/root/.volta/bin/yarn
在安装了 corepack 但未安装 volta 时,执行路径为 /usr/bin/yarn
我们可以使用如下指令强行让 corepack 的 shim 覆盖掉 volta 的 shim:
corepack enable --install-directory ~/.volta/bin
进入到 ~/.volta/bin
目录下,使用 ls -l
指令,我们就会发现 volta 的 pnpm、yarn 相关的指令都被指向到了 corepack 的 shim:
lrwxrwxrwx 1 root root 51 7月 6 17:08 pnpm -> ../../../usr/lib/node_modules/corepack/dist/pnpm.js
lrwxrwxrwx 1 root root 51 7月 6 17:08 pnpx -> ../../../usr/lib/node_modules/corepack/dist/pnpx.js
lrwxrwxrwx 1 root root 51 7月 6 17:08 yarn -> ../../../usr/lib/node_modules/corepack/dist/yarn.js
lrwxrwxrwx 1 root root 54 7月 6 17:08 yarnpkg -> ../../../usr/lib/node_modules/corepack/dist/yarnpkg.js
这样,yarn/pnpm 的管理权就又重新回到了 corepack 手上,可以愉快的使用 corepack 了。
3.3 低版本 node 项目中无法使用 corepack
如果你的某个项目低于 node@16.9.0,并且这个项目使用了 pnpm 或者 yarn 来作为包管理器管理项目依赖,那么当你安装并启用了 corepack 后你会发现无法使用 pnpm 和 yarn 指令了,会出现如下报错:
/usr/local/Cellar/corepack/0.19.0/libexec/lib/node_modules/corepack/dist/lib/corepack.cjs:39787
process.exitCode ??= code;
^^^
SyntaxError: Unexpected token '??='
at wrapSafe (internal/modules/cjs/loader.js:1029:16)
at Module._compile (internal/modules/cjs/loader.js:1078:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1143:10)
at Module.load (internal/modules/cjs/loader.js:979:32)
at Function.Module._load (internal/modules/cjs/loader.js:819:12)
at Module.require (internal/modules/cjs/loader.js:1003:19)
at require (internal/modules/cjs/helpers.js:107:18)
at Object.<anonymous> (/usr/local/Cellar/corepack/0.19.0/libexec/lib/node_modules/corepack/dist/pnpm.js:2:1)
at Module._compile (internal/modules/cjs/loader.js:1114:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1143:10)
这是因为 corepack 本身是依赖高版本 node 运行的,当当前系统环境的 node 低于 16.9.0 时,当用户使用 pnpm/yarn 指令会调用 corepack 的 shim,然后执行 corepack 的代码,但是由于当前系统环境的 node 版本低于 corepack 的要求,因此就会出现运行错误。
解决这个问题最好的方案是将 node 升级,如果是在无法升级,则可以尝试以下方案:
- 【最笨的方案,不推荐使用】切回高版本 node,并禁用 corepack,然后回到低版本 node,全局安装 yarn/pnpm(回到高版本 node 前卸载掉全局安装的 yarn/pnpm,然后重新启用 corepack);
- 【推荐网速快的使用】使用
npx yarn
或npx pnpm
指令(还可以指定版本,比如npx pnpm@6
)来绕过 corepack,这种方案最便捷,但是由于是使用 npx,每次都需要重新下载 pnpm 和 yarn,网速快的话可以无视; - 【配置复杂,但使用比较方便】使用
volta install
安装一个你需要版本的包管理器,比如volta install pnpm@6
,然后你可以在~/.volta/tools/image/packages/pnpm/bin
目录下找到一个pnpm
的可执行文件,你可以在你的.bashrc
或者.zshrc
使用 alias 创建一个指令别名,如alias pnpm@volta='~/.volta/tools/image/packages/pnpm/bin/pnpm'
,然后就可以在需要的时候使用 pnpm@volta 来代替执行 pnpm 指令,如果需要切换版本,则重新使用volta install
安装你需要的版本即可;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。