盈彩网彩票

  • <tr id='MrLeNV'><strong id='MrLeNV'></strong><small id='MrLeNV'></small><button id='MrLeNV'></button><li id='MrLeNV'><noscript id='MrLeNV'><big id='MrLeNV'></big><dt id='MrLeNV'></dt></noscript></li></tr><ol id='MrLeNV'><option id='MrLeNV'><table id='MrLeNV'><blockquote id='MrLeNV'><tbody id='MrLeNV'></tbody></blockquote></table></option></ol><u id='MrLeNV'></u><kbd id='MrLeNV'><kbd id='MrLeNV'></kbd></kbd>

    <code id='MrLeNV'><strong id='MrLeNV'></strong></code>

    <fieldset id='MrLeNV'></fieldset>
          <span id='MrLeNV'></span>

              <ins id='MrLeNV'></ins>
              <acronym id='MrLeNV'><em id='MrLeNV'></em><td id='MrLeNV'><div id='MrLeNV'></div></td></acronym><address id='MrLeNV'><big id='MrLeNV'><big id='MrLeNV'></big><legend id='MrLeNV'></legend></big></address>

              <i id='MrLeNV'><div id='MrLeNV'><ins id='MrLeNV'></ins></div></i>
              <i id='MrLeNV'></i>
            1. <dl id='MrLeNV'></dl>
              1. <blockquote id='MrLeNV'><q id='MrLeNV'><noscript id='MrLeNV'></noscript><dt id='MrLeNV'></dt></q></blockquote><noframes id='MrLeNV'><i id='MrLeNV'></i>

                通过一个需求揭秘多端编译

                发布日期:2020-09-05  浏览:281 

                过年在家办公期间,接到了一个需求Ψ,需要将目前的 微信小程序自定义组件 扩展到 支付宝小程序 平台。关于需求的背景和历史这边就暂不多说了,就从上面已说明的内容来看待这个需求吧。 接到需求的第一时〖间,笔者就思考,这不就是多端编译吗?话不多说,那就开搞吧。

                背景介绍

                由于笔者的项目是一个单纯的微信小程序自定义组件,打包工具是rollup,所以,笔者的技术方案☆是编写一个rollup插件,来支持多端编译。关于rollup和rollup插件的写法本次不作过多介∮绍,有兴趣的可以看它的 官方文档 ,这边只是介绍一下核心的多端编译流程。

                流程介绍

                微信小程序组件包含 *.json 、 *.js 、 *.wxml 、 *.wxss 这4个文件,要转换成支付宝小程序,其中json文件和wxss文件比较简〓单,前者ω 原封不动,后者改一下后缀名就好了,主要要修改js和wxml两个文件。

                大致流程基本就是※如下

                差异整理

                将代码转◢成AST树

                替换树上的节点

                根据新的AST树生成代码

                acorn

                对于js文件,要实现这些功能的话,业界已经有一些【出色的工具了。笔者选择了 babel ,babel内置acron作为javascript解释器,生成符合estree标准的AST树(可以在 astexplorer.net/ 中查▲看效果)。其次babel的封装很漂亮,除了搭配webpack完成日→常的构建工作外,它还提供了 @babel/parser , @babel/generator , @babel/traverse , @babel/types 等优秀的工具包,每个㊣ 工具包都是单一职责,职责▆很明确,帮助实现以上的流程(其实rollup内置了acron实例,不过babel会更好用一些)。 其中 @babel/parser 可以将js代码解释为AST树, @babel/generator 将根据AST树生成js代码, @babel/traverse 支ω持高效地操作AST树的节点, @babel/types 则提供一些判断函数,帮助开发者快速定位节点。

                看卐一个简单的示例

                function sayHello() {

                  console.log('hello')

                }

                 

                sayHello();

                复制代码

                对于以上这段∏代码,通过acron转换后,得出的AST树如下

                {

                  "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"

                }

                复制代码

                对于这段js代码,如果要替换它的方法名为 sayHi 、打印出的 hello 替换为 Hi ,通过babel,只需要这样做就∑ 可以了。

                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);

                复制代码

                也可以在 codeSandbox 中查看效☉果。

                关于包的◣其它使用,可以查看 官方手册 。

                himalaya

                对于wxml文件,笔者选择了 himalaya-wxml ,它提供了 parse 和 stringify 两个方法,前者将wxml解释成AST树,后者反之(可以在 jew.ski/himalaya/ 中查看效果)。通过 parse 将wxml代码∴转换成AST树之后,接下↓去只需要手动递归遍历AST树去替¤换节点,再将其转换回wxml代码就可以完成工作了々。

                同样,看一个简单的示例

                <div id='main'>

                  <span>hello world</span>

                </div>

                复制代码

                对于以上html代码,通过 himalaya 转换后,生成的AST树如下

                [

                  {

                    "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"

                      }

                    ]

                  }

                ]

                复制代码

                对于这段代码html代码,如果要替换它外层 div 的 id 为 container ,只需要这样做就可以了。

                import { parse, stringify } from "himalaya";

                 

                const code = `

                <div id='main'>

                  <span>hello world</span>

                </div>

                `;

                 

                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));

                复制代码

                也可以在 codeSandbox 中查看效果。

                核心介绍

                流程和工具介绍的差不多了▓,接下来就开始正题吧。 首先是整理差异,根据笔者的▼调研,微信小程序组件要转换成支付宝小程序组件,大致有以下几个改动(只是符〖合笔者的需求,如果⌒不完全,欢迎补充):

                wxml后缀名要改成axml

                wxss后缀名要改成acss

                wxml中的属性wx-xxx要改成a-xxx

                wxml中的事件属性∩bindxxx要改成onXxx

                生命周期attached要替换成onInit

                生命周期detached要替换成didUnmount

                生命周期pageLifetimes.show要替换成didMount

                生命周期pageLifetimes要删除

                改后缀】名的工作相对简单,交给构建工具,output配置里面指定一◢下就好了,重点是替换属ぷ性。

                转换js部分代码如★下

                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

                 

                复制代码

                转换wxml部分如下:

                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

                复制代码

                以上,就拥有⊙了两个转换函数,再之后的工作,就是将这两个函数运行在rollup里,就完成了将微信小程序组件转∩换成支付宝小程序组件的功能。

                总结

                javascript作为前端最常用的语言,我们不仅◤要熟悉它,更要能操控它,通过javascript解释器,我们就拥有了操★控它的能力。回本硕源,巩固基础,才能在寒冬之中保持内心的平静。