从 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}

推荐阅读