How to convert the existing micro channel native small program to other platform small program? I think if you plan to do such a thing, probably most of the students like me probably have no idea. I first heard that in a small program ecological technology conference, a front-end technical personnel of a company talked about their company is mainly their own micro channel small program through the tools developed by the team into small programs of other platforms, so AT that time I also want to know how to do this tool, what is the realization process?

Just recently, there was a demand to develop other small programs on the existing wechat applet. This can either be done through uni-app, a better solution now, or written with the corresponding native applet. But starting from scratch, the workload is too much, the cycle is so long, so many pages, it has to be done at some time. Just when I decided to research uni-App, I happened to see a technical article on wechat one day: Open Source | WWTO: Cross-terminal Migration Solution for Small Programs — wechat to other small programs finally gave up using Uni-App and tried to convert through this tool.

The following will also be around wwTO this tool library, by transferring our existing wechat small program to Alipay small program to understand its conversion principle, at the same time, it will say how to solve the various problems encountered in the conversion process, I hope to help students who have need

After learning about the wwTO tool library, its general structure is like this, the following diagram is used by the author, a little more authoritative.

By understanding the compilation and runtime implementation of these two modules, you can understand what is done and how to do it in the process of applet conversion

Here’s a brief description of what you do in both phases:

1. In the compilation stage, four files are mainly processed, respectively: *.js, *. Json, *.wxml, * WXSS

*.wxml processing part of the code as follows, can see the source code wxml.js

