记一次有趣的node版本冲突的解决过程
这几天遇到一个项目,里面的情况有点颠覆我的认知,又一次拓展了我的见识,在这里与大家分享,希望与大家有所帮助。
最近接手了一个项目,本以为是一次普通的依赖安装,没想到却让我在Node.js的版本冲突中挣扎了整整两天。不得不说,遇到的情况颠覆了我的认知,居然还有项目主动通过.gitignore屏蔽lock文件;居然在没有lock文件下,项目能正常安装和启动。这是一个不得不去踩的坑。
记录一
拿到了这个项目,根据我的经验,首先运行npm i
进行安装,然后就报各种冲突。
bash-3.2$ npm i
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: byzer@1.0.0
npm error Found: vue@2.6.11
npm error node_modules/vue
npm error vue@"2.6.11" from the root project
npm error peer vue@">= 2.5 < 2.7" from @vue/composition-api@1.7.2
npm error node_modules/@vue/composition-api
npm error dev @vue/composition-api@"^1.4.7" from the root project
npm error peerOptional @vue/composition-api@"^1.0.0-rc.6" from @antv/x6-vue-shape@1.5.4
npm error node_modules/@antv/x6-vue-shape
npm error @antv/x6-vue-shape@"^1.3.1" from the root project
npm error
npm error Could not resolve dependency:
npm error peer vue@"^2.6.12 || ^3.0.0" from @antv/x6-vue-shape@1.5.4
npm error node_modules/@antv/x6-vue-shape
npm error @antv/x6-vue-shape@"^1.3.1" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
...
这也不是没有见过,信息里有提示,因此,我再次调整了安装命令行npm i --legacy-peer-deps
。
经过漫长的等待,迎来了下面的错误。下面这个错误说明了node-sass依赖了node-gyp,而node-gyp与我当前的node版本不兼容。
我当前的node版本是v20,问了一下同事,它们使用的就是node v16.20.2。
npm error code 1
...
yp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
npm error gyp info it worked if it ends with ok
npm error gyp verb cli [
npm error gyp verb cli '/usr/local/bin/node',
npm error gyp verb cli 'rebuild',
...
npm error gyp verb cli '--verbose',
npm error gyp verb cli '--libsass_ext=',
npm error gyp verb cli '--libsass_cflags=',
npm error gyp verb cli '--libsass_ldflags=',
npm error gyp verb cli '--libsass_library='
npm error gyp verb cli ]
npm error gyp info using node-gyp@7.1.2
npm error gyp info using node@20.17.0 | darwin | x64
npm error gyp verb command rebuild []
npm error gyp verb command clean []
npm error gyp verb clean removing "build" directory
npm error gyp verb command configure []
npm error gyp verb find Python checking Python explicitly set from command line or npm configuration
npm error gyp verb find Python - "--python=" or "npm config get python" is "/usr/local/bin/python"
npm error gyp verb find Python - executing "/usr/local/bin/python" to get executable path
npm error gyp verb find Python - executable path is "/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/Ma
cOS/Python"
npm error gyp verb find Python - executing "/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Pytho
n" to get version
npm error gyp verb find Python - version is "2.7.12"
npm error gyp info find Python using Python version 2.7.12 found at "/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python
.app/Contents/MacOS/Python"
npm error gyp verb get node dir no --target version specified, falling back to host node version: 20.17.0
npm error gyp verb command install [ '20.17.0' ]
npm error gyp verb install input version string "20.17.0"
npm error gyp verb install installing version: 20.17.0
npm error gyp verb install --ensure was passed, so won't reinstall if already installed
npm error gyp verb install version is already installed, need to check "installVersion"
npm error gyp verb got "installVersion" 11
npm error gyp verb needs "installVersion" 9
npm error gyp verb install version is good
npm error gyp verb get node dir target node version installed: 20.17.0
...
npm error gyp verb build/config.gypi creating config file
npm error gyp ERR! UNCAUGHT EXCEPTION
npm error gyp ERR! stack TypeError: Cannot assign to read only property 'cflags' of object '#<Object>'
...
npm error gyp ERR! stack at FSReqCallback.oncomplete (node:fs:187:23)
npm error gyp ERR! System Darwin 23.4.0
npm error gyp ERR! node -v v20.17.0
npm error gyp ERR! node-gyp -v v7.1.2
npm error gyp ERR! Node-gyp failed to build your package.
npm error gyp ERR! Try to update npm and/or node-gyp and if it does not help file an issue with the package author.
npm error Build failed with error code: 7
npm error A complete log of this run can be found in: /Users/caozy/.npm/_logs/2025-06-19T00_54_26_360Z-debug-0.log
bash-3.2$
上面日志中,我删除了一些本地信息,但留下来的仍然很重要。
npm error gyp verb install version is already installed, need to check "installVersion"
npm error gyp verb got "installVersion" 11
npm error gyp verb needs "installVersion" 9
npm error gyp verb install version is good
npm error gyp verb get node dir target node version installed: 20.17.0
node-gyp使用installVersion
来判断Nodejs版本的兼容性,映射关系如下:
- installVersion: 9 → 对应 Node.js 10.x
- installVersion: 10 → 对应 Node.js 12.x
- installVersion: 11 → 对应 Node.js 14.x
- installVersion: 12 → 对应 Node.js 16.x
- installVersion: 13 → 对应 Node.js 18.x
- installVersion: 14 → 对应 Node.js 20.x
从信息看node-gyp最高支持node14,但node14已经在2023年停止维护,因此选择node16。
记录二
将系统的node版本切换到Node V16.20.2,再次运行npm i --legacy-peer-deps
。
当然,我的直觉告诉我,我的运气不会这么好,估计还有问题。
npm ERR! gyp verb cli 'rebuild',
npm ERR! gyp verb cli '--verbose',
npm ERR! gyp verb cli '--libsass_ext=',
npm ERR! gyp verb cli '--libsass_cflags=',
npm ERR! gyp verb cli '--libsass_ldflags=',
npm ERR! gyp verb cli '--libsass_library='
npm ERR! gyp verb cli ]
npm ERR! gyp info using node-gyp@3.8.0
npm ERR! gyp info using node@16.20.2 | darwin | x64
npm ERR! gyp verb command rebuild []
npm ERR! gyp verb command clean []
npm ERR! gyp verb clean removing "build" directory
npm ERR! gyp verb command configure []
npm ERR! gyp verb check python checking for Python executable "/usr/local/bin/python" in the PATH
npm ERR! gyp verb `which` succeeded /usr/local/bin/python /usr/local/bin/python
npm ERR! gyp verb check python version `/usr/local/bin/python -c "import sys; print "2.7.12
npm ERR! gyp verb check python version .%s.%s" % sys.version_info[:3];"` returned: %j
npm ERR! gyp verb get node dir no --target version specified, falling back to host node version: 16.20.2
npm ERR! gyp verb command install [ '16.20.2' ]
npm ERR! gyp verb install input version string "16.20.2"
npm ERR! gyp verb install installing version: 16.20.2
npm ERR! gyp verb install --ensure was passed, so won't reinstall if already installed
npm ERR! gyp verb install version is already installed, need to check "installVersion"
npm ERR! gyp verb got "installVersion" 9
npm ERR! gyp verb needs "installVersion" 9
npm ERR! gyp verb install version is good
npm ERR! gyp verb get node dir target node version installed: 16.20.2
如果仔细对比node-gyp的版本,大家会发现这次安装的node-gyp的版本和之前的不一样了。这次安装的是3.8.0版本,之前安装的是7.1.2版本。在项目的package.json中,并没有node-gyp这个依赖,那这个版本的变化是如何产生的呢?
node-gyp的版本需要与 Node.js 版本兼容,因为它负责编译底层 C++ 扩展,而 Node.js 的底层 API 在不同版本中可能有变化。以下是关键对应关系:
node-gyp 版本 | 支持的 Node.js 版本范围 |
---|---|
v3.x | Node.js 4-14 |
v5.x | Node.js 10-16 |
v6.x | Node.js 12-18 |
v7.x | Node.js 14-20+ |
当我们使用 Node.js 20 时,npm 会自动选择支持该版本的最新node-gyp(如 v7.1.2)
当我们切换到 Node.js 16 时,npm 会降级到兼容 v16 的node-gyp版本(如 v3.8.0)
当然,上面的解释只是一个小小的插曲,报错的信息很长,最后显示的错误信息如下:
npm ERR! 4 errors generated.
npm ERR! make: *** [Release/obj.target/binding/src/binding.o] Error 1
npm ERR! gyp ERR! build error
npm ERR! gyp ERR! stack Error: `make` failed with exit code: 2
npm ERR! gyp ERR! stack at ChildProcess.onExit (/.../node_modules/gulp-sass/node_modules/node-gyp/lib/build.js:262:23)
npm ERR! gyp ERR! stack at ChildProcess.emit (node:events:513:28)
npm ERR! gyp ERR! stack at Process.ChildProcess._handle.onexit (node:internal/child_process:293:12)
npm ERR! gyp ERR! System Darwin 23.4.0
...
npm ERR! gyp ERR! node -v v16.20.2
npm ERR! gyp ERR! node-gyp -v v3.8.0
npm ERR! gyp ERR! not ok
npm ERR! Build failed with error code: 1
通过最后的错误,我们发现node-sass在node16上编译失败,如何解决这个问题呢?豆包的解释是我的node-gyp的版本过低,解决方案是建议我将node-gyp的版本升级到9.4.0。
记录三
我和同事都使用的同一个node版本,为什么我这里就安装失败呢?在我迷茫之际,我发现这个项目居然没有lock文件。
lock文件的意义
在 npm 项目中,package-lock.json
(或 Yarn 中的 yarn.lock
)是一个非常重要的文件,它的作用是锁定依赖的版本,确保每次安装时使用的依赖版本完全一致。以下是它的主要意义和作用:
-
版本锁定:
package-lock.json
记录了项目中所有依赖及其子依赖的确切版本号。即使package.json
中使用了模糊的版本范围(比如^1.2.3
),package-lock.json
也会固定具体的版本(比如1.2.4
),避免因依赖版本更新导致的不一致问题。 -
依赖树一致性:
它保存了依赖的完整树状结构,确保不同开发者在不同时间或环境下安装的依赖完全相同。如果没有这个文件,不同机器或不同时间安装的依赖可能会因为版本更新而产生差异,导致项目运行异常。 -
提升安装速度:
由于依赖版本被锁定,npm 可以跳过版本解析的步骤,直接从缓存或远程仓库下载固定版本的依赖,从而加快安装速度。 -
避免“依赖地狱”:
在复杂的项目中,依赖之间可能存在版本冲突。package-lock.json
通过固定版本,避免了因依赖冲突导致的安装失败或运行时错误。 -
团队协作的基石:
在团队开发中,package-lock.json
确保了所有成员使用相同的依赖环境,避免了“在我机器上能跑,在你机器上不行”的问题。
颠覆我的认知
没有lock文件就算了,lock文件还被添加到了.gitignore中。看来还是有意要去掉lock文件啊,这确实颠覆了我的认知。
我问了一下同事,它们是如何安装的,它们给我一个命令yarn add xxx@6.10.2
(这里不便透露具体的库的名称)。
但遗憾的是,即便我使用了这个命令,也还是不行。最后确定,他们用的是windows系统,我用的是mac系统。
记录四
这时,我发现项目里面有一个README.md,上面建议的node版本时11.x。为了排出系统的干扰,这次我使用了Docker容器,通过Docker容器来编译,每一次都是干净的环境,有问题也容易暴露。我的脚本如下:
#!/bin/bash
# 宿主机上的源代码目录
HOST_SOURCE_DIR="..."
# 容器内的挂载目录
CONTAINER_SOURCE_DIR="/app"
# 使用 node:11.7 镜像创建并运行容器
docker run -it --rm \
-v "$HOST_SOURCE_DIR:$CONTAINER_SOURCE_DIR" \
-w "$CONTAINER_SOURCE_DIR" \
node:11.15.0 \
sh -c "rm -rf node_modules && npm config set registry https://registry.npmmirror.com && npm i --legacy-peer-deps && npm run dev"
这次编译没有报node-sass的相关编译错误,并且安装成功。问题出在项目启动上:
> vue-cli-service serve
INFO Starting development server...
(node:2842) ExperimentalWarning: queueMicrotask() is experimental.
ERROR Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc to be present in the dependency tree.
Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc to be present in the dependency tree.
这是一个vue2项目,怎么会依赖vue3才会依赖的@vitejs/plugin-vue
。不用说,这肯定是其中某一个依赖在安装的时候,在没有lock文件的情况下,按照semver的标准,安装了最新版本导致的这个问题。这让我想起之前的一次类似经历,因此,决定改用yarn + resolutions的方式来解决这个问题。
记录五
因此,我调整了安装脚本。
#!/bin/bash
# 宿主机上的源代码目录
HOST_SOURCE_DIR="..."
# 容器内的挂载目录
CONTAINER_SOURCE_DIR="/app"
# 使用 node:11.7 镜像创建并运行容器
docker run -it --rm \
-v "$HOST_SOURCE_DIR:$CONTAINER_SOURCE_DIR" \
-w "$CONTAINER_SOURCE_DIR" \
node:16.20.2-buster \
sh -c "rm -rf node_modules && rm yarn.lock && npm config set registry https://registry.npmmirror.com && yarn install && yarn run dev"
运行上面的脚本肯定会报错,如下:
error abbrev@3.0.1: The engine "node" is incompatible with this module. Expected version "^18.17.0 || >=20.5.0". Got "16.20.2"
error Found incompatible module.
然后我将abbrev添加到package.json的resolutions中,当然需要为其设置一个合适的版本号。
{
...
resolutions: {
"abbrev": "2.0.0",
...
}
}
这个合适的版本我是让豆包提供的,当然,也有几次豆包提供的版本不正确,但安装过程中会列出所有可选的版本号,选一个匹配的就行。
也是在这个过程中,我将之前提到的node-gyp的版本设置为了10.0.0。
这个过程简单,就是一个愚公移山的事情,需要不断运行脚本,根据错误提示将imcompatible module
的名称以及版本号添加到resolutions
中,在运行了47次之后,终于安装成功了,进入了启动阶段。
记录六
用过webpack编译的都知道,当项目启动时,会看到代码的路径会不断跳出来,正想庆贺的时候,又爆出来了一段红色错误。
ERROR Error loading /app/vue.config.js:
ERROR TypeError [ERR_INVALID_ARG_TYPE]: The "original" argument must be of type function. Received an instance of Object
TypeError [ERR_INVALID_ARG_TYPE]: The "original" argument must be of type function. Received an instance of Object
at promisify (node:internal/util:332:3)
at Object.<anonymous> (/app/node_modules/del/index.js:13:17)
at Module._compile (node:internal/modules/cjs/loader:1198:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10)
at Module.load (node:internal/modules/cjs/loader:1076:32)
at Function.Module._load (node:internal/modules/cjs/loader:911:12)
at Module.require (node:internal/modules/cjs/loader:1100:19)
at require (node:internal/modules/cjs/helpers:119:18)
at Object.<anonymous> (/app/node_modules/filemanager-webpack-plugin/dist/index.cjs:9:11)
at Module._compile (node:internal/modules/cjs/loader:1198:14)
error Command failed with exit code 1.
肯定还有哪里的依赖有问题,但是安装过程又没有报错。这个时候,我感觉快崩溃了,因为精力所剩无几。从错误上看是filemanager-webpack-plugin
所依赖的del有问题。
filemanager-webpack-plugin中依赖的del的版本是^6.0.0
,而node_modules中del的实际版本是6.1.1
。
死马当活马医,我决定将del:6.0.0
添加到resolutions中碰碰运气,但没有碰到,依然启动失败,还是相同的错误。
记录七(纯属运气)
正当我崩溃的时候,我瞥见del的package.json中依赖了rimraf库,这个库的版本号是"^3.0.2",我为什么对rimraf有印象呢?因为在之前解决版本冲突的过程中,我就往resolutions里面添加过rimraf,我记得当时添加的版本号不是3开头的。当我检查resolutions中rimraf的版本号时,发现它是5.0.5。
于是,我将resolutions中rimraf的版本号改为3.0.2,再次运行脚本,项目终于成功启动。
最后的思考
项目的lock文件一定要跟踪,如果发生变化,那么要确保CICD流程没问题。
另外,同事说,他们使用yarn add xxx后,并没有生成yarn.lock文件,这是怎么回事,我目前也没有搞明白。
yarn + resolutions
在整个问题的解决过程中,yarn的resolutions
提供了核心支持,它允许我们强制指定依赖项的版本,即使这些依赖项是间接依赖(即嵌套在其他依赖项中的依赖项)。这在解决依赖冲突或修复某些依赖项的特定版本时非常有用。
如何理解 resolutions
?
-
作用:
resolutions
可以覆盖项目中所有依赖项的版本,包括直接依赖和间接依赖。- 它通常用于解决以下问题:
- 依赖冲突:当多个依赖项需要不同版本的同一个包时,可以通过
resolutions
强制统一版本。 - 修复漏洞:如果某个间接依赖存在漏洞,可以通过
resolutions
强制升级到修复后的版本。 - 解决兼容性问题:某些依赖项可能需要特定版本的子依赖才能正常工作。
- 依赖冲突:当多个依赖项需要不同版本的同一个包时,可以通过
-
工作原理:
- Yarn 在安装依赖时,会优先使用
resolutions
中指定的版本,忽略其他依赖项中对该包的版本要求。 - 这类似于
package.json
中的overrides
(在 npm 中也有类似功能)。
- Yarn 在安装依赖时,会优先使用
-
使用场景:
- 当你的项目依赖链中出现了多个版本的同一个包,导致构建或运行时出现问题。
- 当某个间接依赖的版本存在已知问题(如安全漏洞或 bug),需要强制升级或降级。