VUE 组件转换为微信小程序组件的方法
简介:
首先我们介绍一下本文的关键点:抽象语法树,它是以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
通过操作这棵树,可以精确的定位到声明、赋值、运算语句,从而实现对代码的优化、变更等操作。
本文通过对VUE组件的JavaScript、CSS模块进行转换,比如JavaScript模块,包括外层对象,生命周期钩子函数,赋值语句,组件的内部数据,组件的对外属性等等来实现把一个VUE组件转换为一个小程序组件。
AST抽象语法树,似乎我们平时并不会接触到。实际上在我们的项目当中,CSS预处理,JSX亦或是TypeScript的处理,代码格式化美化工具,Eslint,Javascript转译,代码压缩,Webpack,Vue-Cli,ES6转ES5,当中都离不开AST抽象语法树这个绿巨人。先卖个关子,让我们看一下AST到的官方解释:
Itisahierarchicalprogramrepresentationthatpresentssourcecodestructureaccordingtothegrammarofaprogramminglanguage,eachASTnodecorrespondstoanitemofasourcecode.
中文的解释有:
抽象语法树(abstractsyntaxtree或者缩写为AST),或者语法树(syntaxtree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。和抽象语法树相对的是具体语法树(concretesyntaxtree),通常称作分析树(parsetree)。一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树。一旦AST被创建出来,在后续的处理过程中,比如语义分析阶段,会添加一些信息。
抽象语法树,它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
通过操作这棵树,可以精确的定位到声明、赋值、运算语句,从而实现对代码的优化、变更等操作。这些并不是我们想要看到的。
对于AST面纱的神秘感,似乎已经将要揭开。不错,在我刚接触到他的时候,同样感觉确实是难。但是当你开始了解了它以后,你就会越来越喜欢它。
因为他实在太强大了。AST本身并不难,难点在于转换的目标对象与源对象的语法差异,当中水深毋庸置疑。但是,这才能更加激起我们探索他的欲望。在开始之前,我们先看一下抽象语法树到底长什么样子。
一、一探究竟AST
通过 astexplorer[1](AST树查看网站),通过他你可以方便的查看代码的语法树,挑你喜欢的库。你可以在线把玩AST,而且除了JavaScript,HTML,CSS还有很多其它语言的AST库,让我们对他有一个感性而直观的认识。
请看下图,看看AST语法树长什么样子:
此图看到的是一个ExportDefaultDeclaration也就是exportdefault{},还有他的位置信息,注释失信,tokens等等。
国际惯例,先来一个小demo
输入数据
functionsquare(n){
returnn*n;
}
处理数据
astFn(){
constcode=`functionsquare(n){
returnn*n;
}`;
//得到AST语法树
constast=babylon.parse(code);
traverse(ast,{
enter(path){
console.log("enter:"+path.node.type);
//如下的语句匹配到return中的n与参数n,并且将它替换为x。
if(path.node.type==="Identifier"&&path.node.name==="n"){
path.node.name="x";
}
}
});
generate(ast,{},code);//将AST转换为代码
console.log(generate(ast,{},code).code);//打印出转换后的JavaScript代码
}
输出数据
functionsquare(x){
returnx*x;
}
我们看一下我们得到的AST树
接下来我们插入一段把VUE组件转换为微信小程序组件正则版本的处理
二、简单粗暴的版本(VUE组件转换为微信小程序组件)
没有使用AST将VUE组件转换成小程序组件的简易版本介绍
下方是两段代码,简单的逻辑,实现思路,匹配目标字符串,替换字符,然后生成文件。
regs:{//通过标签匹配来替换对应的小程序支持的标签
toViewStartTags:/()|(<\/s>)|(<\/em>)|(<\/ul>)|(<\/li>)|(<\/dl>)|(<\/i>)|(<\/span>)/g,
toBlockStartTags:/()|(<\/p>)/g,
},
signObj:{//通过标签查找来分离脚本,模板和CSS
tempStart:'',
tempEnd:'',
styleStart:'',
styleEnd:'',
scriptStart:''
}
上方是正则版本的一些模板匹配规则,经过后续的一系列处理把一个VUE组件处理得到对应的小程序的WXML,WXSS,JSON,JS,4个文件。
//文件
constwxssFilePath=path.join(dirPath,`${mpFile}.wxss`);
constjsFilePath=path.join(dirPath,`${mpFile}.js`);
constwxmlFilePath=path.join(dirPath,`${mpFile}.wxml`);
constjsonFilePath=path.join(dirPath,`${mpFile}.json`);
if(!fs.existsSync(dirPath)){
fs.mkdirSync(dirPath);
}
fs.writeFile(wxssFilePath,wxssContent,err=>{
if(err)throwerr;
//console.log(`dist目录下生成${mpFile}.wxss文件成功`);
fs.writeFile(jsFilePath,jsContent,err=>{
if(err)throwerr;
//console.log(`dist目录下生成${mpFile}.js文件成功`);
fs.writeFile(wxmlFilePath,wxmlContent,err=>{
if(err)throwerr;
//console.log(`dist目录下生成${mpFile}.wxml文件成功`);
fs.writeFile(jsonFilePath,jsonContent,err=>{
if(err)throwerr;
console.log(`dist目录下生成${mpFile}.json文件成功`)
resolve(`生成${mpFile}.json文件成功`);
})
})
});
});
上方是处理得到的WXML,WXSS,JSON,JS,4个文件,并且生成到对应的目录下。代码实现用时较短,后续更改方案,并没有做优化,这里就不做详细展开讨论这个实现方案了。
回到正题介绍一下,AST抽象语法树的核心部分:
三、抽象语法树三大法宝
Babel是JavaScript编译器,更确切地说是源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。意思是说你为Babel提供一些JavaScript代码,Babel更改这些代码,然后返回给你新生成的代码。
babel-core:Babel的编译器;它暴露了babel.transform方法。
[1]babylon:Babylon是Babel的解析器。用于生成AST语法树。
[2]babel-traverse:Babel的遍历器,所有的transformers都使用该工具遍历所有的AST(抽象语法树),维护了整棵树的状态,并且负责替换、移除和添加节点。我们可以和Babylon一起使用来遍历和更新节点。
[3]babel-generator:Babel的代码生成器。它读取AST并将其转换为代码。
整个编译器就被分成了三部分:分析器、转换器、生成器,大致的流程是:
输入字符串->babylon分析器parse->得到AST->转换器->得到AST->babel-generator->输出
总结核心三步:
AST三大法宝
babylon.parse=>traverse转换AST=>generate(ast,{},code).code)生成
感兴趣的童鞋,可以在网上或者看参考资料都有介绍。该铺垫的都铺垫的差不多了,进入正题。
我们到底是如何通过AST将VUE组件转换为微信小程序组件的呢?
四、VUE组件转换为微信小程序组件中组件的对外属性、赋值语句的转换处理
转换之前的VUE组件代码Demo
exportdefault{
//组件的对外属性
props:{
max:{
type:Number,
value:99
}
},
//组件的内部数据
data(){
return{
num:10000
}
},
//组件的方法
methods:{
textFn(){
this.num=2
},
onMyButtonTap:function(){
this.num=666
},
}
}
处理后我们得到的微信小程序组件JavaScript部分代码
exportdefault{
properties:{
//组件的对外属性
max:{
type:Number,
value:99
}
},
//组件的内部数据
data(){
return{
num:10000
}
},
//组件的方法
methods:{
textFn(){
this.setData({
num:2
});
},
onMyButtonTap:function(){
this.setData({
num:666
});
}
}
};
我们对js动了什么手脚(亦可封装成babel插件):
//toAST
constast=babylon.parse(code,{
sourceType:"module",
plugins:["flow"]
});
//AST转换node,nodetype很关键
traverse(ast,{
enter(path){
//打印出node.type
console.log("enter:"+path.node.type);
}
})
ObjectProperty(path){
//props替换为properties
if(path.node.key.name==="props"){
path.node.key.name="properties";
}
}
//修改methods中使用到数据属性的方法中。this.prop至this.data.prop等与this.setData的处理。
MemberExpression(path){
letdatasVals=datas.map((item,index)=>{
returnitem.key.name//拿到data属性中的第一级
})
if(//含有this的表达式并且包含data属性
path.node.object.type==="ThisExpression"&&
datasVals.includes(path.node.property.name)
){
path.get("object").replaceWithSourceString("this.data");
//判断是不是赋值操作
if(
(t.isAssignmentExpression(path.parentPath)&&
path.parentPath.get("left")===path)||
t.isUpdateExpression(path.parentPath)
){
constexpressionStatement=path.findParent(parent=>
parent.isExpressionStatement()
);
//......
}
}
}
转换之前的js代码的部分AST树:
具体的API使用,童鞋们看一下babel相关的文档了解一下。
五,VUE组件转换为微信小程序组件中ExportDefault到Component构造器的转换与生命周期钩子函数,事件函数的处理
首先我们看一下要转换前后的语法树与代码如下(明确转换目标):
转换之前的AST树与代码
exportdefault{//VUE组件的惯用写法用于导出对象模块
data(){
},
methods:{
},
props:{
}
}
转换之后的AST树与代码
components({//小程序组件的构造器
data(){
},
methods:{
},
props:{
}
})
通过以上转换之前和转换之后代码和AST的对比我们明确了转换目标就是ExportDefault到Component构造器的转换,下面看一下我们是如何处理的:
我们做了什么(在转换中进入到ExportDefault中做对应的处理):
//ExportDefault到Component构造器的转换
ExportDefaultDeclaration(path){
//创建CallExpressionComponent({})
functioninsertBeforeFn(path){
constobjectExpression=t.objectExpression(propertiesAST);
test=t.expressionStatement(
t.callExpression(//创建名为Compontents的调用表达式,参数为objectExpression
t.identifier("Compontents"),[
objectExpression
]
)
);
//最终得到的语法树
console.log("test",test)
}
if(path.node.type==="ExportDefaultDeclaration"){
if(path.node.declaration.properties){
//提取属性并存储
propertiesAST=path.node.declaration.properties;
//创建AST包裹对象
insertBeforeFn(path);
}
//得到我们最终的转换结果
console.log(generate(test,{},code).code);
对于ExportDefault=>Component构造器转换还有一种转换思路下面我们看一下:
[1]第一种思路是先提取ExportDefault内部所有节点的AST,并做处理,然后创建Component构造器,插入提取处理后的AST,得到最终的AST
//propertiesAST这个就是我们拿到的AST,然后在对应的分支内做对应的处理以下分别为data,methods,props,其他的钩子同样处理即可
propertiesAST.map((item,index)=>{
if(item.type==="ObjectProperty"){
//props替换为properties
if(item.key.name==="props"){
item.key.name="properties";
}
}elseif(item.type==="ObjectMethod"){
if(path.node.key.name==="mounted"){
path.node.key.name="ready";
}elseif(path.node.key.name==="created"){
path.node.key.name="attached";
}elseif(path.node.key.name==="destroyed"){
path.node.key.name="detached";
}elseif(path.node.type==="ThisExpression"){
if(path.parent.property.name==="$emit"){
path.parent.property.name="triggerEvent";
}
}else{
voidnull;
}
}
}elseif(path.node.key.name==="methods"){
path.traverse({
enter(path){
if(path.node.type==="ThisExpression"){
if(path.parent.property.name==="$emit"){
path.parent.property.name="triggerEvent";
}
}
}
})
}
else{
//...
console.log("nodetype",item.type);
}
});
[2]第二种思路呢,就是我们上面展示的这种,不过有一个关键的地方要注意一下:
//我把ExportDefaultDeclaration的处理放到最后来执行,拿到AST首先进行转换。然后在创建得到新的小程序组件JS部分的AST即可
traverse(ast,{
enter(path){},
ObjectProperty(path){},
ObjectMethod(path){},
//......
ExportDefaultDeclaration(path){
//...
}
})
如果你想在AST开始处与结尾处插入,可使用path操作:
path.insertBefore(
t.expressionStatement(t.stringLiteral("start.."))
);
path.insertAfter(
t.expressionStatement(t.stringLiteral("end.."))
);
注:关于微信小程序不支持computed,与watch,我们具体的初期采用的方案是挂载computed和watch方法到每一个微信小程序组件,让小程序组件也支持这两个功能。
六,VUE组件转换为微信小程序组件中的Data部分的处理:
关于Data部分的处理实际上就是:函数表达式转换为对象表达式(FunctionExpression转换为ObjectExpression)
转换之前的JavaScript代码
exportdefault{
data(){//函数表达式
return{
num:10000,
arr:[1,2,3],
obj:{
d1:"val1",
d2:"val2"
}
}
}
}
处理后我们得到的
exportdefault{
data:{//对象表达式
num:10000,
arr:[1,2,3],
obj:{
d1:"val1",
d2:"val2"
}
}
};
通过如上的代码对比,我们看到了我们的转换前后代码的变化:
转换前后AST树对比图明确转换目标:
我们对JavaScript动了什么手脚(亦可封装成babel插件):
constast=babylon.parse(code,{
sourceType:"module",
plugins:["flow"]
});
//AST转换node、nodetype很关键
traverse(ast,{
enter(path){
//打印出node.type
console.log("enter:"+path.node.type);
}
})
我们的转换部分都尽量在一个Traverse中处理,减少AST树遍历的性能消耗
//Data函数表达式转换为Object
ObjectMethod(path){
//console.log("path.node",path.node)//data,add,textFn
if(path.node.key.name==="data"){
//获取第一级的BlockStatement,也就是Data函数体
path.traverse({
BlockStatement(p){
//从Data中提取数据属性
datas=p.node.body[0].argument.properties;
}
});
//创建对象表达式
constobjectExpression=t.objectExpression(datas);
//创建Data对象并赋值
constdataProperty=t.objectProperty(
t.identifier("data"),
objectExpression
);
//插入到原Data函数下方
path.insertAfter(dataProperty);
//删除原Data函数节点
path.remove();
}
}
七,VUE组件转换为微信小程序组件中CSS部分的处理:
那CSS我们也是必须要处理的一部分,lettry
以下是我们要处理的css样本
constcode=`
.text-ok{
position:absolute;
right:150px;
color:#e4393c;
}
.nut-popup-close{
position:absolute;
top:50px;
right:120px;
width:50%;
height:200px;
display:inline-block;
font-size:26px;
}`;
处理后我们得到的
.text-ok{
position:absolute;
right:351rpx;
color:#e4393c;
}
.nut-popup-close{
position:absolute;
top:117rpx;
right:280.79rpx;
width:50%;
height:468rpx;
display:inline-block;
font-size:60.84rpx;
}
通过前后代码的对比,我们看到了单位尺寸的转换(比如:top:50px;转换为top:117rpx;)。
单位的转换(px转为了rpx)
CSS又做了哪些处理呢?
同样也有不少的CSSCodeParsers供我们选择Cssom,CssTree等等,
我们拿Cssom来实现上方css代码的一个简单的转换。
varast=csstree.parse(code);
csstree.walk(ast,function(node){
if(typeofnode.value=="string"&&isNaN(node.value)!=true){
letnewVal=Math.floor((node.value*2.34)*100)/100;//转换比例这个根据情况设置即可
if(node.type==="Dimension"){//得到要转换的数字尺寸
node.value=newVal;
}
}
if(node.unit==="px"){//单位的处理
node.unit="rpx"
}
});
console.log(csstree.generate(ast));
当然这只是一个demo,实际项目中使用还的根据项目的实际情况出发,SCSS,LESS等等的转换与考虑不同的处理场景哦!
注:本文有些模块的转换实现还未在小程序开发工具中测试。
插播一个通过AST实现的好东东:
将JavaScript代码转化生成SVG流程图js2flowchart(4.5kstars在GitHub)
当你拥有AST时,可以做任何你想要做的事。把AST转回成字符串代码并不是必要的,你可以通过它画一个流程图,或者其它你想要的东西。
js2flowchart使用场景是什么呢?通过流程图,你可以解释你的代码,或者给你代码写文档;通过可视化的解释学习其他人的代码;通过简单的js语法,为每个处理过程简单的描述创建流程图。
马上用最简单的方式尝试一下吧,去线上编辑看看 js-code-to-svg-flowchart[8]。
此处有必要附上截图一张。
八、总结:
通过以上我们的介绍,我们大概对抽象语法树有了初步的了解。总体思路是:我们用Babel的解析器把JavaScript源码转化为抽象语法树,
再通过Babel的遍历器遍历AST(抽象语法树),替换、移除和添加节点,得到一个新的AST树。最后,使用,Babel的代码生成器BabelGenerator模块读取处理后的AST并将其转换为代码。任务就完成了!
本文通过对VUE组件转换为微信小程序组件的转换部分包括如下内容:
- VUE组件JavaScript模块对外属性转换为小程序对外属性的处理
- VUE组件JavaScript模块内部数据的转换为小程序内部数据的处理
- VUE组件JavaScript模块methods中的赋值语句转换为小程序赋值语句的处理
- VUE组件JavaScript模块外层对象,生命周期钩子函数的处理与CSS模块的简易处理
总结
以上所述是小编给大家介绍的VUE组件转换为微信小程序组件的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。