从 Wepy 到 UniApp 变形记( 六 )

这里要注意一点,由于对 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}forEachListenEvents 函数主要是通过 wepy 中 声明的 events 事件名和入参,借助 babel types 手动创建对应的 AST Node,最终生成对应的形如 this.eventBus.on("canceldeposit", this.canceldeposit) 形式的监听,其中,this.canceldeposit 为原有 events 中的事件被移入 methods 后的函数,相关伪代码实现如下所示:

经验总结扩展阅读