如何让taro旧项目在Node17/18上跑起来

语言: CN / TW / HK

虽然人常说版本能用就不要动,但是有些时候总是想在新的环境中运行旧的项目,Taro就是个典型的例子,当下载了官方的实例项目之后,会发现在新版的Node中运行不起来,这篇文章就是来解决两个最为常见的问题.

什么是Taro

参照Taro官方文档

Taro 是一个开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发 微信 / 京东 / 百度 / 支付宝 / 字节跳动 / QQ / 飞书 小程序 / H5 / RN 等应用。
显而易见,其是一个主要用于小程序开发的跨平台框架,通过使用Taro组件库提供的公共接口以及生命周期,就能实现一端编写,编译成多个平台,甚至是RN. Taro跨端编写与原生小程序的对比

为什么是Taro

原生开发大多基于Vue的template-Scripts范式,对于React开发者不太友好,并且也不利于组件化.并且使用

Taro的运行原理

在微信小程序使用类Vue的模板形式开发的情况下,对于React开发者而言,只能自己实现转换.就像Babel 一样,对源代码进行词法分析,知道你写了哪些词,然后再进行语法分析,判断你的词有没有意义,符不符合基本(语)法,如果ok就进行语义分析,构建出一个表达代码功能含义的AST(虚拟语法树).因此只要使用官方提供的标准组件以及路由,在编译的时候会根据编译目标平台自动替换成相应的组件以及接口. 更多内容可以参照 官方博客中介绍AST的部分.
但是,这意味着编译后的代码与React毫无关系,只是单纯的代码像是React而已,这就要求开发者在写代码的时候严格按照Taro组件库的约束进行.既然eact最终还是调用浏览器的DOM接口,那直接将其进行抽象,从让调用者适应小程序变为让小程序配对调用的接口,最终实现运行时适配.
关于针对React的兼容性适配以及事件机制的实现可以参见 官方介绍

Taro旧项目在新版本(例如17/18以上)时可能会出现的问题

当直接把旧项目在新版的Node环境下运行,会出现两个错误,一个是少了点东西,一个是多了点东西.

ERR_OSSL_EVP_UNSUPPORTED

在进行构建的时候,有可能会输出以下的报错

{

opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],

library: 'digital envelope routines',

reason: 'unsupported',

code: 'ERR_OSSL_EVP_UNSUPPORTED'

}

该回答所示,具体原因在于中间React等包在生成哈希的时候使用了被OpenSSL 3.0所废弃的算法.

If you hit an ERR_OSSL_EVP_UNSUPPORTED error in your application with Node.js 17, it’s likely that your application or a module you’re using is attempting to use an algorithm or key size which is no longer allowed by default with OpenSSL 3.0. A command-line option, --openssl-legacy-provider, has been added to revert to the legacy provider as a temporary workaround for these tightened restrictions.

解决办法

  1. 设置环境变量使得Node使用旧版的openssl环境.以windows为例,以windows+powershell为例,在package.json中使用”pre”脚本来在运行build之前自动设置变量.具体而言,如果目标是运行”build”,那么在scripts中增加 json "prebuild ":"echo \"set node ssl config\"&& $env:NODE_OPTIONS=\"--openssl-legacy-provider\" "即可.

  2. 手动指定Webpack提供的哈希方法.如文档所示,通过在TaroWebpack配置文件config/index.js中配置hashFunction,可以使用npm包,而不是系统提供的ssl算法来进行哈希生成.

    The hashing algorithm to use. All functions from Node.JS' crypto.createHash are supported. Since 4.0.0-alpha2, the hashFunction can now be a constructor to a custom hash function. You can provide a non-crypto hash function for performance reasons.

    module.exports = { //... output: { hashFunction: require('metrohash').MetroHash64, }, };

    Make sure that the hashing function will have an update and digest methods available.

You must provide the URL of lib/mappings.wasm by calling SourceMapConsumer.initialize({ 'lib/mappings.wasm': ... }) before using SourceMapConsumer

在打包的时候还会出现这个问题

image.png
根据查找,可以发现这个问题出现在source-map这个包里.作为为混淆后代码提供源代码参照的文件,其被React Refresh Webpack Plugin等多个插件在打包时被使用.

Source-map干了什么

仓库以及使用介绍

SourceMap 与前端异常监控
一开始还会好奇为什么只要禁用了新版本Node提供的原生fetch就可以解决问题,实际查看解决问题的Commit后才会感觉哭笑不得: ```diff - if (typeof fetch === "function") { + / Determine browser vs node environment by testing the default top level + context. Solution courtesy of: http://stackoverflow.com/questions/17575790/environment-detection-node-js-or-browser / + const isBrowserEnvironment = (function() { + // eslint-disable-next-line no-undef + return (typeof window !== "undefined") && (this === window); + }).call(); + + if (isBrowserEnvironment) { // Web version of reading a wasm file into an array buffer.

let mappingsWasm = null; `` 好吧,原来一开始这个包使用是否有fetch方法来判断运行环境是浏览器还是Node.不过也难怪,其实谁都没想到一直都闷声发大财的Node咋突然就支持fetch了.现在判断this的指向总不会错了,毕竟Node中的this指向为Global或者{}(根据运行环节的不同,参见[这篇文章](http://zhuanlan.zhihu.com/p/489467530)),但是浏览器就是指向window. #### 解决方法 既然是包的问题,并且在新的版本里已经修复了,那就只要更新项目的包依赖就好了...吗?没这么简单.直接在package.jsondependencies`里设定Source-map的版本,只会更新第一层依赖的版本,例如使用npm list source-map查看 项目的依赖包信息,如下图:

项目的旧依赖版本
为了修改子依赖的版本,需要使用Override来进行更新. 如官方文档所示:
json { "overrides": { "[email protected]": { "foo": "1.0.0" } } }
通过嵌套可以指定覆盖特定包,甚至指定版本的包的子依赖包版本,并且对下面无限深的嵌套都会默认覆盖(子依赖,孙子依赖等),适合这种安全更新发版,但是一些依赖直接写死了版本号导致不能应用新补丁的情况.
以本项目为例,在overrides的第一层规定source-map的版本为最新,就能使得所有依赖了这个包的包都将版本更新到0.7.4,而无视各自包配置中规定的依赖版本. image.png