如今前端对移动端和小程序的开发热情很高,各类多端解决方案百花齐放。例如很火的Taro和mpvue,还有后来居上的uni-app等等。php
因公司业务须要,本人最近也在忙活各类小程序,例如:以前开发的小程序的业务逻辑须要在其余平台复用,咱们不可能把业务再重写一遍,因此须要研究下小程序之间的差别和转换,所以花了很多精力,有点心得体会,写点东西和你们交流交流。css
这篇总结文章主要是对转换工具 github.com/xujie-phper… 的介绍,想进一步研究的同窗能够带着问题看看代码,这样你就会更疑惑了~~html
类型 | 微信小程序 | 百度小程序 | 支付宝小程序 |
---|---|---|---|
api | wx.* | swan.* | my.* |
视图模版 | 循环: wx:for条件: wx:if | 循环: s-for 条件:s-if条件判断中不须要使用插值语法 | 循环: a:for条件: a:if |
事件处理 | bindtap | bindtap | onTap |
过滤器 | wxs语法 | filter语法 | 无 |
原生组件 | 除canvas,基本一致 | 除canvas,基本一致 | 更强大组件库系统 |
登录流程 | 指定scope受权 | 指定scope,获取code,换token | 受权获取code,获取token |
支付 | 微信支付 | 聚合收银台 | 支付宝 |
配置信息 | project.config.json | project.swan.json和pkginfo.json | 无 |
生命周期和样式 | 一致(理论) | 一致(理论) | 一致(理论) |
使用
recursive-copy
库,完成文件的总体拷贝,替换文件名后缀,例:.wxml===> .swan前端
一、找到josn文件里“usingComponents“包含的值,将组件引用中的驼峰改成kebabCase 二、若包含抽象节点“componentGenerics“字段,手百中不支持,存放在错误日志中vue
⚠️将修改后的组件名的映射关系记录在全局的contextStore中,属性值为“renamedComponents“,视图层中转换中须要使用新的组件名node
如下的转换逻辑会大量依赖babel,进行AST的代码转换,因此咱们先巩固下抽象语法树相关的知识。git
可能刚接触AST的人会感受无从下手,毕竟ast相关的知识点确实比较繁杂,并且相关的入门指导比较少。这里咱们以一个完整的例子,过一下AST经常使用基本语法,方便你们入门,虽然说是入门,但若是熟练掌握,已经能够应用于实际开发了。github
注意:js中不一样的数据类型,对应的ast节点信息也不竟相同。以图中为例,externalClasses对象的节点信息中类型(type)是ObjectProperty,包含key ,value等关键属性(其余类型节点可能就没有)json
注意选择最新的babel7版本,否则下面例子中的类型会匹配不上,canvas
假设咱们的目标是要把properties属性中key为‘current’的属性改成myCurrent。let's go!
原始代码:
/*eslint-disable*/
/*globals Page, getApp, App, wx,Component,getCurrentPages*/
Component({
externalClasses: ['u-class'],
relations: {
'../tab/index': {
type: 'child',
linked() {
this.changeCurrent();
},
linkChanged() {
this.changeCurrent();
},
unlinked() {
this.changeCurrent();
}
}
},
properties: {
current: {
type: String,
value: '',
observer: 'changeCurrent'
}
},
methods: {
changeCurrent(val = this.data.current) {
let items = this.getRelationNodes('../tab/index');
const len = items.length;
if (len > 0) {
items.forEach(item => {
item.changeScroll(this.data.scroll);
item.changeCurrent(item.data.key === val);
item.changeCurrentColor(this.data.color);
});
}
},
emitEvent(key) {
this.triggerEvent('change', { key });
}
}
});
复制代码
首先在原始代码中选中'current',查看右边ast的节点结构,如图:
查找
export default function (babel) {
const { types: t } = babel;
return {
name: "ast-transform", // not required
visitor: {
Identifier(path) {
//path.node.name = path.node.name.split('').reverse().join('');
},
ObjectProperty(path) {
if (path.node.key.type === 'StringIdentifier' &&
path.node.key.name === 'current') {
console.log(path,'StringIdentifier')
}
}
}
};
}
复制代码
这里须要用到@babel/types
babeljs.io/docs/en/bab…来辅助咱们进行类型判断,开发中会很是依赖这个字典进行查找
在控制台会看见,path下面的节点信息不少,关键字段为node和parentPath,node记录了该节点下数据信息,例如以前提到过的key和value。parentPath表明父级节点,此例中表示ObjectExpression中properties节点信息,有时咱们须要修改父节点的数据,例如常见的节点移除操做。接下来咱们修改该节点信息。
修改
在@babel/types
中找到该ObjectProperty的节点信息以下,咱们须要须要构造一个新的同类型节点(ObjectProperty)来替换它。
ObjectProperty(path) {
console.log(path,'ObjectProperty--')
if (path.node.key.type === 'Identifier' &&
path.node.key.name === 'current') {
//替换节点
path.replaceWith(t.objectProperty(t.identifier('myCurrent'), path.node.value));
}
}
复制代码
其中咱们用到了replaceWith方法,这个方法表示用一个ast节点来替换当前节点。 还有一个经常使用的replaceWithSourceString方法,表示用一个字符串来代替该ast节点,参数为一串代码字符串,如:'current : {type:String};',感兴趣的,能够本身试试。
最后查看转换后的代码,发现'current'已经被咱们替换成了'myCurrent'。
到这里,一个完整的例子就演示完了。这里补充说明一下,在实际中可能会遇到嵌套结构比较深的ast结构。咱们须要嵌套类型判断,好比:
ObjectProperty(path) {
console.log(path,'ObjectProperty--')
MemberExpression(memberPath) {
console.log(path,'memberPath--')
}
}
复制代码
由于遍历中的path指定的是当前匹配的节点信息。因此能够为不一样的类型遍历指定不一样的path参数,来获取当前遍历的节点信息,避免path覆盖,例如上面的path和memberPath。
到这里,babel的基本用法就差很少介绍完了,想要熟练掌握,还须要你在项目中反复练习和实践。想系统学习babel,并在实际项目中使用的同窗能够先看看这篇babel的介绍文档,边写边查,巩固学习
借助babel的三剑客:@babel/parser
、@babel/traverse
、@babel/generator
。
js的转换规则较复杂,会大量依赖
babel/types
作类型判断,并借助在线AST工具辅助测试。
+--------+ +----------+
Input ->- | Parser | ->- Syntax Tree ->- | Compiler | ->- Output
+--------+ | +----------+
X
|
+--------------+
| Transformers |
+--------------+
复制代码
名称不一样,功能相同的api,须要作映射,例: navigateToMiniProgram
===> navigateToSmartProgram
自定义组件的处理: 百度小程序构造器不支持的属性: moved,relations, observers 内置behaviors的处理:
`wx://form-field` ===> `swan://form-field`
`wx://component-export` ===> `swan://component-export`
复制代码
relations
中如有使用link回调函数,则对应到百度的attached生命周期中执行, 配套使用的getRelationNodes
,则对应百度的selectComponent方法。
为解决页面多组件实例的问题,引入swanId作为惟一标识,咱们会为有依赖关系的组件添加swanId属性,同一组的父子组件共用一个swanId。
全部的父子组件的依赖关系存在在全局的contextStore中,供视图层添加swanId时使用
独有api没法自动匹配,存放到转换日志中,需手动删除或替换对应逻辑
关键词替换:wx ===> swan
视图层的转换也是使用的AST,借助
stricter-htmlparser2
将html转化为节点树,遍历,替换指定节点,最后生成新的html结构。
<view wx:='aaa'>test</view>
"parseHtml": {
"type": "tag",
"name": "view",
"attribs": {
"wx:": "aaa"
},
"children": [
{
"data": "test",
"type": "text"
}
],
"singleQuoteAttribs": {},
"selfclose": false
}
复制代码
微信:
//循环
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
//条件
<view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view>
<view wx:elif="{{view == 'APP'}}"> APP </view>
<view wx:else="{{view == 'MINA'}}"> MINA </view>
复制代码
百度
//循环
<view>
<view s-for="p,index in persons">
{{index}}: {{p.name}}
</view>
</view>
//条件
<view s-if="is4G">4G</view>
<view s-elif="isWifi">Wifi</view>
<view s-else>Other</view>
复制代码
转换逻辑为:
- 将wx:替换为s-,例:wx:if =====> s-if
- 去掉插值语法(花括号)
- wx:for, wx:for-index,wx:for-item合并为s-for="p,index in persons"
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
//**微信**:
<template is="msgItem" data="{{...item}}"/>
//**百度**:
<template is="msg-item" data="{{ {...item} }}" />
复制代码
转换逻辑为:
- data属性外增长一个大括号
- 名称改成小写字母与中划线“-”的组合
for
和if
做用于同一标签微信可使用,手百禁止, 编译会报错
注意:
s-if
与s-for
不可在同一标签下同时使用。
将微信中的if
标签,借助虚拟组件block,分红父子组件。 例:
<view wx:for="{{list}}" wx:if="{{item}}">test</view>
复制代码
转化为
<view s-for="item, index in list">
<block s-if="item">test</block>
</view>
复制代码
//**微信**:
<scroll-view scroll-into-view="{{toView}}" scroll-top="{{scrollTop}}">
<view id="green" class="scroll-view-item bc_green"></view>
</scroll-view>
//**百度**:
<scroll-view scroll-into-view="{=toView=}" scroll-top="{=scrollTop=}">
<view id="green" class="scroll-view-item bc_green"></view>
</scroll-view>
复制代码
转换逻辑为:
将插值语法变换为{= * =}
微信使用wxs来进行数据处理,定义共用函数段;对应的百度的filter语法
//**微信**:
<wxs module="test">
var some_msg = "hello world";
module.exports = {
setPosition: function (position) {
return 'transform: translateX(' + position.pageX + 'px);';
}
}
</wxs>
//**百度**:
<filter module="test">
var some_msg = "hello world";
export default {
setPosition: function (position) {
return 'transform: translateX(' + position.pageX + 'px);';
}
}
</filter>
复制代码
转换逻辑为:
将module.exports替换为export default
注:百度的filter中不支持导出变量,可是微信是支持的,全部这部分须要开发者手动处理下逻辑
小程序间的样式彻底同样,只是文件后缀名不一样,只须要替换引入的样式文件后缀wxss ===> css
例:
@import "header.wxss";
复制代码
转化为:
@import "header.css";
复制代码
转换日志分为'info'、'warning'、'error'三种类型,转换过程当中产生的日志信息都存放在统一logStore中,结束时会借助mkdirp
和fs
能力把logStore存储的全部信息,写入到日志文件中。
注:小程序独有能力和私有能力,没法转化(目前),须要手动进行逻辑替换或删除。转换中不涉及项目依赖文件的替换,例:
project.swan.json
和pkginfo.json
,可使用百度开发者工具自动生成
以上所讨论的都是最近写的一个微信转百度小程序工具的详细介绍和具体实现,对小程序和babel感兴趣的能够去看看代码,应该会有所收获,并能发现其中还存在的一些问题,欢迎讨论,一块儿学习。