function convert(wxmlText, isWpy) {
  return wxmlText
    .replace(/wx:/g.'a:')
    .replace(/a:for-items/g.'a:for')
    .replace(/a:for-key/g.'a:key')
    // data-set is all lowercase
    .replace(/data-[^=\s]=/g, (match) => match.toLocaleLowerCase())
    // // s:for-index="{{idx}}" -> s:for-index="idx"
    .replace(/a:for-index=['"]({{\w+}})['"]/ig, (match) => match.replace('{{'.' ').replace('}} '.' '))
    // Custom component names cannot be humped
    .replace(/<[\w]+/ig, (match) => {
      return isWpy ? match : match.replace(/[A-Z]/g, (m) => [The '-', m.toLowerCase()].join(' '));
    })
    // Event binding name alignment
    .replace(/\s+catch[\w]+=['"]/ig, (match) => match.replace(/catchsubmit/ig.'onSubmit')
      .replace(/catch(\w)/g, (m, p1) => ['catch', p1.toUpperCase()].join(' '))); . Omit}module.exports = convert;
Copy the code

Through the processing of documents: e.g

<view bind:cellTap='onTabMyCrad' wx:if="{{hasJoin}}">.</view>Turned out to be<view onCellTap='onTabMyCrad' a:if="{{hasJoin}}">.</view>That is to convert the grammar of wechat into the grammar structure of the target applet.Copy the code

*.js processing part of the code as follows, the source code script.js

function convert(jsText, isWpy) {
  return jsText
    .replace(/(require\(['"])(\w+)/g.'$1 / $2')
    .replace(/(from\s+['"])(\w+)/g, (match, p1) => {
      // Relative paths start with a./
      return match.replace(p1, [p1, isWpy ? ' ' : '/'].join(' '));
    })
    .replace(/\.properties/g, (match) => {
      return match.replace('.properties'.'.props');
    })
    .replace(/Component\([\s\S]+methods:[^{]*{/, (match) => {
      return [
        match,
        `,\r\ntriggerEvent: function(name, opt) { this.props['on' + name[0].toUpperCase() + name.substring(1)]({detail:opt}); },\r\n`
      ].join(' ');
    })
    .replace(/[\s\S]+/, (match) => {
      // Only components are processed
      if(! match.match(/Component\(/)) returnmatch; . Omit}); }module.exports = convert;
Copy the code

Through the processing of components as shown in figure:

As mentioned in wwTO, the life cycle function of alipay’s applets is completely different from that of wechat’s applets, and there is no one-to-one correspondence. This kind of situation will not be able to use simple method of regular replacement, this solution is injected into pay treasure small program component life cycle function, in the life cycle function in the life cycle of a call WeChat small program function, since it avoids the method of replacing one-to-one problem, can also adapt more easily write code.

*. Json and * WXSS processing code is not listed, you can see the source code: json.js, wxss.js

2. What did you do at runtime? . The main adaptation code adaptor.js is added in the header of each JS file

The intercept code is as follows: Refer to Converter. Js for the source code

function convert(opt = {}) {
  const src = opt.source || './src';
  const dest = opt.target || './alibaba';
  constassets = opt.assets || config.getAssets(src); . omit// Inject adapter code
  gulp.src(sysPath.resolve(__dirname, '.. /.. /.. /node_modules/mp-adaptor/lib/alibaba.js'))
    .pipe(rename('adaptor.js'))
    .pipe(gulp.dest(dest)).on('end', () => {
      logger.info('Copy adaptor.js is done! ');
    });

  // Process the script file
  gulp.src(`${src}/**/*.js`)
    .pipe(replace(/[\s\S]*/g, (match) => converter.script(match)))
    .pipe(through2.obj(function(file, enc, cb) {
      const path = file.history[0].replace(file.base, ' ');
      const spec = path.split(sysPath.sep);
      const adaptor = new Array(spec.length - 1).fill('.. ').concat('adaptor.js').join('/');
      const str = [
        `import wx from '${adaptor.replace(/ ^ \ \. /.'. ')}'; `,
        ab2str(file.contents)
      ].join('\r\n');
      file.contents = str2ab(str);

      this.push(file);
      cb();
    }))
    .pipe(gulp.dest(dest));
}

module.exports = convert;
Copy the code

What does the adapter.js code look like? Reference source code alibaba. Js

function getInstance() {
  // eslint-disable-next-line no-undef
  const wx = my;
  wx.has_ali_hook_flag = true;
  
  const {
    getSystemInfo
  } = wx;
  wx.getSystemInfo = function(opt) {... omitreturn getSystemInfo.call(this, opt); }; . omitreturn wx;
}

export defaultgetInstance(); The above adaptation code: mainly is the packaging to smooth the difference between wechat and Alipay platform API call, can use the original wechat WX.* way to call, can also use alipay small program platform my.* way to call API, to put it plainly is to wechat API packaging a layer.Copy the code

By analyzing the implementation process of wwTO this tool library, we learned how to transfer the implementation process of other platform applets based on the existing wechat applets. Here are the problems encountered in the process of transformation and how to solve them.

Wechat applet code conversion stage – practice

There were a few problems with the conversion: first, wwTO couldn’t clean up the run-time diff or create an API from scratch

1. At present, our wechat applet relies on vantUI component library, and wwTO conversion is not supported at all

2. API commonly used in wechat applet: selectComponent is not supported in Alipay applet

3. Is subcontracting loading of wechat supported? What should I do if I am not supported?

For the second question, we need to modify the code of wwTO tool library to support this API. My implementation ideas are as follows: If Page A relies on Component B, you can mount the current Component instance this to A property in the global getApp() object during the READY lifecycle of Component B, and then implement the selectComponent API in Page A. This API gets the corresponding instance component mounted on getApp().

The changes are in script.js code, and you can open the file alignment as follows:

For the third question, understanding the mechanism of the subcontract documents, pay treasure can support WeChat small programs, but here when I was in the debug alipay developer tools and preview to the real machine, not the same, the difference in the developer tools run perfectly normal, but the real machine preview will meet all kinds of wonderful work problems, Wx.* API rewritten by adaptor.js is the main cause of the problem. After debugging for a long time, I finally found the root cause of the problem. And I have submitted PR for correction

For the second big problem, and doing things is relatively much, if not understand wwto this tool library code implementation approach, may be unable to solve, can think of a solution is at the time, like vantUI component library code, to adopt WeChat custom components in the form of rewriting, but went up again to do this work, This is not feasible and a lot of work compared to using uni-app. At this point, I almost gave up using this tool for conversion again. Is there another way to solve this problem? The answer is yes, if you understand the code implementation process and ideas of wwTO tool: Wwto is in the conversion, by modifying the original wechat applet file, then I can imitate the idea of running the applet to add compatible code to make vantUI wechat applet component library can run in the Alipay applet, sounds like an interesting thing

How do you do that? By looking at the vantUI component library code implementation, can be implemented according to this idea, roughly need to modify the component library in the two code

1. The source code basic.js is modified as follows, mainly to solve the function of wechat small program triggerEvent API, and obtain the component instance

let Behavior = p= > p
export const basic = Behavior({
    methods: { $emit(... args) {let name = args[0];
          let onPropsfn = this.props['on' + name[0].toUpperCase() + name.substring(1)];
          Data - * / / can be regular match [' datadata - mm 'and' data - 'and' data - 1]. The filter (v = > v.m atch (/ ^ data \ w + /))
          let index = this.data && this.data['data-index'];
          if (onPropsfn) {
            if (args.length == 1) {
              onPropsfn({detail: undefined})}else if (args.length == 2) {
              onPropsfn({detail: args[1].currentTarget: {dataset: {index: index}}})
            } else if (args.length >= 3) {
              onPropsfn.apply(this, args); }}// this.triggerEvent(... args);},... Omit}}); Added code implementation: all follow the ideas of wwTO implementationCopy the code

2. The source code component.js is modified as follows, mainly to solve a series of features in wechat applets such as: ExternalClasses, properties, behaviors => simulation to Alipay applet. If you are interested in comparing the added codes, how can you smooth out the differences between these features? Wechat’s relations component cannot be simulated, so alipay applet related components can only be used as an alternative

import { basic } from '.. /mixins/basic';
import { observe } from '.. /mixins/observer/index';
function mapKeys(source, target, map) {
    Object.keys(map).forEach(key= > {
        if(source[key]) { target[map[key]] = source[key]; }}); }function noop() {}
function VantComponent(vantOptions = {}) {
    const options = {};
    mapKeys(vantOptions, options, {
        data: 'data'.props: 'properties'.mixins: 'behaviors'.methods: 'methods'.beforeCreate: 'created'.created: 'attached'.mounted: 'ready'.relations: 'relations'.destroyed: 'detached'.classes: 'externalClasses'
    });
    options.properties = options.properties || {};
    // Relations wechat component features, temporarily unable to simulate alipay
    const { relation } = vantOptions;
    if (relation) {
        options.relations = Object.assign(options.relations || {}, {
            [`.. /${relation.name}/index`]: relation
        });
    }
    // add default externalClasses
    options.externalClasses = options.externalClasses || [];
    options.externalClasses.push('custom-class');
    // add default behaviors
    options.behaviors = options.behaviors || [];
    options.behaviors.push(basic);
    // map field to form-field behavior
    if (vantOptions.field) {
        options.behaviors.push('wx://form-field');
    }
    // add default options
    options.options = {
        multipleSlots: true.addGlobalClass: true
    };
    observe(vantOptions, options);

    /** * See wwTO => runtime to adjust options */
     
    /** * mixins */
    options.mixins = options.mixins || [];
    options.mixins = options.mixins.concat(options.behaviors);

    /** * const lifeCircleNames = ['created', 'attached', 'ready', 'detached']; * /
    options.methods = options.methods || {};
    const lifeCircleNames = ['created'.'attached'.'ready'.'detached'];
    lifeCircleNames.forEach(name= > {
      let methods = options.methods[name] = options.methods[name] || options[name] || noop;
      // fix selectComponents api
      if (name == 'ready') {
        options.methods[name] = function() {
          if(this.data.id){
            var app = getApp();
            app.globalData.insComp = app.globalData.insComp || {};
            app.globalData.insComp[this.data.id] = this; }; methods(); }}})/** * Handle this.__observers */
    let has = Object.prototype.hasOwnProperty;
    let propMap = {};
    let observerMap = null;
    let walkProps = obj= > {
      Object.keys(obj).forEach((key) = > {
        if(! has.call(obj, key))return
        let item = obj[key];
        // String Number Boolean Sets the default value
        if (item === String) {
          propMap[key] = ' ';
        } else if (item === Boolean) {
          propMap[key] = false;
        } else if (item === Number) {
          propMap[key] = 0;
        } else if (item && typeof item == 'object') {
          let type = item.type;
          if(! ('value' in item)) {
            if (type === String) {
              propMap[key] = ' ';
            } else if (type === Boolean) {
              propMap[key] = false;
            } else if (type === Number) {
              propMap[key] = 0;
            } else {
              propMap[key] = ' '; // Temporary default value}}else {
            propMap[key] = item.value;
          }

          if (item.observer) {
            // debugger
            observerMap = observerMap || {};

            if (typeof item.observer === 'function') {
              observerMap[key] = item.observer;
            } else { // The observer can also use the object form in wechat applet
              observerMap[key] = function() {
                this[item.observer] && this[item.observer].apply(this.arguments); }; }}}else{ propMap[key] = item; }}); }// Handle properties => props
    let properties = options.properties;
    walkProps(properties);

    let mininsProperties = options.mixins.filter(item= > item.properties);
    mininsProperties.forEach(item= > {
      walkProps(item.properties);
    })

    /** * handle externalClasses while manually copying class */
    let externalClasses = options.externalClasses;
    externalClasses.forEach(clas= > {
      propMap[clas.replace(/-(\w)/g, (match, p) => p.toUpperCase())] = ' ';
    })

    options.props = propMap;
    options.props.__observers = observerMap

    /** * my life cycle function */
    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 && this.ready.apply(this.arguments);

      /** * Resolve to initialize the Observer component */
      if (this.props.__observers) {
        Object.keys(this.props.__observers).forEach(key= > {
          this.props.__observers[key].call(this.this.props[key])
        })
      }
    }

    options.didUnmount = function(){
      this.detached && this.detached.apply(this.arguments);
    }

    options.didUpdate = function(prevProps, preData) {
      for (let key in this.props) {
        if (this.props.__observers && typeof(this.props.__observers[key]) === 'function') {
          if (JSON.stringify(prevProps[key]) ! = =JSON.stringify(this.props[key]) &&
          JSON.stringify(preData[key]) ! = =JSON.stringify(this.props[key])) {
            this.setData(Object.assign({}, this.data, {[key]: this.props[key]}));
            this.props.__observers[key].apply(this[this.props[key], prevProps[key]]); }}else if (this.props[key] ! == prevProps[key]) {this.data[key] = this.props[key];
          this.setData(this.data);
        }
      }
    }

    Component(options);
}
export { VantComponent };

Copy the code

Here the main problem solved, some other micro channel small program to alipay small program differences are not listed, you can flexibly modify wwTO code to achieve the conversion of the difference, if the later students have the same needs to try to convert the problem, you can also leave a message.

The last

At the beginning of the decision whether to use wwTO this tool to convert wechat small program, the heart is not sure, after all, just open source, I estimate is the first to do the conversion when just open source. Moreover, I have never developed alipay applets, and I don’t know the general differences between Alipay applets and wechat applets. In addition, I don’t have enough time to make decisions about what schemes to use to realize other platforms applets. Finally, I decided to use WWTO do this, mainly because I didn’t want to do repetitive work. I only used the new technical framework Uni-app to rewrite it, and it is estimated that there will not be too much technical accumulation benefit for me in the short term. Of course, AT the same time, I also want to quickly understand some differences between wechat and Alipay. Every part of the source code can be debugged. Overall, the project cycle is shortened a lot, it took about two weeks to complete, but also understand the differences between alipay small program and wechat small program, solve a lot of problems, the process of solving the problem is also very headache…

The final conversion effect map is not given, welcome to wechat and Alipay search: “kaka looking for a car” small program to see the difference between the two.

Pay attention to our