While working from home during the Spring Festival, I received a demand to extend the current wechat mini program custom component to the Alipay mini program platform. Without going into the background and history of the requirement, let’s look at the requirement from what has been stated above.

The first time I received the demand, THE author thought, this is not multi-terminal compilation? Well, let’s do it without talking.

background

Since the author’s project is a simple custom component of wechat small program and the packaging tool is rollup, the author’s technical solution is to write a rollup plug-in to support multi-side compilation. Rollup and the rollup plugin are not written in this article. If you are interested in the rollup plugin, you can read its official documentation.

The process is introduced

Wechat applet components include *. Json, *. Js, *. WXML, *. WXSS these four files, to convert into Alipay applet, which json file and WXSS file is relatively simple, the former unchanged, the latter change the suffix name is good, mainly to modify js and WXML two files.

The general process is basically as follows

  1. Differences in finishing
  2. Turn the code into an AST tree
  3. Replace the node in the tree
  4. Generate code from the new AST tree

babel

For JS files, there are already some excellent tools in the industry to do this. I chose Babel (you can check out astexplorer.net/). Secondly, Babel package is very beautiful. In addition to webpack to complete daily construction work, it also provides excellent toolkits such as @babel/ Parser, @babel/ Generator, @babel/traverse, @babel/types, etc. Each toolkit has a single responsibility, which is very clear, to help implement the above process (rollup actually has acorn instances built in, the only difference from Babel is that the generated AST has some minor differences, as you can see in AstExplorer.net/). Where @babel/ Parser can interpret JS code as AST tree, @babel/ Generator will generate JS code according to AST tree, @babel/traverse supports efficient operation of AST tree nodes, @babel/types provides some judgment functions. Help developers quickly locate nodes.

Look at a simple example

function sayHello() {
	console.log('hello')
}

sayHello();
Copy the code

For the above code, after Babel transformation, the AST tree is as follows

