當(dāng)學(xué)習(xí)成為了習(xí)慣同木,知識(shí)也就變成了常識(shí)。感謝各位的 點(diǎn)贊豪墅、收藏和評(píng)論泉手。
新視頻和文章會(huì)第一時(shí)間在微信公眾號(hào)發(fā)送黔寇,歡迎關(guān)注:李永寧lyn
文章已收錄到 github偶器,歡迎 Watch 和 Star。
封面
簡(jiǎn)介
從問(wèn)題定位開(kāi)始缝裤,到給框架(uni-app)提 issue屏轰、出解決方案(PR),再到最后的思考憋飞,詳細(xì)記錄了整個(gè)過(guò)程霎苗。
前序
當(dāng)你在業(yè)務(wù)中不幸踩了開(kāi)源框架的某些坑,這是你的不幸榛做,但這同時(shí)也是你的幸運(yùn)唁盏,因?yàn)檫@是你給自己簡(jiǎn)歷中增加亮點(diǎn)的絕佳機(jī)會(huì)。
而給開(kāi)源社區(qū)貢獻(xiàn) PR 是你證明自己技術(shù)側(cè)擁有 P7 實(shí)力的絕佳方式检眯,P7 的評(píng)判標(biāo)準(zhǔn)無(wú)非是業(yè)務(wù)和技術(shù)厘擂,業(yè)務(wù)上有收益,技術(shù)上有深度和廣度(別人有的你能做的更好锰瘸,別人沒(méi)有的你能有)刽严。
這次整個(gè)過(guò)程歷時(shí) 3-4 天,在此之前我也沒(méi)讀過(guò) uni-app 和 ucharts 的源碼避凝,所以這里把整個(gè)過(guò)程分享出來(lái)也是給大家一個(gè)解決問(wèn)題的思路舞萄。
環(huán)境
- uni-app cli 版本 3.0.0-alpha-3030820220114011
- hbuilder 版本 3.3.8.20220114-alpha
- ucharts 版本 uni-modules 2.3.7-20220122
現(xiàn)象
uni-app、vue3 + ucharts 繪制圖表管削,開(kāi)發(fā)環(huán)境正常倒脓,但是打包上線(xiàn)后,H5 無(wú)法繪制圖表含思,也不報(bào)任何錯(cuò)誤崎弃。
開(kāi)發(fā) | 線(xiàn)上 | |
---|---|---|
APP | 正常 | 正常 |
H5 | 正常 | 無(wú)法繪制 |
問(wèn)題定位
給 ucharts 的社區(qū)提 issue,經(jīng)過(guò)交流茸俭,維護(hù)者 “懷疑“ 是 uni-app 的 vue3 的 renderjs 有問(wèn)題吊履,但是他也給不了一個(gè)肯定的答復(fù),讓去 uni-app 的社區(qū)提 issue 而且示例中不能用 ucharts调鬓。個(gè)人對(duì)于該回答持懷疑態(tài)度艇炎,于是決定自己去定位問(wèn)題。
懷疑是 ucharts 的 bug
- ucharts 視圖部分的關(guān)鍵代碼
<view ...其它屬性 :prop="uchartsOpts" :change:prop="rdcharts.ucinit">
<canvas ...屬性 />
</view>
這里有一個(gè)知識(shí)點(diǎn)需要補(bǔ)充:當(dāng) prop 發(fā)生改變腾窝,change:prop 的回調(diào)會(huì)被調(diào)用缀踪,這是 uni-app 框架提供的能力居砖,但官方文檔沒(méi)有提及,從源碼中可以看到驴娃。
- 看了 ucharts 的源碼奏候,繪制圖表時(shí)的代碼執(zhí)行過(guò)程如下:
可是打包后的 H5 線(xiàn)上環(huán)境,當(dāng)執(zhí)行 this.uchartsOpts = newConfig
之后卻沒(méi)有觸發(fā) change:prop
事件唇敞,所以這看起來(lái)似乎是 uni-app 的 view 組件有問(wèn)題
感謝 ucharts 官方蔗草,在定位問(wèn)題過(guò)程中,和社區(qū)進(jìn)行交流后疆柔,ucharts 免費(fèi)贈(zèng)送了一個(gè)永久超級(jí)會(huì)員咒精,感謝 ?? ?? !!
view 組件的 prop 和 change:prop
提供如下示例:
<template>
<view>
<view :prop="counter" :change:prop="changeProp"></view>
<view>{{ msg }}</view>
</view>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";
const counter = ref(1)
const msg = ref('hello')
function changeProp() {
msg.value = 'hello' + counter.value
}
// @ts-ignore
let timer = null
onMounted(() => {
timer = setInterval(() => {
counter.value += 1
}, 1000)
})
onBeforeUnmount(() => {
// @ts-ignore
clearInterval(timer)
})
</script>
<style>
</style>
H5 開(kāi)發(fā)環(huán)境 | H5 打包后 | |
---|---|---|
vue2 | 正常 | 正常 |
vue3 | 正常 | change:prop 未執(zhí)行 |
因?yàn)殚_(kāi)發(fā)環(huán)境沒(méi)有問(wèn)題,所以在開(kāi)發(fā)環(huán)境中通過(guò)在 change:prop 方法中打斷點(diǎn)旷档,查看調(diào)用棧模叙,找到觸發(fā) change:prop 回調(diào)的方法,再一步步往上看鞋屈,終于發(fā)現(xiàn)了 uni-app 重寫(xiě)渲染器(render 函數(shù))的地方范咨,在 @dcloudio/uni-h5-vue/dist/vue.runtime.esm.js 中。
通過(guò)閱讀 uni-app 的源碼厂庇,得到如下內(nèi)容:
響應(yīng)式數(shù)據(jù)發(fā)生變化渠啊,觸發(fā) vue 的響應(yīng)式更新。比如你的響應(yīng)式數(shù)據(jù)作為元素的 prop 屬性傳遞宋列,則在 patch 階段會(huì)觸發(fā) patchProps 方法昭抒, 觸發(fā)該方法后,方法內(nèi)判斷新老 props 是否發(fā)生改變炼杖,如果變了灭返,則遍歷新的 props 對(duì)象,將其中的每個(gè)屬性坤邪、值和老的對(duì)比熙含,如果不相等 或者 props 的 key 為 change:xx 則直接調(diào)用 patchProp 方法,如果 __UNI_FEATURE_WXS__
為真并且 props 的 key 為 change: 開(kāi)頭艇纺,則調(diào)用 patchWxs怎静,patchWxs 方法最終會(huì)通過(guò) nextTick 調(diào)用 change:prop 的回調(diào)方法。
以下為上述執(zhí)行過(guò)程的流程圖:
最終定位到問(wèn)題就出在 __UNI_FEATURE_WXS__
上,發(fā)現(xiàn)開(kāi)發(fā)環(huán)境中它是 true,但是打包后就變成了 false鉴嗤。
__UNI_FEATURE_WXS__
__UNI_FEATURE_WXS__
是一個(gè)全局變量,所以肯定是通過(guò) vite 的 define 選項(xiàng)進(jìn)行設(shè)置的夜牡。
于是接下來(lái)的目的就是需要找到 __UNI_FEATURE_WXS__
是在什么地方進(jìn)行設(shè)置的÷虑可以全局搜該變量塘装,然后找到在 @dcloudio/uni-cli-shared
包中找到一個(gè)叫 initFeatures
的方法急迂,該方法中聲明了一個(gè) features
對(duì)象:
const {
wx,
wxs,
// ...其它變量
} = extend(
initManifestFeature(options),
// ... 其它方法
)
const features = {
// vue
__VUE_OPTIONS_API__: vueOptionsApi, // enable/disable Options API support, default: true
__VUE_PROD_DEVTOOLS__: vueProdDevTools, // enable/disable devtools support in production, default: false
// uni
__UNI_FEATURE_WX__: wx, // 是否啟用小程序的組件實(shí)例 API,如:selectComponent 等(uni-core/src/service/plugin/appConfig)
__UNI_FEATURE_WXS__: wxs, // 是否啟用 wxs 支持蹦肴,如:getComponentDescriptor 等(uni-core/src/view/plugin/appConfig)
// ... 其它屬性
}
看了該對(duì)象的設(shè)置沒(méi)什么問(wèn)題僚碎,wxs
在開(kāi)發(fā)和生產(chǎn)環(huán)境下都是 true。那接下來(lái)就需要找到誰(shuí)調(diào)用了 initFeatures 方法阴幌,而且可能調(diào)用完了以后通過(guò)判斷當(dāng)前命令勺阐,比如:執(zhí)行 build 時(shí),將 __UNI_FEATURE_WXS__
設(shè)置為了 false裂七。
剛開(kāi)始想正向推導(dǎo)皆看。vite-plugin-uni 是 uni-app 提供給 vite 的一個(gè)插件框架,uni-app 中的 vite 配置都來(lái)自于這里背零。
插件當(dāng)中的 uni 插件提供了 config 選項(xiàng),config 選項(xiàng)的值是調(diào)用 createConfig 方法返回的函數(shù)无埃,該函數(shù)會(huì)返回一個(gè)對(duì)象徙瓶,該對(duì)象會(huì)和 vite 的配置做深度合并;該對(duì)象有 define 選項(xiàng)嫉称,該選項(xiàng)的值為 createDefine 函數(shù)的返回值侦镇,該返回值是一個(gè)對(duì)象,其中調(diào)用了 initDefine织阅,再往下看發(fā)現(xiàn)不對(duì)壳繁,然后路 走死了。
發(fā)現(xiàn)上面正向推導(dǎo)的方式走不通以后荔棉,于是開(kāi)始反向推導(dǎo)闹炉,即全局搜索,都有哪些地方調(diào)用了 initFeatures润樱,然后一步步的往下推渣触,得到如下正確的流程圖:
經(jīng)過(guò)最終的調(diào)試,發(fā)現(xiàn) 啟動(dòng)開(kāi)發(fā)環(huán)境和打包時(shí)最終的調(diào)用路徑是:uniH5Plugin -> createConfig -> configDefine -> initFeatures壹若。
而最終的問(wèn)題也就是出在了 initFeatures 方法調(diào)用的 initManifestFeature 方法中嗅钻。
答案
最終定位到出問(wèn)題的地方在 @dcloudio/uni-cli-shared/src/vite/features.ts
文件的 initManifestFeature
方法中。有如下對(duì)比:
- github 倉(cāng)庫(kù)的最新代碼店展,版本號(hào):3.0.0-alpha-3030820220114011
if (command === 'build') {
// TODO 需要預(yù)編譯一遍养篓?
// features.wxs = false
// features.longpress = false
}
- 已發(fā)版的代碼,最高版本號(hào):3.0.0-alpha-3031120220208001
if (command === 'build') {
// TODO 需要預(yù)編譯一遍赂蕴?
features.wxs = false;
features.longpress = false;
}
已發(fā)版的版本居然高于倉(cāng)庫(kù)內(nèi)的最新版本號(hào)柳弄。查看 npm 上的發(fā)布版本信息:
發(fā)現(xiàn)版本號(hào)發(fā)生了回退。這幾次回退的版本號(hào)都是不符合規(guī)范的版本號(hào)睡腿,而且其中可能攜帶了 bug语御,比如上面提到的最高版本峻贮。
發(fā)版出現(xiàn)版本號(hào)不符合規(guī)范的情況是由于項(xiàng)目還沒(méi)有一個(gè)規(guī)范的發(fā)版流程導(dǎo)致的,但是已經(jīng)是 alpha 版本了应闯,這種低級(jí)錯(cuò)誤還是應(yīng)該避免的纤控。
更致命的操作是,回退版本號(hào)碉纺。uni-app 目前每次升級(jí)都是升級(jí)的最小版本號(hào)后面的數(shù)值船万,而業(yè)務(wù)項(xiàng)目的 package.json 都是 "@dcloudio/uni-app": "^xxx"
的形式,這就意味著骨田,你每次重新裝包(比如自動(dòng)化部署時(shí))或者升級(jí)包時(shí)耿导,都會(huì)更新到這個(gè)存在 bug 的高版本,這就會(huì)導(dǎo)致線(xiàn)上系統(tǒng)報(bào) bug态贤。
解決方案
所以這里正確的處理方式是重新發(fā)一個(gè)更高版本的包舱呻,而不是回退版本。因?yàn)樵摬僮鲿?huì)導(dǎo)致用戶(hù)線(xiàn)上的系統(tǒng)出 bug悠汽,即以下代碼無(wú)法正常執(zhí)行:
<view :prop="msg" :change:prop="cb"></view>
當(dāng)正常情況下箱吕,當(dāng) msg 改變后,change:prop 的回調(diào)會(huì)執(zhí)行柿冲。但是這個(gè)攜帶 bug 的高版本包茬高,在打包時(shí)(npm run build)將 __UNI_FEATURE_WXS__
設(shè)置為了 false,導(dǎo)致 change:prop 的回調(diào)不會(huì)被調(diào)用假抄。
總結(jié)
代碼可以回退怎栽,但是版本號(hào)不要回退,應(yīng)該基于當(dāng)前穩(wěn)定版本宿饱,重新發(fā)一版版本號(hào)更高的版本熏瞄。
于是就給官方提了 issue 和 解決方案。
結(jié)果
官方已采納該解決方案刑棵,基于當(dāng)前穩(wěn)定版重新發(fā)布一版版本號(hào)更高的版本巴刻。
思考
針對(duì) uni-app 這種處于 alpha 版本的框架,項(xiàng)目?jī)?nèi)部也確實(shí)不應(yīng)該繼續(xù)使用 ^ 符號(hào)蛉签,還是應(yīng)該將版本號(hào)寫(xiě)死為最新的 tag 版本胡陪,因?yàn)榭偢S alpha 的最新版,確實(shí)可能會(huì)踩坑碍舍。
鏈接
感謝各位的:點(diǎn)贊柠座、收藏和評(píng)論,我們下期見(jiàn)片橡。
當(dāng)學(xué)習(xí)成為了習(xí)慣妈经,知識(shí)也就變成了常識(shí)。感謝各位的 點(diǎn)贊、收藏和評(píng)論吹泡。
新視頻和文章會(huì)第一時(shí)間在微信公眾號(hào)發(fā)送骤星,歡迎關(guān)注:李永寧lyn
文章已收錄到 github,歡迎 Watch 和 Star爆哑。