微信小程序也要強(qiáng)行熱更代碼,鵝廠不服你來肛我呀
時(shí)間:2022-08-07 14:18:01 | 來源:網(wǎng)站運(yùn)營
時(shí)間:2022-08-07 14:18:01 來源:網(wǎng)站運(yùn)營
時(shí)過境遷,這篇文已經(jīng)失效了,2022年6月23號的時(shí)候,微信團(tuán)隊(duì)發(fā)了通知將禁止小程序使用 JavaScript 解釋來動態(tài)更新代碼。然而時(shí)代真的變了嗎?不,沒變!
前言
微信為了限制小程序的熱更越過審核,所以限制了 eval / new Function 等方式動態(tài)執(zhí)行代碼。其實(shí)說實(shí)話,限制熱更本來能理解,但是你這審核速度如此龜速,讓版本迭代和bug修復(fù)都異常艱難。尤其是今年春節(jié)前夕的時(shí)候,我們小程序一審就是5天!但是隔壁某大廠的告訴我們他們小程序?qū)徍瞬庞昧?個(gè)小時(shí),所以也不難猜測我們這些創(chuàng)業(yè)小廠永遠(yuǎn)都是被瘋狂插隊(duì)的。
好,鵝廠既然你不仁,那就特么我就別怪我不義了。不能用 eval / new Function 就不能動態(tài)執(zhí)行代碼了?不存在的!這種限制聊勝于無,不過一張紙而已,有本事就關(guān)了 ajax 呀。
把最近寫的文章也更新在這里吧,方便大家學(xué)習(xí)正確的姿勢:
首先用 JavaScript 寫一個(gè) JavaScript 解釋器
熱更的第一步,就是用 JavaScript 寫一個(gè) JavaScript 解釋器。對于很多小伙伴來說,寫一個(gè)JavaScript 解釋器這聽起來天方夜譚,但事實(shí)上卻是非常簡單,因?yàn)槭褂?JavaScript 實(shí)現(xiàn) JavaScript 解釋器,所以語義幾乎可以完全復(fù)用。
我現(xiàn)在列一下具體實(shí)現(xiàn)步驟:
- 解析 JavaScript 代碼字符串,得到 JavaScript 代碼的抽象語法樹(AST)。解析這個(gè)步驟不需要自己做,有非常多優(yōu)秀的現(xiàn)成的庫,比如 acornjs/acorn 。結(jié)果會解析成標(biāo)準(zhǔn)的 ESTree。
- 因?yàn)?JavaScript 的語法樹是有標(biāo)準(zhǔn)格式的 estree/estree ,所以只需要對照這個(gè)標(biāo)準(zhǔn)格式進(jìn)行實(shí)現(xiàn)語法樹的求值,只要保證 JavaScript 一樣的語義,就和 eval / new Function 等效果差不多了。
需要注意的細(xì)節(jié):
- 作用域。比如 var 聲明的變量是函數(shù)作用域,const / let 是詞法作用域,這些要注意區(qū)分。
- || 和 && 運(yùn)算符。這兩個(gè)運(yùn)算符有短路的效果,所以不能和其他運(yùn)算符一樣先求兩邊的值。
- 函數(shù)閉包。就像上述所說的,用 JavaScript 實(shí)現(xiàn) JavaScript 解釋器幾乎可以完全復(fù)用 JavaScript 語義,在這里就體現(xiàn)的淋漓精致了。解釋器函數(shù)閉包就可以用語言本身的閉包來進(jìn)行實(shí)現(xiàn),非常輕松愉快。
- 注入標(biāo)準(zhǔn)庫。我們做這個(gè)解釋器的目的不是為了做沙盒,所以沒有對環(huán)境進(jìn)行隔離,但是即便如此,我們還是需要把 JavaScript 的標(biāo)準(zhǔn)庫注入進(jìn)去。標(biāo)準(zhǔn)庫可以在 JavaScript 標(biāo)準(zhǔn)庫 找到。
- new 操作符。這個(gè)問題我研究了很久,最終找到了還算是有效的方案,在下面示例代碼里面有。
- 打斷控制流的操作。break / return / continue 這類會打斷控制流的操作,處理起來需要小心。
示例代碼:bramblex/jsjs
效果如下:
效果圖然后設(shè)計(jì)完整熱更方案
page / component 這種東西我暫時(shí)還沒研究過能怎么更新,畢竟我對小程序也不是非常熟悉。我們就直接討論如何熱更程序代碼吧。
首先既然我們要熱更,我們就必須思考幾個(gè)問題:
- 從哪里更新代碼?毫無疑問,我們需要一個(gè)用來更新的服務(wù)器。
- 更新下來的代碼放在那里?目前來看,更新下來的放在小程序里面的 Storage 里面就很好。
- 什么時(shí)候知道代碼需要更新了?要解決這個(gè)問題,那需要我們要在遠(yuǎn)端服務(wù)器和本地記錄代碼版本,對比版本來確定是否需要更新。
- 代碼什么時(shí)候時(shí)候執(zhí)行?我建議是把熱更的代碼當(dāng)成一個(gè)單獨(dú)的模塊,在 App 創(chuàng)建之前就可以執(zhí)行了
接下來,解決問題,設(shè)計(jì)具體熱更方案:
- 代碼從服務(wù)器更新,有兩個(gè) api 。/update/version 返回 v0.0.1 這樣的版本號,/update/code 返回代碼字符串。
- 本地的 Storage 有 version 和 code 兩個(gè) key 進(jìn)行存儲對應(yīng)的本地代碼和版本。
- 當(dāng)打開小程序的時(shí)候,獲取比較本地版本和遠(yuǎn)程版本,如果版本一致怎不管,如果版本不一致則更新本地代碼。
- 執(zhí)行熱更的代碼,熱更部分代碼可以用 module.exports 或者你喜歡的變量形勢導(dǎo)出暴露的接口,保存在一個(gè)模塊里面。
- 然后各個(gè) page 調(diào)用熱更部分的模塊代碼,嵌進(jìn)業(yè)務(wù)代碼里面。
具體方案的示例代碼我就不給了,因?yàn)槲乙矝]有……畢竟,之后如果需要有什么項(xiàng)目需要熱更小程序的時(shí)候,再寫這部分代碼,反正已經(jīng)設(shè)計(jì)好了。
最后
這個(gè)方案還有非常多能夠改進(jìn)的地方,比如用實(shí)現(xiàn) IR 中間碼來進(jìn)行傳輸和使用,提升性能和內(nèi)存占用等等。不過這是后話了,之后有時(shí)間再做改善吧。
抱歉,懂編譯原理真的可以為所欲為的