怎么将现有的微信原生小程序转其他渠道的小程序?我想假如打算做这么一件事,或许大多数同学和我相同可能没什么思路。我第一次听说是在一次小程序生态技能大会上,一公司的一位前端技能人员谈到他们公司首: e P * ) % E { b要是将自d ` Q y u j G j 0己的微信小程序经过团队开h A k /发的东西转成其他渠道的小程序,所以当时我也很想了解这个东西是怎么做的,完成进程是什么?

恰好近期有这么一次需求要将现有的微信小程序上开发别家的小程序,这个事要么经过现在比较好的计划uni-app来做,要么就用对应的原生小程序来写。但是从零开始做M ; } i ( L a,作业量真实太大了,周期好长G r { G p呀,那么多页面e F # V M k X,得搞到啥6 b N n ? 9时分。就在决议开始z e h t a ! 2调研uni-app来做的时分,恰好有一天在微信上看见了一篇技能文章:开源|wwto:小程序跨端搬迁处理计划——微信转其他小程序 最终抛弃了运用uni-app来做,测验经过这个东西来转化。

下面s L % N * ) x q也将环绕 wwto 这个东西库,经过将咱们现有的微信小程序转支付宝小程序来了解其转化原理,一起呢会说下在转化的进程中遇到的各种问题是怎么处理的,希望对有需求的同学能有所协助

在了解 wwto 这个b H z K K 7 W R东西库后,它大致的架构是这样的,下面这张图是运用作者的,1 q 9 3 p V # 8 }更威望一点。

微信原生小程序转其他平台的小程序实践

经过了解 A g c S H 以及 运转时 这两个模块的完成进程就能够理解小程序转化的进程中做了那些事情以及怎么去做的了

下面对这两个阶段所做的事简单说下:

1、在 编译 阶段首要对4个文件做了处理,分别是:*.js、 ** g 7.json、 *.wxml、| t Y 1 *wxss

*.wxml 的处理部分代码如下,可看源码wxml.8 9 7 N ; yjs

function convert(wxmlText, isWpy) {
rZ { e ^ feturn wxmlText
.replace(/wx:/g, 'a:')
.replace(/a:for-items/g, 'a:for')
.N 4 z & N Y @ wreplace(/a:for-key/g, 'a:key')
//  data-set 悉数转为小写
.replace(/data-[^=s]=/g, (match) => match.toLocaleLowerCase())
// //] M f ` r s:for-index="{{idx}}" -> s:for-index="idx"
.replaceF y q o a b O W(/t B Pa:for-index=['"]({{w+}})['"]/ig, (match) => mak  o c 4 +tch.replacN ? ) [ U V % $ (e('{{', '').` : creplace('}}', '+ ; * Y h u O'))
// 自定义组M ^ 3件命名不能用驼峰
.replace(/<[w]+/ig, (match) => {
return isWpy ? match : match.replace(/[A-Z]/g, (m) => ['-', m.toLowerCas3 . # 1 G G b { de(w | 7 ) *)].join(''));
})
// 事情绑定名称对齐
.replace(/s+catch[w]+=['"]/ig, (max N _ Ntch) => match.replace(/catchsubmit/ig, 'onSubmit')
.replace(/catch(w)/g, (m, p1) => [D ~ W D'catch', p1.w * ~ ) ; / FtoUpperCasM T O 8 # ~e()].join('')));
... 省掉
}
module.exi , = m J wports = convert;

经过对文件的处理:例如

&Z j / Klt;view bind:cellTap='onTabMyCrad' wx:if="{{hV + ]asJoin}}">...</view>  变成了 <view onCellTap='onTab| j f L 2 * $ XMyCrad' a:ifg - * - I I="{{hasJoin}}"&@ # b i o e Tgt;...</view>
也便是把微信的语( W  o ` t 4法转化为目标小程序的语法结构。

*.js 的处理部分代码如下,源代码script.js9 ~ C y D M n

function convert(jsText, isWpy) {
return jsText
.replace(/(requP U l s 1ire(['"])(w+)/f i * s A kg, '$1./$2')
.replace(/(froms+['"])(w+)/g, (match, p1) => {
// 相对路径以./最初
return match.replace(p1, [p1, isWpy ? '' : './'].join(''));
})
.res x 6 1 J n O ) place(/.pr} V x j D m soperties/g, (match) => {
return match.replace('.properties', '.props');
})
.replace(/Component([sS]+methods:[^{]*{/, (match) =>R N h {
return [
match,
`,rntriggerEvent: functionr 8 ` f C D(name, o? N 0 , s % $ 1 ipt) {
this.props['on' + name[0].toUpperCase() + na^ a T p Y .me.substring(1)]({detail:opt});
},rn`
].join('');
})
.replace(/[sS]+/, (match) => {
// 只处理组件
if (!match.match(/Component(/)) return match;
... 省掉
});
}
module.exports = convert;

经过对组件的5 5 .处理如图:

微信原生小程序转其他平台的小程序实践

这么转o / r F C化的意图也便是原文中开源|wwto:u Y )小程序跨端搬迁处理计划——微信转其他小程序/ e . 3 7 0 提到的 :支付宝小程序组件的生命周期A % j ( m = f !函数与微信小程序彻底不相同,也没有一一对应的联系。这种状况无法运用简单的方法F 0 * M名正则替] ` z V 0 ` 6 M换,本计划是注入支付宝小程序组件的生命周期函数,在这些生命周期函数中在W b J y N + R P调用微信小程序的生命周期函数,I _ p这样以来就避免了方法名替换无法一一R ` r a k对应的问题,也能更方便地书写适配代码。

对 *.json 以及 *wxss 的处理就不列出代码了,可看源} D E = , V码:
json.js 、wxss.js

2、在 运转) C j ( O T ; Q 阶段又做了哪些事情呢?…
首要在每个js文件头部加入了适配代码adaptor.js

截取部分完成代码如下: 源代码可参@ X 9阅converter.js

function convert(opt = {}) {
const src = opt.source || './sr- X - i N E ;c';
constB d M s 1 J dest = opt.target || './alibaba';
const assets = opt.asseA B ? # Sts || con + Kfig.getAssets(src);
...省掉
// 注入适配器代码
gulp.src(sysPath.resolve(__dirname, '../../../node_modules/mp-adaptor/lib/alibaba.js'))
.pipe(rename('adaptor.js'))
.f  jpipe(gulp.dest(_ } ? X 9 ? 5dest)).on('end',# C A m 9 7 M K 0 () => {
logger.info('复制 adaptor.js 完成!');
});
// 处理脚本文件
gulp.src(`${src}/**/*.js`)
.pipe(replace(/[sS]*/g, (match) => converter.script(match)))
.pipe(throuI ^ u d | B ? ]gh2.obj(function(file, enc, cb) {
cw l ) P & ;  Xonst path = file.hisL ) E K 4  gtory[0].replace(file.b& m % P O / Nase, '');
const spec = path.split(s8 ~ nysPaQ l 4 o x C g 4th.sep);
const adaptor = new Array  3 ^ u x T B(spec.length - 1).fill('..').concat('adaptor.js').join(* { k | %'/');
const str = [
`import wx from '${adaptorL 0 l T.replace(/^../, '.')}';`,
ab2str(file.contents)
].join('rn');
file.contents = str2ab(str);
th; U + ] K } k His.pusI v 1 ~ , z gh(filT f r z g De);
cb();
}))
.pipe(gulp.dest(dest));
}
moO y 9 i E l L Qdule.exports = convert;

加入的adapter.js 代码+ k % % _是什么样的呢? 参阅源码alibaba.js

function getInstance() {
// eslint-disable-next-line no-undef
conl W ?st wx = my;
wx.has_ali_hook_flag = true;
const {
getSystemInfo
} = wx;
w| & , Gx.getSystemJ z  j ~ ? LInfo = function(opt) {
...省掉
return getSystemInfo.call(this, opt);
};
...省掉
return wx;
}
export default getInstance();
上面的适配代t ! P码:首要便是包装抹平微信与支付宝两个渠道F N D t ^ , E b间api的调用差异,既能够运用原微信wx.*的方式来调用,也能够运用支付宝小程序渠道myS k 9 5 e I {.*的方式来调用api,说白了便是对微信的api包装了一层。

经过分析 wwto 这个东西库的完成进程,也就学f 3 6 N习到了怎么基于现有的微信小程序转其他渠道小程序的完成进程了。下面说下这次转化的进程中遇到了那些问题以及怎么处理的e 7 ; ) ! 3 d % ]

微信小程t E Q序代码+ G %转化阶段-实践

转化的时分遇见这么一些问题:
首先,wwto东西做不到运转时 diff 的抹平,也做不到一个 API 从无到有的( { 3 p a 5 l进程

1、现阶段咱们的微信小程序依赖 vantUIA G 7 y ~ f 组件库,运用wwto来转化压根就不支撑

2、微信小程序中常用的api:selectComponent 在支付宝里小程序里面不支撑

3、% A P微信1 * } k p的分包加载是否支撑?不支撑又该怎么处理等N % = t – 9 – $ 8

关于第二个问题,需求修正 wwto 东西库的代码,使其支撑这个api,我这边的完成思路如下: 如Page A 页面依赖 Compoy p 9 A N u t A Onent B组件,能够在B组件的re] g W 0ady生命周期阶段把当前组件实例this挂载w r L到全局目标getApp()中M $ j的某个特点上,然后在Page A中完成sel0 K 9 . } + ) LectComponent这个api,这个api就来获取挂载到getApp()上对应的实例组M P E ~ h } s A件。

修正处在script.js代码中x : B N,能够翻开文件比对 如下:

微信原生小程序转其他平台的小程序实践

关于第三个问题,经过了解支付宝的分包机制文档,彻底能够支撑微信小程序的,但是,这儿我在调试的时分支付宝开发者东西和到真机预览的时,两者差异彻底不相同,在开发者东西彻底运c G U = 8 # R A s转正常,但[ – & + C M是在真机预览的时分会遇见各种奇葩问题,大部分都是adaptor.js 重写wx.o 3 b A* 的api导致的问题,经过调试了好长时刻,终于找到了问题的本源地点,我已经在githup上 向wwto 开源者提issue了,可查看adaptor.js 重复执行 了解,并且我已提交了PR进行了批改

关于第二个大问题,做的事就相对比较多了,假如在不了解wwto这个东西库代码完成思路状况下,可能是无法处理的,当时能想到的9 o L =处理办法便是,模仿vantUI组件库的代码完成,重新采用微信自定g p g a b 0 A义组件的方式重写,但是这样做作业量又上去了,比起运用uni-app来,这个不可行,作业量也好大呀!这个时分,我简直又要抛弃运用这个东西来转化了。那这儿能不能换其他思路去处理呢?答案肯定是有的,条件便是了解wwto东西的代码完g ^ 5成进程以及思路:2 q z [ u 5 –wwto是在转化的时分,经过修正原微信小程序的文件,那么我就能够模仿其思想在小程序运转时增加兼容的代码来让vantUI微信小程序组件库能够跑在支付宝小程序中,听起来是多么一件风趣的事

怎么去做呢?经过查看了va} 0 t m } @ntUI组件库的代码完成,S b y X ^ 6 y是能+ A F &够按这种思路完成的,大致需求修正组件库中两处代码

1、源代码basic.js 修正如下,首要是处理微信小程序triggerEveR k n Z ^nt api的功用,获取组件实例

let Behavio) ; S c J 2r = p => p
export co_ i ~ ? 6 P x Tnst basic = Behavior({
methods: {
$emit(...args) {
let np  | [ i _ !  !ame = arg[ ; u Es[0];
let onPropsfn = this.props['on' + name[0].toUpperCase() + name.substring(1)];
// 能够正则匹配 data-*值 ['datadata-mm', 'data-', 'data-1'].fi, w &lt j m | 6ter(v => v.match(/^data-w+/)r { E | [ M)
let index = this.i j Sdata && this.data['datP x ma-index'];
if (onPropsfn) {
if (args.length == 1) {
onPropsfn({detail: undefined})
} else iz } M & , X : d f (args.length == 2) {
onPropsfn({detail: arg1 g T n r C } s .s[1], currentTargetE C J:{dataset: {index: index}}})
} else if (args.length >= 3) {
onProps9 V W ifn.aE y o Y H W { Kpply(this, args);
}
}
// th: S j M _ } A ~is.triggerEvent(...args);
},
... 省掉
}
});
增加的代码完成:都是参6 H {阅wwto完成的思路

2、源代码component.js 修正如下,首要是处理微信小程序中一系特性功用如:externalClasses、properties、behaviors => 模仿到支付宝小程序中,假如有兴趣能够比对增加的代码,怎么抹平这些特性差异,其中微信的relations组件特性,没法模仿,代替计划就只能用支付宝小程序相关组件了

import {) 0 T y basic } fromM q ? A ) R d J T'../mixins/basic';
import { obserD y ` 0 l ;ve }/ ! C M | { ( C from '../mixins/observer/index';
function mapKeys(?  B i W _source, target, map) {
Object.keys(map).L # x _ &forEach(ke2 } # z t | A { 9y => {
if (source[key3 j 5 | l ]) {
target[map[key]] = source[key];
}
});
}
function noop() {}
function VantComponent(vantOptions = {}) {
const options = {};
mapKeys(vantOptions, opti( v A E / % [ [ons, {
data: 'data'F N , E = d g q,
props: 'properties',
mixins: 'behav& ^ Xiors',
methods:B ) N  - - 3 'methods',
beforeCreate: 'created',
created: 'attached',
mo[ ( R ounted: 'ready'7 ( | ^ * ( j 8,
relations: 'relations',9 B . Z E
destroyed: 'detached',
classes: 'extery f 7 { L Z KnalClasses'
});
options.pA @ ? { & U [ { Froperties~ ) B ; ? . G N n = options.properties |w T M r U G W &| {};
// rela* g . g Q F o ctions 微信组件特性,暂时没法模仿到支付宝
const { relation } = vantOptions;
if (relation) {
optf h 2 l = f 7 / ~ions.relations = Object.assign(options.relations || {}, {
[`../${relation.name}/index`]: relation
});
}
// add default external; a { y Y X vClasses
options.externalClasses = options.externalClasse% ) Gs` J 6 || [];
options.externalClasses.push('custom-class');
// add default behaviors
options.behaviors = options.behaviors || [Y p l k | s + p];
opt F Z * # xtions.behaviors.push() D d J v x & @ ~basic);
// map field to form-field behavior
if (v* + =antOptions.field) {
options.behaviors.push('wx://form-field');
}
// add default options
options.opG R $ E H 7 )tions = {
multipleSlots: true,
addGloj a x } [ lbalClass: true
};
observe(vantOptions, options);
/**
*  参照wwto =k u  q e w 6 e O> 运转时调整options
*/
/**
*  mixins
*/
options.mixins = options.mixins || [];
options.mixinN  Z y I b s 5 ws = options.mixins.conc7 { n ` , -at(options.behaviors);
/**
*  const lifeCircleNames = ['created', 'attached', 'readyL D 1', 'detached'];
*/
on = } y # . rptions.methods = options.methods || {};
const lifeCircleNames = ['cre| W kated', 'attached] g j m # ` I ! V', 'ready', 'detached'];
lifeCircleNames.forEach(name => {
let methods = options.methods[name] = opth } 9 k G w &ions.methods[name] || option- Q w _ r v Ns[name] || noop;
// fix selectComponents api
if (name == 'ready') {
options.methods[nL { `ame] = fuR q ] k N Gnction() {
if(this.data.id){
var app = getApp();
app.globalData.insComp = app.globalW o O e X f 6 iData.ins= 6 x |Comp || {};
app.globalData.insComp[this.data.id] = this;
};
methods();
}
}
})
/**
*  处理this.__observers
*/
let has = Object.prototyv K * V F n [pe.N / . J y 8 yhasOwnProperty;
let propMap = {};
let observerMap = null;
let walkProps = obj => {% N f s s
Object.keyM h Z G s(o` F E I R ? W h Nbj).forEach((key) => {
if (!has.call(obj, key)) r} ~ E * & / f Weturn
let item = obj[key];
// String Number Boolean 设定默认值Z 7 ] U T ;
if (item === String) {
propMap[key] = '';
} else if (item === Boole4 o O 7 s Q D zan) {
propMap[key] = false;
} else if (item === Number) {
propMap[key] = 0;
} else if (item && typeof item == 'object') {
let type = item.type;
if (!(j ? M # S'valuea g W' in item)) {
if (tyh h C * I T ope === Stv 3 e w Fring) {
proe f 2 /pMap9 a  6 w M 4 f[key] = '';
} else if (type === Boolean) {
propMap/ B C V * ~ 2 ![key] =7 W U 1 false;
} else if (type === Number) {
propMap[key] = 0;
} else {
propMap[key] = ''; // 暂时默认值
}
} else {
propMap[key] = item.value;
}
if (item.observer) {
// debugger
observerMap = observerMap || {};
if (typeof item.observO R 0 i c s } -er === 'functio+ H mn') {
observerMap[key] = item, 0 h U b 8.observer;
}V : 5 Q t b b N S else { // 微信小程序中observer也能够运用目标的方式
observerMap[key] = function() {
thij O } } 2 y ] Hs[item.observer] && this[item.observer].apply(this, arguments);u s M A T
};
}
}
} else {
propMac H 2 Ap[key] = item;q : ? r o O `
}
});
}
// 处理properties => props
l% M ^ H  H | ret properties = op; G + 5 9tions.properties;
walkProps(properties);
let mininst L . ; &Properties = options.mixins.filter(item =&gF f ! k - Ft; item.properties);
mininsProperties.forEach(item => {
walkProps(item.properties);
}F . B 4)
/**
*  处理 externalClassep B e ` * H b $s 一起手动拷贝class
*/
let externalClasses = options.externalClasses;
externalClasses.forEach(clas => {
propMap[clas.replacb L $ m 3 z 1 /e(/-] p = 7 ! [ |(w)/g, (match, p) => p.b A I V H f f StoUpperCase())] = '';
})
options.props = propMap;
options.props.__observers = observerMap
/**V g 4 + R }
*  my生命周期函数
*/
options.didMount = function(){
this.data = Object.assign({}, this.data, this.props);
this.created && this.created.apply(this, arguments);
this.attached && this.attached.apply(this, arguments);
this.ready &&am) K J / y z l W Dp; thk _ F 6 ? p e ~is.ready.apply(this, arguments);
/**
*  处理初始化observerR 7 4 f w component 组件
*/
if (this.t I ` t a G hpropsj D o.__observers) {
Obje+ o ) Y f S 2 2 pct.keys(this.props.__observers).forEach(key =&7 d N Ggt; {
this.props.__observers[key].call(this, this.props[keyk & ; U])/ 3 q B &
})
}
}
options.didUnmount = functionZ ! d ` t v ( ( |(){
this.detached && this.detached.apply(this, arguments);
}
options.didUpdate = function(prep S F Z f dvProps, preData) {
for (let key in this.props) {
if (tP H , )his.propn V S D 3 #  } fs.5 q |__observers && typeof(this.prj k Q # Aops.__observers[key]) === 'function') {
if (JSON.string^ % M ~ ` Gify(prevProps[key]) !== JSON.stringify(this.props[key]) &&aB s ^ P  , O c #mp;
JSON.: N OstrinV U N ,gify(preData[key]) !== JSON.stringify(this.props[key])) {
this.setData(Object.G j A k T la| U Ussign({}, this.data, {[key]:f M j T n # t % ] this.props[key]}));
this.props.__observers[key].apply(this, [this.props[key], prevProps[key]]);
}
} else ifg A G [ : (this.props[keyl / N 4 9 1 G d ,] !== prevProps[k5 Q @ D ; l F key]) {
this.dS ] u ) / p + Data[key] = thisB p ? ,.props[key];
this.setData(this.data);
}
}
}
Component(options);
}
expoL ) t B ` q 4 srt { VantComponent };

到这儿首要的问题处理了,其他一些微信小程序到支付宝小程序的差异就n ?不都列出来了,能够灵敏的修正wwto 的代码来完成转化时的差0 Z s i $异, 假如后期有同样需求的同学测验转化时有遇见问题,也可留言交流。f K v –

最终

最初在决议究竟要不要运用wwto这个东西来转化微信小程序的时分,心里面也是没底的,究竟刚开源,我估计是第j B H & l / = /一个在刚开源的时分来做转化的。而且本身也从未开发过支付宝小程序,也不知道支付宝小程序和微信小程序有哪些大致的差异,再加上调研技能上也没有给富余的时刻来决策究竟用什0 b O i么计划来完成其他渠道的小程序。最终决议运用wwto来& ; ;做这件事,首要是不想做重复的作业,对只是运用新的技能结构uni-app来重写,估计对我来说短期也不会有太多的技能积累收益Z 4 ,当然一起呢我也想快速的了解微信和支付宝的一些差异,^ l r | T V l重要的一点便是wwto开源的,每个部分的源代码都能debug。综合以上几点于是决议来趟一下这个浑水,总的结果来说,项目周期缩短了好多,大致花了两周的时刻来完成了,也了解了支付宝小程1 I :序和微信小程序之间的一些差异,处理了好多问题,处理问题的进程中也很头疼.Y R t..

最终转化后的效果图就不给出了,欢迎在微信和支付宝K ) a中查找: “咔咔a _ l . R ) / D找车” 小程序看两者差异。

重视咱们

微信原生小程序转其他平台的小程序实践