懂编译真的可以为所欲为|不同前端框架下的代码转换

背景

整个前端领域在这几年迅速发展,前端框架也在不断变化,各团队选择的解决方案都不太一致,此外像小程序这种跨端场景和以往的研发方式也不太一样。在日常开发中往往会因为投放平台的不一样需要进行重新编码。前段时间我们需要在淘宝页面上投放闲鱼组件,淘宝前端研发DSL主要是React(Rax),而闲鱼前端之前研发DSL主要是Vue(Weex),一般这种情况我们都是重新用React开发,有没有办法一键将已有的Vue组件转化为React组件呢,闲鱼技术团队从代码编译的角度提出了一种解决方案。

编译器是如何工作的

日常工作中我们接触最多的编译器就是Babel,Babel可以将最新的Javascript语法编译成当前浏览器兼容的JavaScript代码,Babel工作流程分为三个步骤,由下图所示:

抽象语法树AST是什么

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构,详见维基百科。这里以const a = 1转成var a = 1操作为例看下Babel是如何工作的。

将代码解析(parse)成抽象语法树AST

Babel提供了@babel/parser将代码解析成AST。

const parse = require('@babel/parser').parse;const ast = parse('const a = 1');

经过遍历和分析转换(transform)对AST进行处理

Babel提供了@babel/traverse对解析后的AST进行处理。@babel/traverse能够接收AST以及visitor两个参数,AST是上一步parse得到的抽象语法树,visitor提供访问不同节点的能力,当遍历到一个匹配的节点时,能够调用具体方法对于节点进行处理。@babel/types用于定义AST节点,在visitor里做节点处理的时候用于替换等操作。在这个例子中,我们遍历上一步得到的AST,在匹配到变量声明(VariableDeclaration)的时候判断是否const操作时进行替换成vart.variableDeclaration(kind, declarations)接收两个参数kinddeclarations,这里kind设为var,将const a = 1解析得到的AST里的declarations直接设置给declarations

