前言
最近,发现自己电脑上的nodejs版本居然还是14+,而最新的已经到了18+,由于并没有使用任何版本管理工具,于是直接升级到了最新的版本(v18.14.1),然后悲剧就发生了。
电脑里以往的绝大部分的Vue和React项目,在执行脚本构建命令如 npm run dev
或 npm run build
时,都出现同样的错误,导致构建失败,并且报类似下面的错误:
查找原因
出现了问题,自然要想办法解决。
在网上搜索了一圈,发现该问题早已出现,一般描述的大致原因就是:当 nodejs
升级到17+版本以后,开始支持 OpenSSL 3.0
,而 OpenSSL 3.0
对各种摘要算法做了更严格的限制,可能会导致一些程序运行错误。
webpack源码与配置
但其实,并不是所有的项目都会出现,比如使用了最新版本的 webpack
工具的项目就能够正常运行,所以得搞清楚其中的具体原因,到底是哪些地方影响了项目运行。
而要搞清原因,还得回到错误信息里,仔细查看上图里的错误信息,可以发现:Error
出现在了 webpack
包的 lib/util/createHash.js
代码文件,查看该文件,可知主要是用于创建hash值,这部分的核心代码如下所示:
switch (algorithm) {
case 'debug':
return new DebugHash()
default:
return new BulkUpdateDecorator(require('crypto').createHash(algorithm))
}
从上面的代码,我们可以发现:webpack
的 hash
使用了 nodejs
中的 crypto
加密模块,根据传入的算法名称创建 Hash
对象。
我们直接代码测试,进一步发现当前版本的 webpack
,这里的参数 algorithm
算法都是使用的 md4
,搜索 webpack
源码还能发现很多地方直接写死了 md4
算法名:
而查看webpack文档,也有类似的介绍。
从 webpack
的源码里,我们还找到了一个配置属性:output.hashFunction
,在webpack配置说明文档里也找到该属性的说明:
如上图所示,该属性定义webpack可使用的散列算法,默认也是 md4
。
查看nodejs的crypto模块
在回到错误源码上,主要是这句代码出错:
require('crypto').createHash(algorithm)
上面的代码里调用了 crypto
模块的 createHash
方法,我们查看该方法的文档,可以发现有这样一段话:
algorithm
取决于平台上OpenSSL
版本支持的可用算法。
例如'sha256'
、'sha512'
等。
在最近的OpenSSL
版本中,openssl list -digest-algorithms
将显示可用的摘要算法。
可见 webpack
源码里的 algorithm
就表示 OpenSSL
能够支持的可用算法,——在源码注释里也有解释。
而 md4
显然也是其中一种算法,但这里却创建失败了。
到这里,我们已能发现问题所在:crypto
模块的 createHash
方法在使用 md4
算法创建 Hash
对象时失败,无法创建,因此报错。
为什么会报错呢?继续寻找答案,我们直接在当前node环境下调用该方法:
require('crypto').createHash('md4')
立马报错:
错误信息和项目中的类似,出现不支持 unsupported
,这里还出现了 opensslErrorStack
错误栈,初始化失败,网上提到 openssl 3.0
不支持 md4
算法看上去是正确的,但为什么不支持呢?
查看openssl问题
我们先了解下这里提到的 openssl
是什么?OpenSSL
是一个强大的安全套接字层密码库的开源项目,它提供了一个功能丰富 openssl
安全工具包,拥有多种加密算法,能处理对称与非对称算法密钥、数字证书编解码等等。
而上文也提到在 nodejs
的加密模块中,生成hash就借助了 openssl
等能力,其中通过命令 openssl list -digest-algorithms
可以查看当前平台支持哪些算法,在命令行执行后,可以发现有几十种算法,而 md4
也名列其中:
那又为什么会创建失败呢,继续查看 openssl 3.0
相关的文档,经过寻找,终于发现问题的细节:
OpenSSL
中提供了5个Provider
(它表示算法实现的容器),我们需要了解如下两个:- default:默认,提供了所有标准的内置算法,如果没明确指定则将使用默认算法容器;
- legacy:遗留,已不常用或者被反对使用的算法,正常情况下不可用,除非显式指定使用
legacy
容器;
openssl 3.0
正常都是使用default
容器提供的算法,而md4
却位于legacy
算法容器中,所以正常情况下并不支持md4
算法,因此导致错误。
legacy
算法容器中,包含 MD2、MD4、MDC2、RMD160、CAST5、BF、IDEA、SEED、RC2、RC4、RC5 和 DES 等算法。
解决方法
升级webpack
从前文可知,目前环境出现的问题,主要是 webpack
的使用了 md4
算法导致错误,可见还得在 webpack
工具上下功夫。
注意:只改变
webpack
的配置属性hashFunction
值为其他算法,无法解决该问题,是因为webpack
中很多地方写死了md4
字符串。
如果我们把 webpack
进行升级到较新的版本,就可以解决此类问题。
据查,自 5.61.0
版本开始,webpack
就已解决了这个问题,可以查看同样创建hash的源码,新修改的如下所示:
switch (algorithm) {
//...
case "md4":
if (createMd4 === undefined) {
createMd4 = require("./hash/md4");
if (BatchedHash === undefined) {
BatchedHash = require("./hash/BatchedHash");
}
}
return new BatchedHash(createMd4());
// ...
default:
if (crypto === undefined) crypto = require("crypto");
return new BulkUpdateDecorator(
() => crypto.createHash(algorithm),
algorithm
);
}
以上代码可看出,针对 md4
已经做了专门处理,不再直接使用 crypto
模块创建,而是新建 md4
算法。
针对使用 @vue/cli-service
和 react-scripts
工具构建的项目,由于还是依托 webpack
,所以对应工具进行升级即可,其中:
@vue/cli-service
在5.0.1
版本开始,已经支持webpack@5.61.0
react-scripts
在5.0.0
版本开始,已经支持webpack@5.61.0
但是,由于升级构建工具,可能随之带来很多不可知的问题,不少依赖包都会因为不支持而报错,升级带来的成本并不小,所以很多人并不愿意升级构建工具。
当然,我们也可以自己修改
webpack
源码解决问题,只需要再创建hash对象的地方拦截md4
换种算法就可以了,但还需要更改另外一个依赖包terser-webpack-plugin
,这个库里也有md4
。
提供legacy容器环境
根据上文提到的错误原因,可知是由于我们使用了 openssl
的 legacy
算法容器里的算法 md4
,并不被标准默认容器支持,所以需要显式地指定算法容器环境。
指定 legacy
算法容器,使用 --openssl-legacy-provider
,网上提供的很多解决方案,都是基于这个原则去做的,只不过都不交代具体的原因。
增加系统环境变量
Windows系统下,可在系统环境变量里添加一个新的变量:NODE_OPTIONS
,值为:--openssl-legacy-provider
。
如下图所示:
需要注意的是,
通过设置系统变量,我们只能解决 Git Bash
在系统右键启动时的构建命令正确执行,或者直接启动 git-bash.exe
软件,即单独的命令窗口能有效解决问题:
而在VSCode中添加的 git-bash
工具,却并不起作用,即在下面的窗口下执行构建命令时仍然会报错:
我并没有找到VSCode设置git-bash时有啥特殊的配置,导致两者之间有差异,如果有人了解,希望可以在评论里告知。
在package.json中添加设置
一个能在新版本nodejs中完整解决问题的方案,就是在 package.json
配置的脚本命令中,增加对应的设置,如下所示:
"dev": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
即增加 set NODE_OPTIONS=--openssl-legacy-provider
命令设置,就可以在 Git Bash
的右键命令以及VSCode中都能正常执行构建命令了。
注意,如果在 package.json
中添加脚本命令设置,会导致使用老版本的同事,无法执行脚本,因为老版本 nodejs
并不支持设置 --openssl-legacy-provider
属性值,将有如下提示并中断构建执行:
node: --openssl-legacy-provider is not allowed in NODE_OPTIONS
nodejs多版本管理或者版本降级
另外还有一种方案,就是不要使用最新版本的 nodejs
,降级后重新使用V16的版本,低版本的 nodejs
可以保障项目正常运行。
或者,还可以使用 nodejs
的多版本管理工具,能够自由切换不同的版本,同样满足要求。关于 nodejs
版本管理的简单介绍,可查看博文 node 和 npm 如何进行版本升级。
但是,已经使用了新版本的nodejs,再去用老版本的,也多少有些不那么回事。
最后
本文详细描述了 nodejs
升级后引起的前端老旧项目中构建工具生成hash报错的问题(0308010C:digital envelope routines::unsupported),以及给出的几种解决方案。
这几种解决方案各自都存在一些不完美的地方,但都能解决当前构建错误,可以根据自己的实际需求来确定使用哪种方案。