-
Notifications
You must be signed in to change notification settings - Fork 1
Description
这篇文章并不是关于使用 nvm 或者 n 等版本工具的指南,而是探讨如何在系统中同时安装两个或多个不同版本的 Node.js。注意是 同时 存在,比如在终端输入
node1跟node2时,可以分别执行两个不同版本的 Node.js,而不需要版本管理工具事先进行切换。
缘由
同时在系统中安装多个不同版本的 Node.js 跟 npm 听起来像是个伪需求,毕竟当我们需要某个特定版本的 Node.js 时,使用版本工具进行安装、切换都很方便。
不过前些天公司一个运维的小伙伴刚好跟我讨论这个问题,他想在机器上已经安装了一个 Node.js 的情况下,再安装一个不同版本的 Node.js (通过 node2, npm2 来调用),然后执行 npm2 run build 的时候用 node2 来编译项目,但是经过他实际测试发现 build 会失败,不清楚问题出在哪里,出于好奇的目的我打算自己试一试。
实验
为了不搞乱自己的电脑,我用 docker 启动了一个 ubuntu,在这个干净的 ubuntu 里实验。
从官网下载 v10.16.0 及 v8.12.0 两个版本的 Node.js,解压后分别进入各自的目录执行:
./configure
make进行编译。编译完成后分别将各自的 node, npm 可执行文件链接到 /usr/local/bin 目录下:
# v10.16.0
ln -s `pwd`/out/Release/node /usr/local/bin/node
ln -s `pwd`/deps/npm/bin/npm-cli.js /usr/local/bin/npm
# v8.12.0
ln -s `pwd`/out/Release/node /usr/local/bin/node1
ln -s `pwd`/deps/npm/bin/npm-cli.js /usr/local/bin/npm1最后得到的结构如下所示:
/usr/local/bin/
|-- node -> /root/node-v10.16.0/out/Release/node
|-- node1 -> /root/node-v8.12.0/out/Release/node
|-- npm -> /root/node-v10.16.0/deps/npm/bin/npm-cli.js
`-- npm1 -> /root/node-v8.12.0/deps/npm/bin/npm-cli.js我们在终端里试一下命令:
后来发现这张图里有个 typo,最后的
npm本来应该为npm1,输出的版本应该是6.4.1,如果我没记错的话。暂时没纠正这个错图了,因为 docker container 被我删了...
可以看到目前系统里存在两个不同的命令 node 跟 node1,分别指向 v10.16.0 与 v8.12.0,以及各自搭配的 npm。
编译
安装成功后,现在是时候体验一下不同版本的 Node.js, npm 实际使用效果了 。为了简化操作,我们选用 preact 项目来实验 npm run build。
在正式运行 npm 命令之前,我们先查看一下 npm1 这个命令本身的代码,执行 vim /usr/local/bin/npm1 可以看到第一行 (shebang) 是:
#! /usr/bin/env node这一行的意思是,找到当前系统中叫 node 的命令来执行此文件。回想一下我们之前的链接操作,node 是指向 v10.16.0 版本的,也就是说我们直接执行 npm1 -v 的时候,其实调用的是 v10.16.0 版本的 node 来运行 npm1 。为了匹配正确的 node 版本,这里我们需要把 npm1 的 shebang 改成:
#! /usr/local/bin/node1改完保存并且克隆 preact 的项目后,我们执行 npm1 install,这一步显示成功。
继续执行 npm1 run build,编译成功。不过有个 warning 引起了我的注意:
npm WARN lifecycle The node binary used for scripts is /usr/local/bin/node but npm is using /root/node-v8.12.0/out/Release/node itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.
这里的意思是,如果我们想让运行 run-script 命令时,用的 node 版本与运行当前 npm 的版本一致的话,应该要传递一个 --scripts-prepend-node-path 参数。事实上,通过查看调试 npm 自身的代码可以发现,这个参数会使当前执行 npm 的 node 的可执行文件所在的目录,优先前置到 run-script 开启的子进程的 PATH 环境变量中,这样在实际执行 build 命令时,用的版本跟执行 npm1 的版本保持一致。可以看下 /usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/index.js 中第 118 行的代码:
if (shouldPrependCurrentNodeDirToPATH(opts)) {
// prefer current node interpreter in child scripts
pathArr.push(path.dirname(process.execPath))
}既然了解了这个参数的作用,这次我们执行:
npm1 run build --scripts-prepend-node-pathwarning 的确没了,但是出现了一个报错:
Error: The module '/root/preact/node_modules/iltorb/build/bindings/iltorb.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 64. This version of Node.js requires
NODE_MODULE_VERSION 57. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
这里提示说项目的依赖包在安装时候使用的 Node.js 版本与执行 build 的时候的版本不一致。我们最开始安装依赖的时候直接执行的是 npm1 run install,并没有带上 --scripts-prepend-node-path 参数,所以实际安装用的版本是 v10.16.0,而不是想要的 v8.12.0。我们重新带上参数安装并 build 试试:
npm1 install --scripts-prepend-node-path
npm1 run build --scripts-prepend-node-path可以发现这次成功了,而且用的版本是 npm1 对应的 Node.js v8.12.0。
(其实还有一种情况就是,当 npm 版本与 Node.js 版本不兼容时,直接运行 npm 也会报错)
结论
可以看到为了让 npm 能识别指定版本的 Node.js,我们需要改动到 npm-cli.js 中的 shebang。另外当多个版本的 Node.js, npm 同时共存时,还需要注意 npm 的配置文件需要隔离多份并且有些配置不能重复,比如用来放置全局依赖包的 prefix 文件夹路径不能相同,否则也会引起一些问题。鉴于这些问题,如果没有特殊需求,并不推荐同时安装多个不同版本的 Node.js,建议还是使用版本管理工具比较好。如果一定要保持现有的 Node.js 存在的情况下,需要使用另一个版本的 Node.js 来做一些事情,建议利用 docker 的 Node.js 镜像来实现,这样既方便又不怕搞乱现有的环境。
