如何讓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