const traverse = require('@babel/traverse').default;
const t = require('@babel/types');traverse(ast, {VariableDeclaration: function(path) { //识别在变量声明的时候if (path.node.kind === 'const') { //只有const的时候才处理path.replaceWith(t.variableDeclaration('var', path.node.declarations) //替换成var);}path.skip();}
});

将最终转换的AST重新生成(generate)代码

Babel提供了@babel/generator将AST再还原成代码。

const generate = require('@babel/generator').default;let code = generate(ast).code;

Vue和React的异同

我们来看下Vue和React的异同,如果需要做转化需要有哪些处理,Vue的结构分为style、script、template三部分

style

样式这部分不用去做特别的转化,Web下都是通用的

script

Vue某些属性的名称和React不太一致,但是功能上是相似的。例如data需要转化为stateprops需要转化为defaultPropspropTypescomponents的引用需要提取到组件声明以外,methods里的方法需要提取到组件的属性上。还有一些属性比较特殊,比如computed,React里是没有这个概念的,我们可以考虑将computed里的值转化成函数方法,上面示例中的length,可以转化为length()这样的函数调用,在React的render()方法以及其他方法中调用。
Vue的生命周期和React的生命周期有些差别,但是基本都能映射上,下面列举了部分生命周期的映射

  • created -> componentWillMount
  • mounted -> componentDidMount
  • updated -> componentDidUpdate
  • beforeDestroy -> componentWillUnmount
    在Vue内函数的属性取值是通过this.xxx的方式,而在Rax内需要判断是否stateprops还是具体的方法,会转化成this.statethis.props或者this.xxx的方式。因此在对Vue特殊属性的处理中,我们对于datapropsmethods需要额外做标记。

template

针对文本节点和元素节点处理不一致,文本节点需要对内容{{title}}进行处理,变为{title}

Vue里有大量的增强指令,转化成React需要额外做处理,下面列举了部分指令的处理方式

  • 事件绑定的处理,@click -> onClick
  • 逻辑判断的处理,v-if="item.show" -> {item.show && ……}
  • 动态参数的处理,:title="title" -> title={title}

还有一些是正常的html属性,但是React下是不一样的,例如style -> className
指令里和model里的属性值需要特殊处理,这部分的逻辑其实和script里一样,例如需要{{title}}转变成{this.props.title}

Vue代码的解析

以下面的Vue代码为例

<template><div><p class="title" @click="handleClick">{{title}}</p><p class="name" v-if="show">{{name}}</p></div>
</template><style>
.title {font-size: 28px;color: #333;}
.name {font-size: 32px;color: #999;}
</style><script>
export default {props: {title: {type: String,default: "title"}},data() {return {show: true,name: "name"};},mounted() {console.log(this.name);},methods: {handleClick() {}}
};
</script>

我们需要先解析Vue代码变成AST值。这里使用了Vue官方的vue-template-compiler来分别提取Vue组件代码里的templatestylescript,考虑其他DSL的通用性后续可以迁移到更加适用的html解析模块,例如parse5等。通过require('vue-template-compiler').parseComponent得到了分离的templatestylescriptstyle不用额外解析成AST了,可以直接用于React代码。template可以通过require('vue-template-compiler').compile转化为AST值。script@babel/parser来处理,对于script的解析不仅仅需要获得整个script的AST值,还需要分别将datapropscomputedcomponentsmethods等参数提取出来,以便后面在转化的时候区分具体属于哪个属性。以data的处理为例:

const traverse = require('@babel/traverse').default;
const t = require('@babel/types');const analysis = (body, data, isObject) => {data._statements = [].concat(body); // 整个表达式的AST值let propNodes = [];if (isObject) {propNodes = body;} else {body.forEach(child => {if (t.isReturnStatement(child)) { // return表达式的时候propNodes = child.argument.properties;data._statements = [].concat(child.argument.properties); // 整个表达式的AST值}});}propNodes.forEach(propNode => {data[propNode.key.name] = propNode; // 对data里的值进行提取,用于后续的属性取值});
};const parse = (ast) => {let data = {};traverse(ast, {ObjectMethod(path) {/*对象方法data() {return {}}*/const parent = path.parentPath.parent;const name = path.node.key.name;if (parent && t.isExportDefaultDeclaration(parent)) {if (name === 'data') {const body = path.node.body.body;analysis(body, data);path.stop();}}},ObjectProperty(path) {/*对象属性,箭头函数data: () => {return {}}data: () => ({})*/const parent = path.parentPath.parent;const name = path.node.key.name;if (parent && t.isExportDefaultDeclaration(parent)) {if (name === 'data') {const node = path.node.value;if (t.isArrowFunctionExpression(node)) {/*箭头函数() => {return {}}() => {}*/if (node.body.body) {analysis(node.body.body, data);} else if (node.body.properties) {analysis(node.body.properties, data, true);}}path.stop();}}}});/*最终得到的结果{_statements, //data解析AST值list //data.list解析AST值}*/return data;
};module.exports = parse;

最终处理之后得到这样一个结构:

app: {script: {ast,components,computed,data: {_statements, //data解析AST值list //data.list解析AST值},props,methods},style, // style字符串值template: {ast // template解析AST值}
}

React代码的转化

最终转化的React代码会包含两个文件(css和js文件)。用style字符串直接生成index.css文件,index.js文件结构如下图,transform指将Vue AST值转化成React代码的伪函数。

import { createElement, Component, PropTypes } from 'React';
import './index.css';export default class Mod extends Component {${transform(Vue.script)}render() {${transform(Vue.template)}}
}

script AST值的转化不一一说明,思路基本都一致,这里主要针对Vue data继续说明如何转化成React state,最终解析Vue data得到的是{_statements: AST}这样的一个结构,转化的时候只需要执行如下代码

const t = require('@babel/types');module.exports = (app) => {if (app.script.data && app.script.data._statements) {// classProperty 类属性 identifier 标识符 objectExpression 对象表达式return t.classProperty(t.identifier('state'), t.objectExpression(app.script.data._statements));} else {return null;}
};

针对template AST值的转化,我们先看下Vue template AST的结构:

{tag: 'div',children: [{tag: 'text'},{tag: 'div',children: [……]}]
}

转化的过程就是遍历上面的结构针对每一个节点生成渲染代码,这里以v-if的处理为例说明下节点属性的处理,实际代码中会有两种情况:

  • 不包含v-else的情况,<div v-if="xxx"/>转化为{ xxx && <div /> }
  • 包含v-else的情况,<div v-if="xxx"/><text v-else/>转化为{ xxx ? <div />: <text /> }

经过vue-template-compiler解析后的template AST值里会包含ifConditions属性值,如果ifConditions的长度大于1,表明存在v-else,具体处理的逻辑如下:

if (ast.ifConditions && ast.ifConditions.length > 1) {// 包含v-else的情况let leftBlock = ast.ifConditions[0].block;let rightBlock = ast.ifConditions[1].block;let left = generatorJSXElement(leftBlock); //转化成JSX元素let right = generatorJSXElement(rightBlock); //转化成JSX元素child = t.jSXExpressionContainer( //JSX表达式容器// 转化成条件表达式t.conditionalExpression(parseExpression(value),left,right));
} else {// 不包含v-else的情况child = t.jSXExpressionContainer( //JSX表达式容器// 转化成逻辑表达式t.logicalExpression('&&', parseExpression(value), t.jsxElement(t.jSXOpeningElement(t.jSXIdentifier(tag), attrs),t.jSXClosingElement(t.jSXIdentifier(tag)),children)));
}

template里引用的属性/方法提取,在AST值表现上都是标识符(Identifier),可以在traverse的时候将Identifier提取出来。这里用了一个比较取巧的方法,在template AST值转化的时候我们不对这些标识符做判断,而在最终转化的时候在render return之前插入一段引用。以下面的代码为例

<text class="title" @click="handleClick">{{title}}</text>
<text class="list-length">list length:{{length}}</text>
<div v-for="(item, index) in list" class="list-item" :key="`item-${index}`"><text class="item-text" @click="handleClick" v-if="item.show">{{item.text}}</text>
</div>

我们能解析出template里的属性/方法以下面这样一个结构表示:

{title,handleClick,length,list,item,index
}

在转化代码的时候将它与app.script.data、app.script.props、app.script.computed和app.script.computed分别对比判断,能得到title是props、list是state、handleClick是methods,length是computed,最终我们在return前面插入的代码如下:

let {title} = this.props;
let {state} = this.state;
let {handleClick} = this;
let length = this.length();

最终示例代码的转化结果

import { createElement, Component, PropTypes } from 'React';export default class Mod extends Component {static defaultProps = {title: 'title'}static propTypes = {title: PropTypes.string}state = {show: true,name: 'name'}componentDidMount() {let {name} = this.state;console.log(name);}handleClick() {}render() {let {title} = this.props;let {show, name} = this.state;let {handleClick} = this;return (<div><p className="title" onClick={handleClick}>{title}</p>{show && (<p className="name">{name}</p>)}</div>);}
}

总结与展望

本文从Vue组件转化为React组件的具体案例讲述了一种通过代码编译的方式进行不同前端框架代码的转化的思路。我们在生产环境中已经将十多个之前的Vue组件直接转成React组件,但是实际使用过程中研发同学的编码习惯差别也比较大,需要处理很多特殊情况。这套思路也可以用于小程序互转等场景,减少编码的重复劳动,但是在这类跨端的非保准Web场景需要考虑更多,例如小程序环境特有的组件以及API等,闲鱼技术团队也会持续在这块做尝试。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/519302.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试稳了!网易资深工程师揭秘运维面经!

作者 | 阿文责编 | 伍杏玲出品 | 程序人生&#xff08;ID&#xff1a;coder_life&#xff09;受新型冠状病毒影响&#xff0c;很多企业都推迟了复工时间或集体开始远程办公&#xff0c;而一些企业本来计划春节过后开始春季招聘&#xff0c;但是受疫情影响已做出了调整&#xff…

java.lang.NumberFormatException: null

public static void main(String[] args) {String str null;try {int a Integer.parseInt(str);} catch (NumberFormatException e) {e.printStackTrace();}}

Pick!闲鱼亿级商品库中的秒级实时选品

一、业务背景 在电商运营工作中&#xff0c;营销活动是非常重要的部分&#xff0c;对用户增长和GMV都有很大帮助。对电商运营来说&#xff0c;如何从庞大的商品库中筛选出卖家优质商品并推送给有需要的买家购买是每时每刻都要思索的问题&#xff0c;而且这个过程需要尽可能快和…

一文看懂Microsoft Azure的十年变迁

来源 | forbes编译 | 火火酱责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;当微软前首席软件架构师雷奥兹&#xff08;Ray Ozzie&#xff09;在2008年的PDC大会上发布Windows Azure时&#xff0c;没人能预估这个软件平台将会为该公司和整个行业…

74HC595

脚位图及说明 管脚说明&#xff1a; 14脚&#xff1a;DS&#xff08;SER&#xff09;&#xff0c;串行数据输入引脚 13脚&#xff1a;OE&#xff0c;输出使能控制脚&#xff0c;它是低电才使能输出&#xff0c;所以接GND 12脚&#xff1a;RCK&#xff08;STCP&#xff09;&…

UI2Code智能生成Flutter代码——机器生成代码

背景 在《UI2CODE--整体设计》篇中&#xff0c;我们提到UI2Code工程的整体流程。前步图片分析之后&#xff0c;我们可以得到对应的DSL布局描述。利用DSL的资讯&#xff0c;结合IntelliJ Plugin介面工具&#xff0c;面向使用者提供生成对应Flutter代码。 本篇主要介绍我们如何…

初始化java工具失败,“初始化 Java 工具”期间发生了内部错误, java.lang.NullPointerException...

今天刚打开eclipse就报了这个错误&#xff0c;我怀疑是昨晚想关电脑的时候&#xff0c;关闭eclipse太快&#xff0c;没有等待工作空间保存就关了电脑的缘故错误如图&#xff1a;(图片来自下方链接博客&#xff0c;因为忘记截图了) 我百度后按照提示&#xff0c;删除了eclipse工…

Node.js 应用故障排查手册 —— 正确打开 Chrome devtools

楔子 前面的预备章节中我们大致了解了如何在服务器上的 Node.js 应用出现问题时&#xff0c;从常规的错误日志、系统/进程指标以及兜底的核心转储这些角度来排查问题。这样就引出了下一个问题&#xff1a;我们知道进程的 CPU/Memory 高&#xff0c;或者拿到了进程 Crash 后的核…

钉钉流量暴增百倍,阿里云抗住了!

2月12日&#xff0c;钉钉已连续在苹果应用商店霸榜7天。记者采访获悉&#xff0c;春节以来&#xff0c;在家办公及在家上课的强需求&#xff0c;使得钉钉后台系统峰值流量暴增百倍。钉钉通过阿里云连续扩容10万台云服务器&#xff0c;成功抗住这一巨大的流量冲击&#xff01; 2…

PB 级数据处理挑战,Kubernetes如何助力基因分析?

引言 James Watson 和 Francis Crick 于 1953 年发现了 DNA 的双螺旋结构&#xff0c;从此揭开了物种进化和遗传的神秘面纱&#xff0c;开启了人类对数字化遗传的认知&#xff0c;但是人类基因奥秘却是一点点被读懂的。 1956 年&#xff0c;一则癌症和染色体相关性的发现令整…

Nginx 外的另一选择,轻量级开源 Web 服务器 Tengine 发布新版本

新版发布 近日&#xff0c;轻量级开源 Web 服务器 Tengine 发布了2.3.0版本&#xff0c;新增如下特性&#xff1a; ngx_http_proxy_connect_module [1] &#xff0c;该模块让 Tengine 可以用于正向代理场景&#xff0c;支持对 CONNECT 方法请求的处理&#xff1b;HTTP2 Serve…

腾讯云数据库Redis助力百万企业远程办公

受疫情影响&#xff0c;多数企业员工目前无法回到写字楼办公&#xff0c;学生推迟开学&#xff0c;稳定高效的远程办公和直播授课成为2020年的开年刚需。腾讯从1月24日开始向全国免费开放可支持300人同时在线会议的“腾讯会议”&#xff0c;直至疫情结束。央视新闻联播对此也给…

打通前后端逻辑,客户端Flutter代码一天上线

一、前沿 ​ 随着闲鱼的业务快速增长&#xff0c;运营类的需求也越来越多&#xff0c;其中不乏有很多界面修改或运营坑位的需求。闲鱼的版本现在是每2周一个版本&#xff0c;如何快速迭代产品&#xff0c;跳过窗口期来满足这些需求&#xff1f;另外&#xff0c;闲鱼客户端的包…

迈向电商认知智能时代的基石:阿里电商认知图谱揭秘

阿里妹导读&#xff1a;电商平台最大的挑战是从日益增长的海量商品&#xff08;数十亿&#xff09;中挑选出的一个小的子集&#xff08;几十或上百&#xff09;展示给用户&#xff0c;以满足用户的个性化的购物需求。为了解决仍存在的重复推荐、缺少新意等问题&#xff0c;我们…

我是如何用6个月,从0编程经验变成数据科学家的?

来源 | medium编译 | 武明利责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;我叫Kate&#xff0c;刚从长达 8 年的学习和艰苦的工作中走出来&#xff0c;没有任何预兆。你可能想问&#xff0c;为什么有人会这么做&#xff1f;不得不说&#xff…

Node.js 应用故障排查手册 —— 综合性 GC 问题和优化

楔子 本章前面两节生产案例分别侧重于单一的 CPU 高和单一的内存问题&#xff0c;我们也给大家详细展示了问题的定位排查过程&#xff0c;那么实际上还有一类相对更复杂的场景——它本质上是 V8 引擎的 GC 引发的问题。 简单的给大家介绍下什么是 GC&#xff0c;GC 实际上是语…

“龙井”开箱评测 |Alibaba Dragonwell 新手上路指南

阿里巴巴有着最丰富的 Java 应用场景&#xff0c;覆盖电商&#xff0c;金融&#xff0c;物流等众多领域&#xff0c;是世界上最大的 Java 用户之一。 2019 年 3 月 21 日&#xff0c;阿里巴巴在北京阿里云峰会上正式宣布开源了 Alibaba Dragonwell 8 产品&#xff0c;并建立了 …

基于角色的访问控制(RBAC)

来源 | 编程新说责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;很多时候&#xff0c;需要对一些事物进行控制&#xff0c;如一个房间&#xff0c;为了不让人随便进&#xff0c;通常会装一把锁&#xff0c;如果要想进入&#xff0c;你必须得有一…

win10 ie中没有java,win10没有ie浏览器怎么处理_window10找不到ie浏览器如何解决

很多用户升级到win10系统之后&#xff0c;发现默认浏览器是edge&#xff0c;想要使用ie浏览器的时候却发现没有ie浏览器&#xff0c;遇到window10找不到ie浏览器的话该怎么办呢&#xff0c;下面随小编一起来看看详细的解决步骤吧。方案一&#xff1a;1、直接搜索&#xff0c;右…

手把手教程:用Python开发一个自然语言处理模型,并用Flask进行部署

截住到目前为止&#xff0c;我们已经开发了许多机器学习模型&#xff0c;对测试数据进行了数值预测&#xff0c;并测试了结果。实际上&#xff0c;生成预测只是机器学习项目的一部分&#xff0c;尽管它是我认为最重要的部分。今天我们来创建一个用于文档分类、垃圾过滤的自然语…