從 Wepy 到 UniApp 變形記( 五 )


從 Wepy 到 UniApp 變形記

文章插圖
預處理 AST , 目的是提前將 wepy 源碼中的代碼塊解析為 AST Node 節點后,按語法進行歸集到預置的 clzProperty 對象中,其中:
  • props 對象用來盛放 ClassProperty 語法的 ast 節點
  • notCompatibleMethods 數組用來盛放非生命周期函數白名單內的函數 AST 節點 。
  • appEvents 數組用來盛放生命周期函數白名單內的函數 AST 節點 。
  • listenEvents 數組用來盛放 發布/訂閱事件注冊的函數 AST 節點 。
核心代碼實現如下所示:
import { NodePath, traverse, types } from '@babel/core'this.clzProperty = {props: {},notCompatibleMethods: [],appEvents: [],listenEvents: []}traverse() {ClassProperty: (path) => {const name = path.node.key.namethis.clzPropertyprops[name] = path.node},ClassMethod: (path) => {const methodName = path.node.key.name// 判斷是否存在于生命周期白名單內const isCompEvent = TOTAL_EVENT.includes(methodName)if (isCompEvent) {this.clzProperty.appEvents.push(path.node)} else {this.clzProperty.notCompatibleMethods.push(path.node)}},ObjectMethod: (path: any) => {if (path.parentPath?.container?.key?.name === 'events') {this.clzProperty.listenEvents.push(path.node)}}}這里要注意一點,由于對 wepy 來說,實際上 page 也屬于 component 的一種實現,所以兩者的 event 會有一定的重合,而且由于 wepy 中生命周期和 Vue 生命周期的差異性,我們需要對如 attached、detached、ready 等鉤子做一些 hack 。
2.構建 Vue AST
buildCompVueAst 函數即為 構建 Vue AST 部分 。從直觀上來看 , 這個函數只做了一件事,即用 types.program 重新生成一個 AST 節點結構 , 然后將原有的 wepy 語法轉換為 vue 語法 。但是實際上我們還需要處理許多額外的兼容邏輯,簡單羅列一下:
  • created 重疊問題
  • methods 中函數的收集
  • events 中函數的調用處理
created 重疊問題主要是為了解決 created/attached/onLoad/onReady 這4個生命周期函數都會轉換為 created 導致的多次重復聲明問題 。我們需要針對若存在 created 重疊問題時,將其余鉤子中的代碼塊取出并 push 到第一個 created 鉤子函數內部 。代碼示例如下:
const body = this.ast.program.bodyconst { appEvents, notCompatibleMethods, props, listenEvents } =this.clzProperty// 處理多個 created 生命周期重疊問題const createIndexs: number[] = []const sameList = ['created', 'attached', 'onLoad', 'onReady']appEvents.forEach((node, index) => {const name: string = node.key.nameif (sameList.includes(name)) {createIndexs.push(index)}})if (createIndexs.length > 1) {// 取出源節點內代碼塊const originIndex = createIndexs[0]const originNode = appEvents[originIndex]const originBodyNode = originNode.body.body// 留下的剩余節點需要取出其代碼塊并塞入源節點中// 塞入完成后刪除剩余節點createIndexs.splice(0, 1)createIndexs.forEach((index) => {const targetNode = appEvents[index]const targetBodyNode = targetNode.body.body// 將源節點內代碼塊塞入目標節點中originBodyNode.push(...targetBodyNode)// 刪除源節點appEvents.splice(index, 1)})} 由于 wepy 中非 methods 中函數的特殊性,所以我們需要在轉換時將獨立聲明的函數、events 中的函數都抽離出來再 push 到 methods 中,偽代碼邏輯如下所示:
buildCompVueAst() {const body = this.ast.program.bodyreturn t.program([...body.map((node) => {return t.exportDefaultDeclaration(t.objectExpression([...Object.keys(props).map((elem) => {if (elem === 'methods') {const node = props[elem]// 1.events 內函數插入 methods 中// 2.與生命周期平級的函數抽離出來插入 methods 中node.value.properties.push(...listenEvents,...notCompatibleMethods)}return props[elem]})]))})])}events 中函數的調用處理主要是為了抹平 wepy 中發布訂閱事件調用和 Vue 調用的差異性 。在 wepy 中,事件的注冊通過在 events 中聲明函數,事件的調用通過 this.$emit 來觸發 。而 vue 中我們采用的是 EventBus 方案來兼容 wepy 中的寫法,即手動為 events 中的函數創建 this.$on 形式的調用,并將其代碼塊按順序塞入 created 中來初始化 。
首先我們要判斷文件中是否已有 created 函數 , 若存在 , 則獲取其對應的代碼塊并調用 forEachListenEvents 函數將 events 中的監聽都 push 進去 。
若不存在 , 則初始化一個空的 created 容器,并調用 forEachListenEvents 函數 。核心代碼實現如下所示:
buildCompVueAst() {const obp = [] as types.ObjectMethod[]// 獲取class屬性和方法const body = node.declaration.body.bodyconst targetNodeArray = body.filter(child =>child.key.name === 'created')if (targetNodeArray.length > 0) {let createdNode = targetNodeArray[0]this.forEachListenEvents(createdNode)} else {const targetNode = t.objectMethod('method',t.identifier('created'),[],t.blockStatement([]))this.forEachListenEvents(targetNode)if (targetNode.body && targetNode.body.body.length > 0) {obp.push(targetNode)}}return obp}

推薦閱讀