網(wǎng)站怎么做評估北京seo優(yōu)化排名
需求
最近遇到個小程序異步解碼的需求,采用了WebAssembly,涉及大量的計算。由于小程序的雙線程模型只有一個線程處理數(shù)據(jù),因此智能尋求其它的解決方案。查看小程序的文檔,發(fā)現(xiàn)小程序還提供一個異步線程的Worker方案,可以并行的。于是嘗試采用Worker來進行異步運算,看了下文檔,貌似只能有一個Worker異步進行,但是聊勝于無,能多一個線程并行計算,頁面邏輯不會卡住就已經(jīng)很不錯了。
由于本人采用uniapp來進行小程序開發(fā),由于引入了uniapp編譯,導致整個開發(fā)過程更加復雜,本文記錄了本人采用uni-best框架使用Worker過程遇到的一排深坑以及爬坑方案
小廣告
uni-best不愧為2024年最佳的uni-app開發(fā)框架,uniapp+vue3+ts+unocss+uni-helper,typescript語言,體驗極致的開發(fā)效率。本次項目就是在unibest生成的項目里進行uniapp開發(fā)
unibest最好用的 uniapp 開發(fā)模板https://codercup.github.io/unibest-docs/
整合過程
下面按照官方的整合過程走一遍
創(chuàng)建目錄
在src項目下創(chuàng)建workers目錄,并建立index.js。這里是坑A,注意小程序Worker的引入必須顯式的指定為.js,因此即使ts能夠自動的編譯為js,但是由于書寫的原因。index文件必須為javascript而不是typeScript,但是index.js再引入的文件,是可以使用typescript格式的
引入文件
在頁面采用一個按鈕,點擊開始進行異步的計算。按鈕點擊的代碼如下:
在App.vue onShow里初始化
onShow(() => {const createNewWorker = () => wx.createWorker('workers/index.js', { useExperimentalWorker: true }) // 開啟編碼多線程let worker = createNewWorker()worker.onProcessKilled(() => {// 重新創(chuàng)建一個workerworker = createNewWorker()})
})
按照微信的文檔,在某些情況下異步線程會被系統(tǒng)殺死。因此在這里采用了開啟useExperimentalWorker?;顧C制
編寫調(diào)用
下面按照微信官方的說明結(jié)合本人的項目開始編寫
異步線程接收事件
下一步開始編寫index.js,開啟異步線程接口
worker.onMessage((obj) => {if (obj.event === 'add') {worker.postMessage({ event: 'addResult', data: obj.data.a + obj.data.b })}
})
解釋下為什么這樣寫:
因為worker的調(diào)用是采用統(tǒng)一的調(diào)用接口,因此需要設計自己的消息格式,本人的消息格式設計如下
export interface IWorkderMessage {event: stringparams: any
}
event承載不同的消息給Worker,這樣Worker可以做不同的事情。這里的例子只使用一個簡單的調(diào)用,把消息參數(shù)里的a和b在異步線程相加,然后返回給主線程相加的結(jié)果
主線程發(fā)起事件
主線程的調(diào)用,在本人的結(jié)構(gòu)里是采用mitt全局消息模型的,這樣在統(tǒng)一的入口注冊后。任何單元代碼的任何地方都可以隨時對異步線程發(fā)起調(diào)用。
utils.on(Global.CC_WORKER_MESSAGE, (data: IWorkderMessage) => {worker.postMessage(data)})
頁面發(fā)起異步調(diào)用
function doWorker() {utils.emit(Global.CC_WORKER_MESSAGE, { event: 'add', data: { a: 2, b: 3 } })
}
按微信官方的說法,在worker.onMessage里打印到console,理應看到輸出(實際不是)。姑且先不管運行的結(jié)果,我們先按微信官方文檔說明把代碼寫完。
主線程接收異步線程結(jié)果
主線程同樣是采用worker.onMessage來接收異步線程的返回結(jié)果。我們加入到startWorker方法里,寫成這樣
onShow(() => {const createNewWorker = () => wx.createWorker('workers/index.js', { useExperimentalWorker: true }) // 開啟編碼多線程let worker = createNewWorker()worker.onProcessKilled(() => {// 重新創(chuàng)建一個workerworker = createNewWorker()})worker.onMessage((obj: Record<string, any>) => {// 異步線程全局消息轉(zhuǎn)發(fā)utils.emit(obj.event, obj.data)})})
這里的utils.emit是我引入mitt后的全局消息模式,這樣可以把返回的消息通過全局消息模型轉(zhuǎn)發(fā)到對應的頁面里
說明下這里為什么obj類型用Record<string,any>而不是IWorkderMessage,因為在小程序定義的d.ts里,已經(jīng)把類型定義為Record,因此只能這樣寫
然后在對應界面寫個全局的事件接收,這里僅打印下接收結(jié)果
utils.on('addResult', (c) => {console.log(`addResult is ${c}`)})
坑來了
坑B
[worker] Uncaught Error: module 'workers/index.js' is not defined, require args is 'workers/index.js'
看到這里本人起初也是一頭霧水的,啥叫index.js沒定義,需要index.js。經(jīng)過了一圈排查,才發(fā)現(xiàn)。我的編譯后的dist\dev\mp-weixin目錄里,沒有workers目錄!心態(tài)炸了,這叫什么錯誤,其它的文件都在,為毛單對workers過不去?
時間一分一秒過去,經(jīng)過數(shù)小時冷靜后。突然想到一個問題,vue3默認開啟了Tree Shaking來優(yōu)化代碼,是不因為編譯優(yōu)化不認識worker機制,把從workders入口開始的整個代碼鏈給弄丟了呢?按騰訊文檔說,worker代碼獨立運行,會自動從createWorker開始運行,實時不是TreeShaking不認這一套,沒代碼調(diào)用的模塊全部掃出家門了呢。之前require引入代碼也不認,TreeShaking也給弄丟了,估計也是一個德行。
想完說干就干,修改下workers/index.js,做個簡單的默認導出
worker.onMessage((obj) => {if (obj.event === 'add') {worker.postMessage({ event: 'addResult', data: obj.data.a + obj.data.b })}
})export default 'workers'
然后在App.vue導入,啥其它都不干,就打印下,這下編譯器應該認為該模塊是有用的吧
import workers from '@/workers'
console.log(workers)
然后開啟調(diào)試,內(nèi)牛滿面,workers目錄出現(xiàn)了,遺憾的是,繼續(xù)出現(xiàn)錯誤了
坑C
估計很多人爬到這里,就會爬不動了。小程序上還是顯示錯誤
app.js錯誤:
?Error: module 'workers/index.js' is not defined, require args is './workers/index.js'
看起來錯誤和前面的一樣,但是仔細看又不一樣。前面的是worker報錯,是在啟動worker的時候找不到模塊,這里是app.js錯誤,而且仔細看是./workers/index.js找不到。那這個'workers/index.js' is not defined又是哪門子毛病呢?
經(jīng)過數(shù)小時排查,發(fā)現(xiàn)編譯后的app.js有這樣一句代碼:
但是如果我修改為workers/index.js就直接編譯報錯了
在這個地方卡了數(shù)小時。各種方法試過,一氣之下想既然導入不對,干脆不要導入算了。于是把編譯后的app.js的c=require("./workers/index.js")直接修改為c="hahaha",然后直接導入小程序模擬器運行。竟然成功了!
也就是說,對于最終編譯的app.js,如果我把坑B產(chǎn)生的代碼在最終編譯結(jié)果去掉的話,代碼就可以正常運行了。TMD VUE,TMD編譯器優(yōu)化!!!
但是不能每次都這樣每編一次手動改一次呀,還不得把人累死,于是有了下一步
自動處理導入
既然是在編譯階段處理,那么我們應該是可以通過插件解決的,例如scss等插件都是可以對最終結(jié)果進行處理。于是想自己寫個插件,對于從沒寫過插件的我來說難度又上了一個等級,幸好有GPT幫助,在折磨一陣子GPT后,再參考下其他類似代碼。于是有了這個插件:
圖片里vite.config.ts里的代碼(頂部記得import fs from 'node:fs'):
process.env.UNI_PLATFORM === 'mp-weixin' && {name: 'fix-uni-app-workers',apply: 'build',async closeBundle() {const buildType = process.env.NODE_ENV === 'development' ? 'dev' : 'build'const filePath = path.resolve(__dirname, `./dist/${buildType}/mp-weixin/app.js`) // 由app.js引入,修復這個即可fs.readFile(filePath, 'utf8', (err, data) => {if (err) {console.error('Error reading file:', err)return}console.log(`patch ${filePath}`)const result = data.replace(/require\("\.\/workers\/[a-zA-z-_]+\.js"\)/g, '""')// 寫回文件fs.writeFile(filePath, result, 'utf8', (err) => {if (err) {console.error('Error writing file:', err)}})console.log('uniapp 小程序 worker 補丁完畢')})}}
解釋下這個插件干了啥。
它在判斷微信編譯時(留著以后H5可以用編譯開關(guān)寫頁面的Worker)開啟,對編譯目標目錄的app.js進行處理。因此你的引用代碼必須寫到app.js。即
import workers from '@/workers'
console.log(workers)
這個是寫在App.vue的,寫到其它文件別怪我沒提醒
然后對生成的文件做替換,把里面所有引入的js文件入口
=require("./workers/XXXXX.js")都替換成了="",這樣都是打印空字符串,不會報錯
對于workders里其它文件,也需要在app.js里通過寫console.log的 方式注冊,否則還是會出詭異的require args報錯,這個正則把所有workers里的引入都替換成了常量字符
這樣uniapp使用小程序的Workers就可以正常工作了🎉🎉🎉
按鈕調(diào)用:
function doWorker() {utils.emit(Global.CC_WORKER_MESSAGE, { event: 'add', data: { a: 2, b: 3 } })
}
日志打印:
功能已經(jīng)正常!!!