如何讓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: https://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或者{}(根據運行環節的不同,參見[這篇文章](https://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