{
  "type": "Program"."start": 0."end": 58."body": [{"type": "FunctionDeclaration"."start": 0."end": 45."id": {
        "type": "Identifier"."start": 9."end": 17."name": "sayHello"
      },
      "expression": false."generator": false."async": false."params": []."body": {
        "type": "BlockStatement"."start": 20."end": 45."body": [{"type": "ExpressionStatement"."start": 23."end": 43."expression": {
              "type": "CallExpression"."start": 23."end": 43."callee": {
                "type": "MemberExpression"."start": 23."end": 34."object": {
                  "type": "Identifier"."start": 23."end": 30."name": "console"
                },
                "property": {
                  "type": "Identifier"."start": 31."end": 34."name": "log"
                },
                "computed": false
              },
              "arguments": [{"type": "Literal"."start": 35."end": 42."value": "hello"."raw": "'hello'"}}}]}}, {"type": "ExpressionStatement"."start": 47."end": 58."expression": {
        "type": "CallExpression"."start": 47."end": 57."callee": {
          "type": "Identifier"."start": 47."end": 55."name": "sayHello"
        },
        "arguments": []}}],"sourceType": "module"
}
Copy the code

For this js code, if you want to replace its method name with sayHi and print Hello with Hi, with Babel, that’s all you need to do.

import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import * as t from "@babel/types";

const code = ` function sayHello() { console.log('hello') } sayHello(); `;

const transform = code= > {
  const ast = parse(code);
  traverse(ast, {
    enter(path) {
      if (t.isIdentifier(path.node, { name: "sayHello" })) {
        path.node.name = "sayHi";
      }
      if (t.isLiteral(path.node, { value: "hello" })) {
        path.node.value = "Hi"; }}});const output = generate(ast, {}, code);
  return output;
};

console.log(transform(code).code);
Copy the code

You can also view the effect in codeSandbox.

For other uses of the package, see the official manual.

himalaya

For the WXML file, I chose himalaya-wxml, which provides both parse and stringify methods that interpret WXML as an AST tree and vice versa (see The effect in Jew. Ski /himalaya/). Once the WXML code has been converted to the AST tree by Parse, it is then simply a matter of manually recursing through the AST tree to replace the node and converting it back to the WXML code.

Again, look at a simple example

<div id='main'>
  <span>hello world</span>
</div>
Copy the code

For the above HTML code, the AST tree generated after himalaya conversion is as follows

[{"type": "element"."tagName": "div"."attributes": []."children": [{"type": "text"."content": "\n "
      },
      {
        "type": "element"."tagName": "span"."attributes": []."children": [{"type": "text"."content": "hello world"}]}, {"type": "text"."content": "\n"}}]]Copy the code

For this HTML code, if you want to replace the id of its outer div with container, you just do that.

import { parse, stringify } from "himalaya";

const code = ` 
      
hello world
`
; const traverse = ast= > { return ast.map(item= > { if (item.type === "element" && item.attributes) { return { ...item, attributes: item.attributes.map(attr= > { if(attr.key ! = ="id") { return attr; } return { ...attr, value: "container"}; })}; }return item; }); }; const transform = code= > { const ast = parse(code); const json = traverse(ast); return stringify(json); }; console.log(transform(code)); Copy the code

You can also view the effect in codeSandbox.

The core is introduced

That’s about it. Let’s get started. First is to sort out the differences, according to the author’s research, wechat small program components to be converted into Alipay small program components, there are roughly the following changes (just meet the needs of the author, if not completely, welcome to add) :

  1. The WXML suffix should be changed to axML
  2. The suffix WXSS should be changed to ACSS
  3. The attribute wX-xxx in WXML should be changed to A-xxx
  4. The event attribute bindxxx in WXML should be changed to onXxx
  5. Life cycle Attached is replaced with onInit
  6. Detached life cycle didUnmount
  7. Life cycle Pagelifetimes. show is replaced with didMount
  8. Life cycle pageLifetimes to be deleted

The job of changing the suffix is relatively simple, give it to the build tool and specify it in the Output configuration. The focus is to replace the attribute.

Conversion JS part of the code is as follows

import { parse } from '@babel/parser';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
import * as t from '@babel/types';

function transformJs(code: string) {
  const ast = parse(code);
  let pp;

  traverse(ast, {
    enter(path) {
      if (t.isIdentifier(path.node, {name: 'attached'})) {
        path.node.name = 'onInit';
      }
      if (t.isIdentifier(path.node, {name: 'detached'})) {
        path.node.name = 'didUnmount';
        pp = path.parentPath;
      }
      if(t.isIdentifier(path.node.key, {name: 'show'})){
        path.node.key.name = 'didMount'; pp.insertAfter(path.node); }},exit(path) {
      if(t.isIdentifier(path.node.key, {name: 'pageLifetimes'})){ path.remove(); }}});const output = generate(ast, {}, code);
  return output
}

export default transformJs

Copy the code

The WXML part of the conversion is as follows:

import { parse, stringify } from 'himalaya-wxml';

const traverseKey = (key: string) = > {
  if(key.startsWith('wx:')) {const postfix = key.slice(3);
    return `a:${postfix}`;
  }
  if(key === 'catchtouchmove') {return 'catchTouchMove';
  }
  if(key === 'bindtap') {return 'onTap';
  }
  if(key === 'bindload') {return 'onLoad';
  }
  if(key === 'binderror') {return 'onError';
  }
  if(key === 'bindchange') {return 'onChange';
  }
  return key
}

const traverseAst = (ast: any) = > {
  return ast.map(item= > {
    if(item.type ! = ='element') {return item;
    }
    let res = item;
    if(item.attributes){ res = { ... item,attributes: item.attributes.map(attr= > ({
          ...attr,
          key: traverseKey(attr.key)
        }))
      }
    }
    if(item.children){
      res.children = traverseAst(item.children);
    }
    return res
  });
}

const transformWxml = (code: string) = > {
  const ast = parse(code);
  const json = traverseAst(ast);
  return stringify(json)
}

export default transformWxml
Copy the code

Above, there are two conversion functions, and then the work is to run these two functions in rollup, to complete the function of converting wechat applet components into Alipay applet components.

conclusion

Javascript is the most commonly used front-end language, we not only need to be familiar with it, but also to be able to manipulate it, through the javascript interpreter, we have the ability to manipulate it. Trace the source, consolidate the foundation, in order to maintain inner peace in the winter.