从 Wepy 到 UniApp 变形记( 四 )

uniapp的 App.vue 可以定义小程序生命周期方法,globalData全局数据对象 , 以及一些自定义方法,核心代码实现如下:
<script>export default {globalData: {text: 'text'}onLaunch: function() {console.log('App Launch,app启动')},onShow: function() {console.log('App Show,app展现在前台')},onHide: function() {console.log('App Hide,app不再展现在前台')},methods: {// .....}}<script>5.2.2 核心转换设计

从 Wepy 到 UniApp 变形记

文章插图
如图,核心转换设计流程:
  1. 对 app.py 进行 parse,拆分出script和style部分,对script部分使用babel进行parse生成AST 。
  2. 通过对 AST 分析出,小程序的生命周期方法,globalData全局数据,自定义方法等 。
  3. 对于AST进行uniapp转换,生命周期方法和全局数据转成对象的方法和属性,对自定义方法转换到method内 。
  4. 其中对 globalData 的访问,要进行替换通过 getApp()进行访问 。
  5. 抽取 ast 中的 config 字段,输出到 app.json 配置文件 。
  6. 抽取 wepy.config.js 中的 config 字段 , 传入 wepy 的 app 实例 。
核心代码实现:
let APP_EVENT = ['onLaunch', 'onShow', 'onHide', 'onError', 'onPageNotFound']//....// 实现wepy app到uniapp App.vue的转换t.program([...body.filter((node: t.Node) => !t.isExportDeclaration(node)),// 插入appClass...appClass,...body.filter((node: t.Node) => t.isExportDeclaration(node)).map((node: object) => {// 对导出的app进行处理if (t.isExportDeclaration(node)) {// 提前config属性const { appEvents, methods, props } = this.clzProperty// 重新导出vue style的对象return t.exportDefaultDeclaration(t.objectExpression([// mixins...mixins,// props...Object.keys(props).filter((elem) => elem !== 'config').map((elem) =>this.transformClassPropertyToObjectProperty(props[elem])),// app events...appEvents.map((elem) =>this.transformClassMethodToObjectMethod(elem)),// methodst.objectProperty(t.identifier('methods'),t.objectExpression([...methods.map((elem) =>this.transformClassMethodToObjectMethod(elem)),])),]))}return node}), ])// ..... 5.2.3 痛点难点在运行期,app.wpy 会继承 wepy.App 类 , 这样就会在运行期和 wepy.App 产生依赖关系,怎么最小化弱化这种关系 。抽取wepy的最小化以来的polyfill,随着业务中代码剔除对wepy的api调用,最终去除对polyfill的依赖 。
5.3 wepy component 转换对于wepy component 的转换主要可以细化到对 component 中 template、script、style 三部分代码块的转换 。
其中,style 部分由于已经兼容 Vue 的规范 , 所以我们无需做额外处理 。而 template 模块主要是需要对 wepy template 中特殊的标签、属性、事件等内容进行处理 , 转化为适配 uni的template,上文做了详细的说明 。
我们只需要专注于处理 script 模块的代码转换即可 。从架构设计的思路来看,component script 的转换主要是是做以下两件事:
  1. 编译期可确定代码块的转换 。
  2. 运行期动态注入代码的兼容 。
wepy-component-transform 就是基于以上这两个标准设计出来的实现转换逻辑的模块 。
5.3.1 差异性梳理首先先解释一下什么是“编译期可确定代码块”,我们来看一个 wepy 和 Vue 语法对比示例:
从 Wepy 到 UniApp 变形记

文章插图
从直观上来说,这个 script 的模板的语法大致和 Vue 语法类似,这意味着我们解析出来的 AST 结构和 Vue 文件对应的 AST 结构上类似 , 基于这一点来看编译转换的工作量大致有底了 。
从细节来看,wpy 文件script 模块中的 API 语法和 Vue 中有声明及使用上的不同,其中包含:
  1. wepy 自身的包依赖注入及运行时依赖
  2. props/data/methods 声明方式不同
  3. 生命周期钩子不同
  4. 事件发布/订阅的注册和监听机制不同 。
  5. ....等等
为了确定这个第5点等等还存在哪些使用场景,我们需要对 wepy 自身的逻辑和玩法有一个详尽的了解和熟悉,通过在团队内组织的 wepy 源码走读,再结合wepy 实际生产项目中的代码相互印鉴 , 我们最终才将 wepy 语法逻辑与 uni-app Vue 语法逻辑的异同梳理清楚 。
5.3.2 核心转换设计我们简单梳理一下 wepy-component-transform 这个模块的结构,可以分为以下三个部分:
  • 预处理 wepy component script 代码 AST 节点部分
  • 构建 Vue AST
  • 通过 generate 吐出代码
1.预处理 AST
基于前文转换设计这一节我们知道, wepy 变色龙的转换器中对代码的 AST 解析主要依赖 babel AST 三板斧(traverse、types、generate)来实现 , 通过分析各个差异点代码语句转换后的 AST 节点,就可以通过 traverse 中的钩子来进行节点的前置处理,这里安利一下 https://astexplorer.net/,我们可以通过它快速分析代码块 AST 节点、模拟场景及验证转换逻辑:

推荐阅读