作者:vivo 互聯(lián)網(wǎng)前端團(tuán)隊(duì)-
Wan Anwen、Hu Feng、Feng Wei、Xie Tao
進(jìn)入互聯(lián)網(wǎng)“下半場(chǎng)”,靠“人海戰(zhàn)術(shù)”的研發(fā)模式已經(jīng)不再具備競(jìng)爭(zhēng)力,如何通過(guò)技術(shù)升級(jí)提升研發(fā)效能?前端通過(guò)Babel等編譯技術(shù)發(fā)展實(shí)現(xiàn)了工程化體系升級(jí),如何進(jìn)一步通過(guò)編譯技術(shù)賦能前端開(kāi)發(fā)?或許我們 wepy 到uniapp 編譯的轉(zhuǎn)換實(shí)踐,能給你帶來(lái)啟發(fā)。
一、背景
隨著小程序的出現(xiàn),借助微信的生態(tài)體系和海量用戶,使服務(wù)以更加便捷方式的觸達(dá)用戶需求?;诖吮尘?,團(tuán)隊(duì)很早布局智能導(dǎo)購(gòu)小程序(為 vivo 各個(gè)線下門(mén)店導(dǎo)購(gòu)提供服務(wù)的用戶運(yùn)營(yíng)工具)的開(kāi)發(fā)。
早期的小程序開(kāi)發(fā)工程體系還不夠健全,和現(xiàn)在的前端的工程體系相差較大,表現(xiàn)在對(duì)模塊化,組件化以及高級(jí)JavaScript 語(yǔ)法特性的支撐上。所以團(tuán)隊(duì)在做技術(shù)選型時(shí),希望克服原生小程序工程體系上的不足,經(jīng)過(guò)對(duì)比最后選擇了騰訊出品的 wepy 作為整體的開(kāi)發(fā)框架。
在項(xiàng)目的從0到1階段,wepy 確實(shí)幫助我們實(shí)現(xiàn)了快速的業(yè)務(wù)迭代,滿足線下門(mén)店導(dǎo)購(gòu)的需求。但隨著時(shí)間的推移,在技術(shù)上,社區(qū)逐步沉淀出以 uniapp 為代表的 Vue 棧體系和以 Taro 為代表的 React ??缍说捏w系,wepy 目前的社區(qū)活躍度比較低。另外隨著業(yè)務(wù)進(jìn)入穩(wěn)定階段,除少量的 wepy 小程序,H5 項(xiàng)目和新的小程序都是基于 Vue 和 uniapp 來(lái)構(gòu)建,團(tuán)隊(duì)也是希望統(tǒng)一技術(shù)棧,實(shí)現(xiàn)更好的跨端開(kāi)發(fā)能力,降低開(kāi)發(fā)和維護(hù)成本,提升研發(fā)效率。
二、思考
隨著團(tuán)隊(duì)決定將智能導(dǎo)購(gòu)小程序從 wepy 遷移到 uniapp 的架構(gòu)體系,我們就需要思考,如何進(jìn)行項(xiàng)目的平穩(wěn)的遷移,同時(shí)兼顧效率和質(zhì)量?通過(guò)對(duì)當(dāng)前的項(xiàng)目狀態(tài)和技術(shù)背景進(jìn)行分析,團(tuán)隊(duì)梳理出2個(gè)原則3種遷移思路。
2.1 漸進(jìn)式遷移
核心出發(fā)點(diǎn),保證項(xiàng)目的平穩(wěn)過(guò)渡,給團(tuán)隊(duì)更多的時(shí)間,在迭代中逐步的進(jìn)行架構(gòu)遷移。希望以此來(lái)降低遷移中的風(fēng)險(xiǎn)和不可控的點(diǎn)。基于此,我們思考兩個(gè)方案:
方案一 融合兩套架構(gòu)體系
在目前的項(xiàng)目中引入和 uniapp 的項(xiàng)目體系,一個(gè)項(xiàng)目融合了 wepy 和 uniapp 的代碼工程化管理,逐步的將 wepy 的代碼改成 uniapp 的代碼,待遷移完成刪除 wepy 的目錄。這種方案實(shí)現(xiàn)起來(lái)不是很復(fù)雜,但是缺點(diǎn)是管理起來(lái)比較復(fù)雜,兩套工程化管理機(jī)制,底層的編譯機(jī)制,各種入口的配置文件等,管理起來(lái)比較麻煩。另外團(tuán)隊(duì)每個(gè)人都需要消化 wepy 到 uniapp 的領(lǐng)域知識(shí)遷移,不僅僅是項(xiàng)目的遷移也是知識(shí)體系的遷移。
方案二 設(shè)計(jì) wepy-webpack-loader
以 uniapp 為工程體系基礎(chǔ),核心思路是將現(xiàn)有 wepy 代碼融入到 uniapp 的體系中來(lái)。我們都知道 uniapp 的底層依賴(lài)于 Vue 的 cli 的技術(shù)體系,最底層通過(guò) webpack 實(shí)現(xiàn)對(duì) Vue 單組件文件和其他資源文件的 bundle。
基于此,我們可以開(kāi)發(fā)一個(gè) wepy 的 webpack 的 loader,wepy-loader 類(lèi)似于 vue-loader 的能力,通過(guò)該 loader 對(duì) wepy 文件進(jìn)行編譯打包,然后最終輸出小程序代碼。想法很簡(jiǎn)單,但我們想要實(shí)現(xiàn) wepy-loader工作量還是比較大的,需要對(duì) wepy 的底層編譯器進(jìn)一步進(jìn)行分析拆解,分析 wepy 的依賴(lài)關(guān)系,區(qū)分是組件編譯還是 page 編譯等,且 wepy 底層編譯器的代碼比較復(fù)雜,實(shí)現(xiàn)成本較高。
2.2 整體性遷移
構(gòu)建一個(gè)編譯器實(shí)現(xiàn) wepy 到 uniapp 的自動(dòng)代碼轉(zhuǎn)換
通過(guò)對(duì) wepy 和 uniapp 整體技術(shù)方案的梳理,加深了對(duì)兩套架構(gòu)差異性的認(rèn)知和理解,尤其 wepy 上層語(yǔ)法和 Vue 的組件開(kāi)發(fā)的代碼上的差異性。基于團(tuán)隊(duì)對(duì)編譯的認(rèn)知,我們認(rèn)為借助 babel 等成熟編譯技術(shù)是有能力實(shí)現(xiàn)這個(gè)轉(zhuǎn)換的過(guò)程,另外,通過(guò)編譯技術(shù)會(huì)極大的提升整體的遷移的效率。
2.3 方案對(duì)比
通過(guò)團(tuán)隊(duì)對(duì)方案的深入討論和技術(shù)預(yù)研,最終大家達(dá)成一致使用編譯轉(zhuǎn)換的方式(方案三)來(lái)進(jìn)行本次的技術(shù)升級(jí)。最終,通過(guò)實(shí)現(xiàn) wepy 到 uniapp 的編譯轉(zhuǎn)換器,使原本 25人/天的工作量,6s 完成。
如下動(dòng)圖所示:
三、架構(gòu)設(shè)計(jì)
3.1 wepy 和 uniapp 單文件組件轉(zhuǎn)換
通過(guò)對(duì) wepy 和 uniapp 的學(xué)習(xí),充分了解兩者之間的差異性和相識(shí)點(diǎn)。wepy 的文件設(shè)計(jì)和 Vue 的單文件非常的相似,包含 template 和 script 和 style 的三部分組成。
如下圖所示,
所以我們將文件拆解為 script,template,style 樣式三個(gè)部分,通過(guò) transpiler 分別轉(zhuǎn)換。同時(shí)這個(gè)過(guò)程主要是對(duì) script 和 template 進(jìn)行轉(zhuǎn)換,樣式和 Vue 可以保持一致性最終借助 Vue 進(jìn)行轉(zhuǎn)換即可。
同時(shí) wepy 還有自己的 runtime運(yùn)行時(shí)的依賴(lài),為了確保項(xiàng)目對(duì) wepy 做到最小化的依賴(lài),方便后續(xù)完全和 wepy 的依賴(lài)進(jìn)行完全解耦,我們抽取了一個(gè) wepy-adapter 模塊,將原先對(duì)于 wepy 的依賴(lài)轉(zhuǎn)換為對(duì)wepy-adapter 的依賴(lài)。
整體轉(zhuǎn)換設(shè)計(jì),如下圖所示:
3.2 編譯器流水線構(gòu)建
如上圖所示,整個(gè)編譯過(guò)程就是一條流水線的架構(gòu)設(shè)計(jì),在每個(gè)階段完成不同的任務(wù)。主要流程如下:
1.項(xiàng)目資源分析
不同的項(xiàng)目依賴(lài)資源不同的處理流程,掃描項(xiàng)目中的源碼和資源文件進(jìn)行分類(lèi),等待后續(xù)的不同的流水線處理。
靜態(tài)資源文件(圖片,樣式文件等)不需要經(jīng)過(guò)當(dāng)中流水線的處理,直達(dá)目標(biāo) uniapp 項(xiàng)目的對(duì)應(yīng)的目錄。
2. AST抽象語(yǔ)法樹(shù)轉(zhuǎn)換
針對(duì) wepy 的源文件(app,page,component等)對(duì) script,template 等部分,通過(guò) parse 轉(zhuǎn)換成相對(duì)應(yīng)的AST抽象語(yǔ)法樹(shù),后續(xù)的代碼轉(zhuǎn)換都是基于對(duì)抽象語(yǔ)法樹(shù)的結(jié)構(gòu)改進(jìn)。
3. 代碼轉(zhuǎn)換實(shí)現(xiàn) - Transform code
根據(jù) wepy 和 uniapp 的 Vue 的代碼實(shí)現(xiàn)上的差異,通過(guò)對(duì)ast進(jìn)行轉(zhuǎn)換實(shí)現(xiàn)代碼的轉(zhuǎn)換。
4. 代碼生成 - code emitter
根據(jù)步驟三轉(zhuǎn)換之后最終的ast,進(jìn)行對(duì)應(yīng)的代碼生成。
四、項(xiàng)目搭建
整體項(xiàng)目結(jié)構(gòu)如下圖所示:
4.1 單倉(cāng)庫(kù)的管理模式
使用 lerna 進(jìn)行單倉(cāng)庫(kù)的模塊化管理,方便進(jìn)行模塊的拆分和本地模塊之間依賴(lài)引用。另外單倉(cāng)庫(kù)的好處在于,和項(xiàng)目相關(guān)的信息都可以在一個(gè)倉(cāng)庫(kù)中沉淀下來(lái),如文檔,demo,issue 等。不過(guò)隨著 lerna 社區(qū)不再進(jìn)行維護(hù),后續(xù)會(huì)將 lerna 遷移到 pnpm 的 workspace 的方案進(jìn)行管理。
4.2 核心模塊
wepy-adapter - wepy運(yùn)行期以來(lái)的最小化的polyfill
wepy-chameleon-cli - 命令行工具模塊
wepy-chameleon-transpiler - 核心的編譯器模塊,按照one feature,one module方式組織
4.3 自動(dòng)化任務(wù)構(gòu)建等
Makefile - *nix世界的標(biāo)準(zhǔn)方式
4.4 scripts 自動(dòng)化管理
shipit.ts 模塊的自動(dòng)發(fā)布等自動(dòng)化能力
4.5 單元測(cè)試
采用Jest作為基礎(chǔ)的測(cè)試框架,使用typescript來(lái)作為測(cè)試用例的編寫(xiě)。
使用@swc/jest作為ts的轉(zhuǎn)換器,提升ts的編譯速度。
現(xiàn)在社區(qū)的vitest直接提供了對(duì)ts的集成,借助vite帶來(lái)更快的速度,計(jì)劃遷移中。
五、核心設(shè)計(jì)實(shí)現(xiàn)
5.1 wepy template 模版轉(zhuǎn)換
5.1.1差異性梳理
下面我們可以先來(lái)大致看一下wepy的模板語(yǔ)法和uniapp的模板語(yǔ)法的區(qū)別。
圖:wepy模板和uni-app模板
從上圖可以看出,wepy模板使用了原生微信小程序的wxml語(yǔ)法,并且在采用類(lèi)似Vue的組件引入機(jī)制的同時(shí),保留了wxml< import/ >、< include/ >標(biāo)簽的能力。同時(shí)為了和wxml中循環(huán)渲染dom節(jié)點(diǎn)的語(yǔ)法做區(qū)別,引入了新的< Repeat/ >標(biāo)簽來(lái)渲染引入的子組件,而uni-app則是完全使用Vue風(fēng)格的語(yǔ)法來(lái)進(jìn)行開(kāi)發(fā)。
所以總結(jié)wepy和uni-app模板語(yǔ)法的主要區(qū)別有兩點(diǎn):
wepy使用了一些特定的標(biāo)簽用來(lái)導(dǎo)入或者復(fù)用其他wxml文件例如< import >和< include >。
wxml使用了xml命名空間的方式來(lái)定義模板指令,并且對(duì)指令值的處理更像是使用模板引擎對(duì)特定格式的變量進(jìn)行替換。
下表列舉一些兩者模板指令的對(duì)應(yīng)轉(zhuǎn)換關(guān)系。
此外,還有一些指令的細(xì)節(jié)需要處理,例如在wepy中wx:key="id"指令會(huì)自動(dòng)解析為wx:key="{{item.id}}",這里便不再贅述。
5.1.2 核心轉(zhuǎn)換設(shè)計(jì)
編譯器對(duì)template轉(zhuǎn)換主要就需要完成以下三個(gè)步驟:
處理wepy引入的特殊的標(biāo)簽例如。
將wxml中使用的指令、特殊標(biāo)簽等轉(zhuǎn)換為Vue模板的語(yǔ)法。
收集引入的組件信息傳遞給下游的wepy-page-transform模塊。
wepy特殊標(biāo)簽轉(zhuǎn)換
首先我們會(huì)處理wepy模板中的特殊標(biāo)簽< import/ >、< include/ >,主要是將wxml的文件引入模式轉(zhuǎn)換成Vue模板的組件引入模式,同時(shí)還需要收集引入的wxml的文件地址和展示的模板名稱(chēng)。由于< include/ >可以引入wxml文件中除了< template/ >和< wxs/ >的所有代碼,為了保證轉(zhuǎn)換后組件的復(fù)用性,我們將引入的xx.wxml文件拆成了xx.vue和xx-incl.vue兩個(gè)文件,使用< import/ >標(biāo)簽的會(huì)導(dǎo)入xx.vue,而使用< include/ >標(biāo)簽的會(huì)導(dǎo)入xx-incl.vue,轉(zhuǎn)換import的核心代碼實(shí)現(xiàn)如下:
transformImport() { // 獲取所有import標(biāo)簽 const imports = this.$('import') for (let i = 0; i < imports.length; i++) { const node = imports.eq(i) if (!node.is('import')) return const importPath = node.attr('src') // 收集引入的路徑信息 this.importPath.push(importPath) // 將文件名統(tǒng)一轉(zhuǎn)換成短橫線風(fēng)格 let compName = TransformTemplate.toLine( path.basename(importPath, path.extname(importPath)) ) let template = node.next('template') while (template.is('template')) { const next = template.next('template') if (template.attr('is')) { const children = template.children() // 生成新的組件標(biāo)簽例如 //// => const comp = this.$(`<${compName} />`) .attr(template.attr()) .append(children) comp.attr(TransformTemplate.toLine(this.compName), comp.attr('is')) comp.removeAttr('is') // 將當(dāng)前標(biāo)簽替換為新生成的組件標(biāo)簽 template.replaceWith(comp) } template = next } node.remove() } }
具體的WXML文件拆分方案請(qǐng)看WXML轉(zhuǎn)換部分。
wepy 屬性轉(zhuǎn)換
上文中已經(jīng)介紹了,wepy模板中的屬性使用了命名空間+模板字符串風(fēng)格的動(dòng)態(tài)屬性,我們需要將他們轉(zhuǎn)換成Vue風(fēng)格的屬性。轉(zhuǎn)換需要操作模板中的節(jié)點(diǎn)及其屬性,這里我們使用了cheerio, 快速、靈活、類(lèi)jQuery核心實(shí)現(xiàn),可以利用jQuery的語(yǔ)法非常方便的對(duì)模板字符串進(jìn)行處理。
上述流程中一個(gè)分支中的轉(zhuǎn)換函數(shù)會(huì)處理相應(yīng)的wepy屬性,以保證后續(xù)可以很方便的對(duì)轉(zhuǎn)換模塊進(jìn)行完善和修改。由于屬性名稱(chēng)轉(zhuǎn)換只是簡(jiǎn)單的做一下相應(yīng)的映射,我們重點(diǎn)分析一下動(dòng)態(tài)屬性值的轉(zhuǎn)換過(guò)程。
WXML中使用雙中括號(hào)來(lái)標(biāo)記動(dòng)態(tài)屬性中的變量及WXS表達(dá)式,并且如果變量是WXS對(duì)象的話還可以省略對(duì)象的大括號(hào)例如
< view wx:for="{{list}}" > {{item}} < /view >、< template is="objectCombine" data="{{for: a, bar: b}}" >< /template >
所以當(dāng)我們?nèi)〉诫p中括號(hào)中的值時(shí)會(huì)有以下兩種情況:
① 得到WXS的表達(dá)式;
② 得到一個(gè)沒(méi)有中括號(hào)包裹的WXS對(duì)象。此時(shí)我們可以先對(duì)表達(dá)式嘗試轉(zhuǎn)換,如果有報(bào)錯(cuò)的話,給表達(dá)式包裹一層中括號(hào)再進(jìn)行轉(zhuǎn)換??紤]到WXS的語(yǔ)法類(lèi)似于Javascript的子集,我們依然使用babel對(duì)其進(jìn)行解析并處理。
核心代碼實(shí)現(xiàn)如下:
/** * * @param value 需要轉(zhuǎn)換的屬性值 */ private transformValue(value: string): string { const exp = value.match(TransformTemplate.dbbraceRe)[1] try { let seq = false traverse(parseSync(`(${exp})`), { enter(path) { // 由于WXS支持對(duì)象鍵值相等的縮寫(xiě){{a,b,c}},故此處需要額外處理 if (path.isSequenceExpression()) { seq = true } }, }) if (!seq) { return exp } return `{${exp}}` } catch (e) { return `{${exp}}` } }
到這里,我們已經(jīng)能夠處理wepy模板中絕大部分的動(dòng)態(tài)屬性值的轉(zhuǎn)換。但是,上文也提及到了,wepy采用的是類(lèi)似模板引擎的方式來(lái)處理動(dòng)態(tài)屬性的,即WXML支持這種動(dòng)態(tài)屬性< view id="item-{{index}}" >,如果這個(gè)< view / >標(biāo)簽使用了wx:for指令的話,id屬性會(huì)被編譯成item-0、item-1... 這個(gè)問(wèn)題我們也想了多種方案去解決,例如字符串拼接、正則處理等,但是都不能很好的覆蓋全部場(chǎng)景,總會(huì)有特殊場(chǎng)景的出現(xiàn)導(dǎo)致轉(zhuǎn)換失敗。
最終,我們還是想到了模板引擎,Javascript中也有類(lèi)似于模板引擎的元素,那就是模板字符串。使用模板字符串,我們僅僅需要把WXML中用來(lái)標(biāo)記變量的雙括號(hào){{}}轉(zhuǎn)換成Javascript中的${}即可。
5.2 Wepy App 轉(zhuǎn)換
5.2.1差異性梳理
wepy 的 App 小程序?qū)嵗兄饕〕绦蛏芷诤瘮?shù)、config 配置對(duì)象、globalData 全局?jǐn)?shù)據(jù)對(duì)象,以及其他自定義方法與屬性。
核心代碼實(shí)現(xiàn)如下:
import wepy from 'wepy' // 在 page 中,通過(guò) this.$parent 來(lái)訪問(wèn) app 實(shí)例 export default class MyAPP extends wepy.app { customData = {} customFunction() {} onLaunch() {} onShow() {} // 對(duì)應(yīng) app.json 文件 // build 編譯時(shí)會(huì)根據(jù) config 屬性自動(dòng)生成 app.json 文件 config = {} globalData = {} }
uniapp的 App.vue 可以定義小程序生命周期方法,globalData全局?jǐn)?shù)據(jù)對(duì)象,以及一些自定義方法,核心代碼實(shí)現(xiàn)如下:
可以看到,wepy的page類(lèi)也是通過(guò)繼承來(lái)實(shí)現(xiàn)的,頁(yè)面文件 page.wpy 中所聲明的頁(yè)面實(shí)例繼承自 wepy.page 類(lèi),該類(lèi)的主要屬性介紹如下:
5.4.2 核心轉(zhuǎn)換設(shè)計(jì)
基于page的api特性以及實(shí)現(xiàn)方案,具體的轉(zhuǎn)換設(shè)計(jì)思路如下:
5.4.3 痛點(diǎn)難點(diǎn)
1.非阻塞異步與異步
在進(jìn)行批量pages轉(zhuǎn)換時(shí),需要同時(shí)對(duì)pages.json進(jìn)行讀取、修改、再修改的操作,這就涉及到使用阻塞 IO/ 異步 IO來(lái)處理文件的讀寫(xiě),當(dāng)使用異步IO時(shí),會(huì)發(fā)起多個(gè)進(jìn)程同時(shí)處理pages.json, 每個(gè)讀取完成后單獨(dú)處理對(duì)應(yīng)的內(nèi)容,數(shù)據(jù)不是串行修改,最終導(dǎo)致最終修改的內(nèi)容不符合預(yù)期,因此在遇到并行處配置文件時(shí),需要使用阻塞式io來(lái)讀取文件,保障最終數(shù)據(jù)的唯一性,具體代碼如下:
// merge pageConfig to app config const rawPagesJson = fs.readFileSync(path.join(dest, 'src/pages.json')) // 數(shù)據(jù)操作 fs.writeFileSync( path.join(dest, 'src', 'pages.json'), prettJson(pagesJson) )
2.復(fù)雜的事件機(jī)制
在轉(zhuǎn)換過(guò)程中,我們也碰到一個(gè)比較大的痛點(diǎn):page.wepy 繼承至 wepy.page,wepy.page 代碼較復(fù)雜,需要將明確部分單獨(dú)抽離出來(lái)。例如說(shuō) events 中組件間數(shù)據(jù)傳遞:`$broadcast`、`$emit`、`$invoke`,`$broadcast`、`$invoke`需要熟悉其使用場(chǎng)景,轉(zhuǎn)換為 Vue 中公共方法。
5.5 Wepy WXML 轉(zhuǎn)換
template轉(zhuǎn)換章節(jié)中提到了wepy模板中可以直接引入wxml文件,但是uni-app使用的Vue模板不支持直接引入wxml,故我們需要將wxml文件處理為uniapp可以引入的Vue文件。我們先來(lái)看一下wepy中引入的wxml文件的大致結(jié)構(gòu)。
{{item.text1}} {{item.text2}} this is footer
5.5.1差異性梳理
從上面的代碼可以看出,一個(gè)WXML文件中支持多個(gè)不同name屬性的< template/ >標(biāo)簽,并且支持通過(guò)在引入設(shè)置data來(lái)傳入屬性。從上面的示例模板中我們可以分析出,除了需要將wepy使用的WXML語(yǔ)法轉(zhuǎn)換成vue模板語(yǔ)法外(這里的轉(zhuǎn)換交給了template模塊來(lái)處理),我們還需要處理以下的問(wèn)題。
確定引入組件時(shí)的傳參格式
確定組件中傳入對(duì)象的屬性有哪些
處理< import/ >和< include/ >引入的文件時(shí)的情況
5.5.2 核心轉(zhuǎn)換設(shè)計(jì)
1. 確定引入組件時(shí)的傳入屬性方式
首先需要將wepy組件引入形式改成Vue的組件引入方式。以上面的代碼為例,即將< import/ >、< script/ >對(duì)的引入形式改寫(xiě)成< component-name / >引入方式。我們會(huì)在轉(zhuǎn)換開(kāi)始前對(duì)代碼進(jìn)行掃描,收集模板中的引入文件信息,傳遞給wepy-page-transform模塊處理,在轉(zhuǎn)換后的Vue組件的< script/ >中進(jìn)行引入。并且將< script is="foo" data="{{item, pic}}" / >轉(zhuǎn)換為< FooBar is="foo" :data=(待定) / >。這里就需要確定屬性傳遞的方式。
從上面的代碼中可以看到,在WXML文件的< template/ >會(huì)自動(dòng)使用傳入的data屬性作為隱式的命名空間,從而不需要使用data.item來(lái)獲取item屬性。這里很自然的就會(huì)想到原來(lái)的< script is="foo" data="{{item, pic}}" / >可以轉(zhuǎn)換成< FooBar compName="foo" :key1="val1" :key2="val2" ... / >。
其中,key1,val1,key2,val2等為原data屬性對(duì)象中的鍵值對(duì),compName用來(lái)指定展示的部分。這樣處理的好處是,引入的WXML文件中使用相應(yīng)的傳入的屬性就不需要做額外的修改,并且比較符合我們一般引入Vue組件時(shí)傳入屬性的方式。
雖然這種方案可以較少的改動(dòng)WXML文件中的模板,但是由于傳入的對(duì)象可能會(huì)在運(yùn)行期間進(jìn)行修改,我們?cè)诰幾g期間比較難以確定傳入的data對(duì)象中的鍵值對(duì)。考慮到實(shí)現(xiàn)的時(shí)間成本及難易程度,我們沒(méi)有選擇這種方案。
目前我們所采用的方案是不去改變?cè)械膶傩詡魅敕绞?,即將組件引入標(biāo)簽轉(zhuǎn)換為< FooBar compName="foo" :data="{item, pic}" / >。從而省去分析傳入對(duì)象在運(yùn)行時(shí)的變動(dòng)。這里就引出了第二個(gè)問(wèn)題,如何確定組件中傳入的參數(shù)有哪些。
2. 確定組件中的傳入的對(duì)象屬性
由于Vue的模板中不會(huì)自動(dòng)使用傳入的對(duì)象作為命名空間,我們需要手動(dòng)的找到當(dāng)前待轉(zhuǎn)換的模板中所使用到的所有的變量。相應(yīng)的代碼如下:
searchVars() { const self = this const domList = this.$('template *') // 獲取wxml文件中template節(jié)點(diǎn)下的所有text節(jié)點(diǎn) const text = domList.text() const dbbraceRe = new RegExp(TransformTemplate.dbbraceRe, 'g') let ivar // 拿到所有被{{}}包裹的動(dòng)態(tài)表達(dá)式 while ((ivar = dbbraceRe.exec(text))) { addVar(ivar[1]) } // 遍歷所有節(jié)點(diǎn)的屬性,獲取所有的動(dòng)態(tài)屬性 for (let i = 0; i < domList.length; i++) { const dom = domList.eq(i) const attrs = Object.keys(dom.attr()) for (let attr of attrs) { const value = dom.attr(attr) if (!TransformTemplate.dbbraceRe.test(value)) continue const exp = value.match(TransformTemplate.dbbraceRe)[1] try { addVar(exp) } catch (e) { addVar(`{${exp}}`) } } } function addVar(exp: string) { traverse(parseSync(`(${exp})`), { // 利用babel分析表達(dá)式中的所有變量 Identifier(path) { if ( path.parentPath.isMemberExpression() && !path.parentPath.node.computed && path.parentPath.node.property === path.node ) return self.vars.add(path.node.name) // 收集變量 }, }) } }
收集到所有的變量信息后,模板中的所有變量前面需要加上傳入的對(duì)象名稱(chēng),例如item.hp_title需要轉(zhuǎn)換成data.item.hp_title??紤]到模板的簡(jiǎn)潔性和后續(xù)的易維護(hù)性,我們把轉(zhuǎn)換統(tǒng)一放到< script/ >的computed字段中統(tǒng)一處理即可:
3.處理 < import/ >和< include/ >兩種引入方式
wepy模板有兩種引入組件的方式,一種是使用< import/ >< script/ >標(biāo)簽對(duì)進(jìn)行引入,還有一種是使用< include/ >進(jìn)行引入,< include/ >會(huì)引入WXML文件中除了< template/ >和< wxs/ >的其他標(biāo)簽。這里的處理方式就比較簡(jiǎn)單,我們把< include/ >會(huì)引入的部分單獨(dú)抽取出來(lái),生成TItem-incl.vue文件,這樣即保證了生成代碼的可復(fù)用性,也降低< import/ >標(biāo)簽引入的部分生成的TItem.vue文件中的邏輯復(fù)雜度。生成的兩個(gè)文件的結(jié)構(gòu)如下:
this is footer
六、階段性成果
截止到目前,司內(nèi)的企微導(dǎo)購(gòu)小程序項(xiàng)目通過(guò)接入變色龍編譯器已經(jīng)順利的從 wepy 遷移到了 uniApp 架構(gòu),原本預(yù)計(jì)需要 25人/天 的遷移工作量在使用了編譯器轉(zhuǎn)換后縮短到了 10s。這不僅僅只是提高了遷移的效率,也降低了遷移中的知識(shí)遷移成本,給后續(xù)業(yè)務(wù)上的快速迭代奠定的扎實(shí)的基礎(chǔ)。
遷移后的企微導(dǎo)購(gòu)小程序項(xiàng)目經(jīng)測(cè)試階段驗(yàn)證業(yè)務(wù)功能 0 bug,目前已經(jīng)順利上線。后續(xù)我們也會(huì)持續(xù)收集其他類(lèi)似的業(yè)務(wù)訴求,幫助業(yè)務(wù)兄弟們低成本完成遷移。
七、總結(jié)
研發(fā)能效的提升是個(gè)永恒的話題,此次我們從編譯這個(gè)角度出發(fā),和大家分享了從wepy到uniapp的架構(gòu)升級(jí)探索的過(guò)程,通過(guò)構(gòu)建代碼轉(zhuǎn)換的編譯器來(lái)提升整體的架構(gòu)升級(jí)效率,通過(guò)編譯器消化底層的領(lǐng)域和知識(shí)的差異性,取得了不錯(cuò)的效果。
當(dāng)然,我們目前也有還不夠完善的地方,如:編譯器腳手架缺乏對(duì)于部分特性顆粒度更細(xì)的控制、代碼編譯轉(zhuǎn)換過(guò)程中日志的輸出更友好等等。后續(xù)我們也有計(jì)劃將 wepy 變色龍編譯器在社區(qū)開(kāi)源共建,屆時(shí)歡迎大家一起參與進(jìn)來(lái)。
現(xiàn)階段編譯在前端的使用場(chǎng)景越來(lái)越多,或許我們真的進(jìn)入了Compiler is our framework的時(shí)代。
-
互聯(lián)網(wǎng)
+關(guān)注
關(guān)注
54文章
11148瀏覽量
103211 -
vivo
+關(guān)注
關(guān)注
12文章
3303瀏覽量
63254 -
小程序
+關(guān)注
關(guān)注
1文章
234瀏覽量
12126
原文標(biāo)題:從wepy到uniapp變形記
文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論