从 Wepy 到 UniApp 变形记( 四 )


WXML中使用双中括号来标记动态属性中的变量及WXS表达式,并且如果变量是WXS对象的话还可以省略对象的大括号例如
< view wx:for="{{list}}" > {{item}} < /view >、< template is="objectCombine" data="https://www.huyubaike.com/biancheng/{{for: a, bar: b}}" >< /template >所以当我们取到双中括号中的值时会有以下两种情况:

  1. 得到WXS的表达式;
  2. 得到一个没有中括号包裹的WXS对象 。此时我们可以先对表达式尝试转换,如果有报错的话,给表达式包裹一层中括号再进行转换 。考虑到WXS的语法类似于Javascript的子集,我们依然使用babel对其进行解析并处理 。
核心代码实现如下:
/** * * @param value 需要转换的属性值 */private transformValue(value: string): string {const exp = value.match(TransformTemplate.dbbraceRe)[1]try {let seq = falsetraverse(parseSync(`(${exp})`), {enter(path) {// 由于WXS支持对象键值相等的缩写{{a,b,c}},故此处需要额外处理if (path.isSequenceExpression()) {seq = true}},})if (!seq) {return exp}return `{${exp}}`} catch (e) {return `{${exp}}`}}到这里,我们已经能够处理wepy模板中绝大部分的动态属性值的转换 。但是,上文也提及到了,wepy采用的是类似模板引擎的方式来处理动态属性的,即WXML支持这种动态属性< view id="item-{{index}}" >,如果这个 < view / >标签使用了wx:for指令的话,id属性会被编译成item-0、item-1... 这个问题我们也想了多种方案去解决,例如字符串拼接、正则处理等,但是都不能很好的覆盖全部场景,总会有特殊场景的出现导致转换失败 。
最终,我们还是想到了模板引擎,Javascript中也有类似于模板引擎的元素,那就是模板字符串 。使用模板字符串,我们仅仅需要把WXML中用来标记变量的双括号{{}}转换成Javascript中的${}即可 。
5.2 Wepy App 转换5.2.1 差异性梳理wepy 的 App 小程序实例中主要包含小程序生命周期函数、config 配置对象、globalData 全局数据对象,以及其他自定义方法与属性 。
核心代码实现如下:
import wepy from 'wepy'// 在 page 中,通过 this.$parent 来访问 app 实例export default class MyAPP extends wepy.app {customData = https://www.huyubaike.com/biancheng/{}customFunction() {}onLaunch() {}onShow() {}// 对应 app.json 文件// build 编译时会根据 config 属性自动生成 app.json 文件config = {}globalData = {}}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}), ])// ..... 

经验总结扩展阅读