最近,发现自己电脑上的nodejs版本居然还是14+,而最新的已经到了18+,由于并没有使用任何版本管理工具,于是直接升级到了最新的版本(v18.14.1),然后悲剧就发生了。
电脑里以往的绝大部分的Vue和React项目,在执行脚本构建命令如 npm run dev 或 npm run build 时,都出现同样的错误,导致构建失败,并且报类似下面的错误:
出现了问题,自然要想办法解决。
在网上搜索了一圈,发现该问题早已出现,一般描述的大致原因就是:当 nodejs 升级到17+版本以后,开始支持 OpenSSL 3.0,而 OpenSSL 3.0 对各种摘要算法做了更严格的限制,可能会导致一些程序运行错误。
但其实,并不是所有的项目都会出现,比如使用了最新版本的 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。
在回到错误源码上,主要是这句代码出错:
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 安全工具包,拥有多种加密算法,能处理对称与非对称算法密钥、数字证书编解码等等。
而上文也提到在 nodejs 的加密模块中,生成hash就借助了 openssl 等能力,其中通过命令 openssl list -digest-algorithms 可以查看当前平台支持哪些算法,在命令行执行后,可以发现有几十种算法,而 md4 也名列其中:
那又为什么会创建失败呢,继续查看 openssl 3.0 相关的文档,经过寻找,终于发现问题的细节:
legacy 算法容器中,包含 MD2、MD4、MDC2、RMD160、CAST5、BF、IDEA、SEED、RC2、RC4、RC5 和 DES 等算法。
从前文可知,目前环境出现的问题,主要是 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,所以对应工具进行升级即可,其中:
但是,由于升级构建工具,可能随之带来很多不可知的问题,不少依赖包都会因为不支持而报错,升级带来的成本并不小,所以很多人并不愿意升级构建工具。
当然,我们也可以自己修改 webpack 源码解决问题,只需要再创建hash对象的地方拦截 md4 换种算法就可以了,但还需要更改另外一个依赖包 terser-webpack-plugin,这个库里也有 md4。
根据上文提到的错误原因,可知是由于我们使用了 openssl 的 legacy 算法容器里的算法 md4,并不被标准默认容器支持,所以需要显式指定算法容器环境。
指定 legacy 算法容器,使用 --openssl-legacy-provider,网上提供的很多解决方案,都是基于这个原则去做的,只不过都不交代具体的原因。
Windows系统下,可在系统环境变量里添加一个新的变量:NODE_OPTIONS,值为:--openssl-legacy-provider。
如下图所示:
需要注意的是,
通过设置系统变量,我们只能解决 Git Bash 在系统右键启动时的构建命令正确执行,或者直接启动 git-bash.exe 软件,即单独的命令窗口能有效解决问题:
而在VSCode中添加的 git-bash 工具,却并不起作用,即在下面的窗口下执行构建命令时仍然会报错:
我并没有找到VSCode设置git-bash时有啥特殊的配置,会导致两者之间有差异,如果有人了解,希望可以在评论里告知。
一个能在新版本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,降级后重新使用V16的版本,低版本的 nodejs 可以保障项目正常运行。
或者,还可以使用 nodejs 的多版本管理工具,能够自由切换不同的版本,同样满足要求。关于 nodejs 版本管理的简单介绍,可查看博文 node和npm如何升级版本。
但是,已经使用了新版本的nodejs,再去用老版本的,也多少有些不那么回事。
本文详细描述了 nodejs 升级后引起的前端老旧项目中构建工具生成hash报错的问题(0308010C:digital envelope routines::unsupported),以及给出的几种解决方案。
这几种解决方案各自都存在一些不完美的地方,但都能解决当前构建错误,可以根据自己的实际需求来确定使用哪种方案。