@qihoo/wx2qh

1.0.6 • Public • Published

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
    • object
    • 转换 processor 执行器
  • compilation
    • map
    • 内置 processorParserCompiler 存储容器
  • output
    • string
    • 编译后文件的输出路径
  • options
    • object
    • 配置选项,包括 cli 选项,prettier 配置等。等同于 PRESET
  • ticks
    • set
    • api.nexttick 传入回调的存储容器
  • files
    • array
    • vfile 存储容器

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) {
  // 1. (name: string, matcher: string): Processor {}
  const processor = api.processor('demo', '*.wxml')

  // 2. (name: string, matcher: RegExp): Processor {}
  const processor = api.processor('demo', /\*.wxml/)

  // 3. (name: string, matcher: function): Processor {}
  const processor = api.processor('demo', file => file.path === 'index.wxml')

  // 4. (name: object): processor {}
  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) {
    // some handling process...
  }
}

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) {
  // 1. (type: string, log: string): void {}
  api.logger('error', 'I am a error log')
  api.logger('warn', 'I am a warning log')
  // 2. (type: string, log: object, canSave: boolean): void {}
  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
    // plugin with options:
    .use(plugin, {})
    // Plugins:
    .use([plugin, pluginB])
    // Two plugins, the second with options:
    .use([plugin, [pluginB, {}]])
    // Preset with plugins and settings:
    .use({
      settings: [(position: false)],
      plugins: [plugin, [pluginB, {}]]
    })
    // Settings only:
    .use({ settings: { position: false } })

  function plugin() {}
  function pluginB() {}

  return processor
}

Attacher

processor 插件仅仅是一种概念,而 attacherprocessor 插件的实现,它可以访问和修改 processor 的属性和方法,尤其是可以为 processor 添加必要的 ParserCompiler 从而实现 textast 的反序列化,asttext 的序列化。

例子

demoProcessor.js:

module.exports = function demoProcessor(api) {
  const processor = api.processor('demo', 'demo.wxml')

  // 获取内置的 compilation ,可以用来处理 xml 类型文件
  const wxmlCompilation = api.compilation.get('wxml2vue')

  processor
    // 自定义
    // .use([parser, compiler])
    // 内置
    .use(wxmlCompilation)
    .use(demoAttacher)

  return processor
}
function demoAttacher(options) {
  return span2div

  function span2div(tree) {
    // process tree...
    const spanElement = tree.children[0]
    const spanText = spanElement.children[0]
    spanElement.tagName = 'div'
    spanText.value = `I am a div`
  }
}
// 自定义 Parser ,也可以使用第三方 Parser 以及 内置的
function parser(settings) {
  this.Parser = parser

  function parser(doc) {
    return {
      type: 'default',
      contents: String(doc),
      toString() {
        return this.contents
      }
    }
  }
}
// 自定义 Compiler ,也可以使用第三方 Parser 以及 内置的
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 如果返回 Errorprocessor 将停止工作。
  • 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 配置如下:

  // 是否需要合并内置 options
  root: true, // boolean
  // === wx2qh cli options ===
  file: '**',
  ignore: [],
  preset: undefined,
  ratio: 2,
  // ====
  // === prettier config ===
  // 是否开启 使用 prettier 格式化代码
  prettier: true,
  // prettier 配置
  prettierrc: {
    // 默认 parser
    parser: 'babel',
    // 根据条件选择合适的 parser
    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'
  },
  // ===
  // 插件列表 processor
  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>

样式

基础组件标签转换

  1. wx 小程序里有,但 360 小程序里没有的基础组件标签:不用转换,直接将该节点转化为注释节点。
  2. wx 小程序里有,但 360 小程序里没有且与 html 原生标签同名的基础组件:不做处理,直接使用原生标签。
  3. 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
    • 支持 this.setData
  • getCurrentPages
    • 暂不支持(需要开发者二次开发改写)
  • Component
    • 选项(options)支持情况见下表格
  • 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 文档

Package Sidebar

Install

npm i @qihoo/wx2qh

Weekly Downloads

19

Version

1.0.6

License

MIT

Unpacked Size

84.1 kB

Total Files

6

Last publish

Collaborators

  • twq0607
  • lizheming
  • lgy573
  • wendy_cy
  • panhongxue
  • willemis
  • heweixiao