wx2qh
wx2qh 是用来将微信小程序迁移到 360 小程序的转化工具,它基于 AST,然后按照一定的规则静态分析来抹平平台之间的语法差异,简化迁移微信小程序到 360 小程序流程。
- wx2qh 解决了平台之间的语法差异,而运行时的差异需要进行二次开发来修复
- 不支持迁移第三方框架开发的微信小程序
切记:
转换完成后,需要切换到转化后生成目录(dest 指定目录)安装 @qihoo/seapp-builder来构建转化后的代码
目录
CLI
切记:转换完成后,需要切换到转化后生成目录(dest 指定目录)安装 @qihoo/seapp-builder来构建转化后的代码
安装
建议全局安装。
npm i -g @qihoo/wx2qh
项目内安装:
npm i @qihoo/wx2qh
命令行参数如下:
wx2qh \<source\> \<output\> transform the files in source directory to dest
wx2qh version output version number
wx2qh help output usage infomation
options:
-f,--file set files pattern \<glob\>
-i,--ignore set ignore files pattern \<glob\>
-p,--preset set preset config \<string\>
-r,--ratio set rpx2px ratio \<number\>
参数说明:
source
待迁移微信小程序项目路径(必填)output
迁移后,360 小程序的生成目录(默认值为 . 即当前目录)-f
,--file
指定要转化的文件(glob
: dir/*.wxml)-i
, --ignore
忽略要转化的文件-p
, --preset
指定预设配置文件路径,详情见使用 preset-r
, --ratio
指定 rpx2px 的转化比例(默认:2, 2rpx => 1px)
编译整个项目
编译整个 wxApp 目录下的文件并输出到 360mp 目录,这个时候会对 已存在的 360mp 目录做二次确认,防止因为路径填写错误 覆盖原有文件,360mp 目录不存在则没有二次确认。
wx2qh ./wxApp ./360mp
? The output directory `/work/360mp` is existed, Pick an action: (Use arrow keys)
\> Overwrite // 删除 360mp 目录,然后写入
Merge // 直接写入
Cancel // 取消
编译选定文件
可以通过设置 file(f) 选项编译符合这个规则的文件,值是 glob 类型。
wx2qh ./wxApp ./360mp -f=*.wxml
忽略某些文件
可以通过设置 ignore(i) 选项忽略符合这个规则的文件,值是 glob 类型。
wx2qh ./wxApp ./360mp -i=*.test.js
使用 preset
可以通过设置 preset(p) 选项设置预设值,从而可以拓展和修改 wx2qh 的一些内置配置、插件等。preset 的路径支持情况如下:
- 本地路径:preset.startsWith('.')
- 绝对路径:path.isAbsolute(preset)
- npm package:preset.startsWith('wx2qh-plugin-')
- 默认:wxApp 目录下的 .wx2qhrc.json
wx2qh ./wxApp ./360mp -p ./wx2qh-plugin-preset
常见转化问题
- 转换 getApp()
在所有调用 getApp 的地方改写成如下:
let app = null
Page({
beforeCreate() {
app = getApp()
}
})
- 确认 app.json 中添加 sdkversion 字段()
{
"sdkversion": "1.0.0"
}
API
Props
- transformer
- compilation
map
- 内置
processor
的 Parser
和 Compiler
存储容器
- output
- options
object
- 配置选项,包括 cli 选项,prettier 配置等。等同于 PRESET
- ticks
set
- api.nexttick 传入回调的存储容器
- files
Methods
api.processor(name[, matcher])
参数
- name (
string
| object
) processor
名称
object
需要满足 { name, matcher }
- matcher (
string
| regexp
| function
) 过滤 files 中的 vfile
string
强制转换成正则function
自定义过滤规则,参数是 vfile
返回值
processor
定义文本处理流程,详情请见processor
例子
module.exports = function(api) {
const processor = api.processor('demo', '*.wxml')
const processor = api.processor('demo', /\*.wxml/)
const processor = api.processor('demo', file => file.path === 'index.wxml')
const processor = api.processor({ name: 'demo', matcher: '*.wxml' })
return processor
}
api.nextTick(callback)
参数
- callback (
function
) 持有 filesMap 上下文的回调函数,该 callback 会在输出文件之前串行调用
例子
module.exports = function(api) {
api.nextTick(callback)
function callback(filesmap) {
}
}
api.logger(type, log[, canSave])
参数
- type (
string
) 日志类型
- error 错误日志
- warn 警告日志
- info 信息日志
- done 完成日志
- log (
string
|object
) 日志内容
object
{ file, row, column, message }, 会在 canSave=true
打印位置等其他信息
- canSave (
boolean
, optional) [default: false
] 是否存入 logStore
例子
module.exports = function demoProcessor(api) {
api.logger('error', 'I am a error log')
api.logger('warn', 'I am a warning log')
api.logger(
'info',
{
file: 'app.json',
message: 'has sdkversion prop',
row: 10,
colum: 4
},
true
)
}
Processor
关于 processor
,下面只列出一些常用接口,更详细文档请参考 unifiedjs
processor.use(plugin[, options])
使用 use 方法为 processor
配置插件,并可以可选的传入配置项
场景
- processor.use(plugin[, options])
- processor.use(preset)
- processor.use(list)
参数
- plugin (
Attacher
) - options (
*
, optional) 插件的配置选项 - preset (
object
) { settings: {}, plugins: [] },都是可选属性 - list (
array
) [plugin, preset, [plugin, options]]
返回值
processor
返回当前调用 use
的调用者,可链式调用
例子
module.exports = function demoProcessor() {
const processor = api.processor('demo', 'index.wxml')
processor
.use(plugin, {})
.use([plugin, pluginB])
.use([plugin, [pluginB, {}]])
.use({
settings: [(position: false)],
plugins: [plugin, [pluginB, {}]]
})
.use({ settings: { position: false } })
function plugin() {}
function pluginB() {}
return processor
}
Attacher
processor
插件仅仅是一种概念,而 attacher
是 processor
插件的实现,它可以访问和修改 processor
的属性和方法,尤其是可以为 processor
添加必要的 Parser
和 Compiler
从而实现 text
到 ast
的反序列化,ast
到 text
的序列化。
例子
demoProcessor.js
:
module.exports = function demoProcessor(api) {
const processor = api.processor('demo', 'demo.wxml')
const wxmlCompilation = api.compilation.get('wxml2vue')
processor
.use(wxmlCompilation)
.use(demoAttacher)
return processor
}
function demoAttacher(options) {
return span2div
function span2div(tree) {
const spanElement = tree.children[0]
const spanText = spanElement.children[0]
spanElement.tagName = 'div'
spanText.value = `I am a div`
}
}
function parser(settings) {
this.Parser = parser
function parser(doc) {
return {
type: 'default',
contents: String(doc),
toString() {
return this.contents
}
}
}
}
function compiler() {
this.Compiler = compiler
function compiler(tree) {
return tree.toString()
}
}
demo.wxml
:
<span>I am a span</span>
cmd
:
wx2qh ./demo.wxml ./360mp -p ./demoProcessor.js
output 360mp/index.wxml
:
<div>I am a div</div>
function attacher([options])
context
上下文对象(this
)指向 processor
参数
- options (
*
, optional) 配置选项
返回值
transformer 可选
function transformer(node, file[, next])
参数
- node (
node
) 经过 Parser 转换后的语法树 - file (
vfile
) 正在被 processor
处理的 file - next (
function
, optional)
返回值
- void 如果返回时是
void
,当前处理的 node
将继续流入下一个 transform
。 - Error 如果返回
Error
, processor
将停止工作。 - node 如果返回
node
,返回的 node
将流入下一个 transformer
- Promise 如果返回的是
Promise
,将会等到 Promise
达到最终状态,然后将返回值流入下一个 transformer
或者中断 processor
function next(err[, tree[, file]])
参数
- err (
Error
, optional) - tree (
node
, optional) - file (
vfile
, optional)
Preset
preset 配置如下:
root: true,
file: '**',
ignore: [],
preset: undefined,
ratio: 2,
prettier: true,
prettierrc: {
parser: 'babel',
parserEntries: [
[/\.js$/, 'babel'],
[/\.wxss$/, 'scss'],
[/\.wxml$/, 'html'],
[/\.json$/, 'json']
],
printWidth: 80,
trailingComma: 'none',
tabWidth: 2,
semi: false,
singleQuote: true,
useTabs: false,
bracketSpacing: true,
insertPragma: false,
arrowParens: 'avoid',
htmlWhitespaceSensitivity: 'ignore'
},
plugins: [
require('wxml2vue'),
require('wxjs2vue'),
require('wxss2css'),
require('wx2qh-plugin-preset')
]
CHANGELOG
v1.0.1
FEATURE
- 插件化重构
- 根据文件后缀格式化代码(prettier)
- 支持在行内样式中转换 rpx2px
- 支持 hidden=x 转为 v-show="false"
- app.json 配置默认添加 sdkversion 字段
v0.1.2-alpha.0
FEATURE
- 支持样式比例转换
- 支持 template 标签转换
- 支持 include 和 import 标签转换
- 自动生成添加了 360 小程序构建工具 latest 版本依赖的 package.json 文件
FIX
- css 迁移时 @keyframes 100% 没有正确转化 100% => .is_100%
能力
我们将以下几个方面来阐述 wx2qh 的迁移能力,您能够了解到它做了什么
视图
wxml 语法转换为 vue 语法
转化关系对应
一、attrs
将微信小程序标签上的属性绑定语法变为 360 小程序绑定语法,如:
微信小程序 | 360 小程序 |
---|
id="item" | id="item" |
id=" {{item}} " | :id="item" |
二、components
将微信小程序组件转换为 360 小程序组件。如果 360 小程序组件没有该组件,则不转换且给出提示。
目前支持组件转换的列表如下:
微信小程序 | 360 小程序 |
---|
movable-view | se-movable-view |
movable-area | se-movable-area |
scroll-view | se-scroll-view |
swiper | se-swiper |
swiper-item | se-swiper-item |
view | se-view |
icon | se-icon |
progress | se-progress |
rich-text | se-rich-text |
text | se-text |
button | se-button |
checkbox | se-checkbox |
checkbox-group | se-checkbox-group |
form | se-form |
input | se-input |
radio | se-radio |
radio-group | se-radio-group |
slider | se-slider |
switch | se-switch |
audio | se-audio |
image | se-image |
video | se-video |
ad | se-ad |
block | template |
三、directives
将微信小程序的指令转换为 360 小程序指令:
微信小程序 | 360 小程序 |
---|
wx:for="{{array}}" wx:for-item="xxx" wx:for-index="yyy" | v-for="(xxx, yyy) in array" |
wx:if | v-if |
wx:elif | v-else-if |
wx:else | v-else |
四、template
将微信小程序的 template 转换成 360 小程序中的 component,目前仅支持 template 的内容转换。微信小程序源码中,通过<import src="a.wxml"/>
和 <include src="header.wxml"/>
这两种方式引用的地方需要将转换后的内容手动添加过来。
template 模版内容转换
转换前:
<template name="A">
<div wx:for="{{ array }}" wx:for-item="it" wx:for-index="$index">{{it}}{{$index}}</div>
<template name="B">
<div class="x-{{ ccc }}">{{ bbb }} {{ array.length }}</div>
</template>
<template is="C" data="{{ ...xxx, x: xxx[eee + 'c'] ? ccc : 22 }}"></template>
</template>
转换后:
<template name="A" scope="{ array, ccc, eee, xxx }">
<div v-for="(it, $index) in array">{{it}}{{$index}}</div>
<template name="B" scope="{ array, bbb, ccc }">
<div :class="\`x-\${ccc}\`">{{ bbb }} {{ array.length }}</div>
</template>
<component is="C" v-bind="{ ...xxx, x: xxx[eee + 'c'] ? ccc : 22 }"></component>
</template>
template 引用转换
转换前:
<template is="{{staffName}}" data="{{...staffA}}"></template>
转换后:
<component :is="staffName" v-bind="{ ...staffA }"></component>
样式
基础组件标签转换
- wx 小程序里有,但 360 小程序里没有的基础组件标签:不用转换,直接将该节点转化为注释节点。
- wx 小程序里有,但 360 小程序里没有且与 html 原生标签同名的基础组件:不做处理,直接使用原生标签。
- wx 小程序里有,且 360 小程序里也有的基础组件标签:统一转换为 class 选择器形式,.is_[基础组件标签名]。
单位转换
遍历所有 css 属性,将 wx 小程序的 rpx
转化为 px
。
框架
由于我们底层是基于 Vue 的,文档从 Vue 的角度说明迁移后的能力支持
接口支持情况如下,其他的 选项(option) 请参考能力支持表格:
- App
- 支持 data 和 methods 直接定义在 选项(options) 根级别上
App({
func1: function() {},
data1: 111
})
- getApp
- 由于框架实现差异,getApp 只能在 beforeCreate 或之后的 vue 生命周期中获取
let app = null
Page({
beforeCreate() {
app = getApp()
}
})
- Page
- getCurrentPages
- Component
- Behavior
能力支持
注意格式问题
-
代表待定,目前不支持,未来可能支持N
代表遗弃,现在和未来都不支持Y
代表已支持,(框架支持)
微信小程序 | 描述 | Seapp | Version |
---|
App : onLaunch | lifecycle -监听小程序初始化 | beforeCreate created beforeMount mounted | |
App : onShow | lifecycle -监听小程序启动或切前台 | - | |
App : onHide | lifecycle -监听小程序切后台 | - | |
App : onError | 错误监听函数 | - | |
App : onPageNotFound | 页面不存在监听函数 | - | |
Page : data | 页面的初始数据 | data | |
Page : onLoad | lifecycle -监听页面加载 | mounted | |
Page : onShow | lifecycle -监听页面显示 | onShow | |
Page : onReady | lifecycle -监听页面初次渲染完成 | onReady | |
Page : onHide | lifecycle -监听页面隐藏 | onHide | |
Page : onUnload | lifecycle -监听页面卸载 | beforeDestroy destroy | |
Page : onPullDownRefresh | 监听用户下拉动作 | - | |
Page : onReachBottom | 页面上拉触底事件的处理函数 | - | |
Page : onShareAppMessage | 用户点击右上角转发 | - | |
Page : onPageScroll | 页面滚动触发事件的处理函数 | - | |
Page : onResize | 页面尺寸改变时触发 | - | |
Page : onTabItemTap | 当前是 tab 页时,点击 tab 时触发 | - | |
Component : properties | 组件的对外属性 | props | |
Component : data | 组件的内部数据 | data | |
Component : observers | 组件数据字段监听器 | watch | 2.6.1 |
Component : methods | 组件的方法 | methods | |
Component : behaviors | 类似于 mixins 和 traits | mixins | |
Component : created | lifecycle -在组建实例创建时执行,不能调用 setData | beforeCreate created | |
Component : attached | lifecycle -在组件实例进入页面节点树时执行 | beforeMount | |
Component : ready | lifecycle -在组件布局完成后执行 | mounted | |
Component : moved | lifecycle -在组件实例被移动到节点树另一个位置时执行 | - | |
Component : detached | lifecycle -在组件实例被从页面节点树移除时执行 | destroy | |
Component : relations | 组件间关系定义 | - | |
Component : externalClasses | 组件接受的外部样式类 | Y | |
Component : options | 一些选项(文档中介绍相关特性时会涉及具体的选项设置) | - | |
Component : lifetimes | 组件生命周期生命对象 (权重 > normal) | Y | 2.2.3 |
Component : pageLifetimes | 组件所在页面的生命周期声明对象 | - | 2.2.3 |
Component : definitionFilter | 定义段过滤器,用于自定义组件扩展 | - | 2.2.3 |
All : 其他 | 开发者可以添加任意的函数或数据到 Object 参数中,在页面的函数中用 this 可以访问 | Y | |
接口
由于 360 小程序 api 与微信小程序 api 无论是方法名,参数形式基本保持一致,所以 360 小程序实现了的 api 迁移后都支持
360 小程序 api 文档