前言Babel在前端领域小有名气。该工具可以实现源码到源码的转换。为什么需要从源代码到源代码的转换?由于前端语言发展迅速,同时由于不同浏览器对语言和规范的实现不一致,导致很多高级优雅的语法在日常开发中无法安全使用。但是,babel可以将源码中新的高级语法转换成旧的、更兼容的语法,可以大大降低开发成本,提高代码的兼容性。同时babel还可以实现代码的静态检查,著名的前端开发框架react也依赖babel。Babel非常强大,已经被很多框架或者开发工具集成,比如reactvue-cli。层层封装使得我们在业务开发过程中很少接触到babel相关的配置和应用。但是作为前端开发者,我们需要了解它的原理和基础开发。本文主要介绍babel相关的工具和使用方法,旨在让大家对babel的原理有一个初步的了解。babel相关工具介绍babel首先通过parser工具将源码转化为抽象语法树(AST);然后通过遍历工具遍历语法树,同时使用模板和类型工具实现抽象语法树节点的增删改查;最后通过生成器将转换后的抽象语法树转换为源字符串输出。这是与babel相关的工具的一般列表。babel正是通过这些工具实现了源码到源码的转换。接下来,我们将简单介绍并举例说明这些工具在开发中的使用方法。@babel/parser:原babylon,用于源码到AST的转换@babel/generator:AST到源码的转换@babel/template:生成AST模板的代码字符串@babel/traverse:AST遍历&&操作@babel/types:ConvertstringtoAST&&AST节点类型判断@babel/core:包含parse转换类型,依赖babel配置文件@babel/runtime:helpers工具库@babel/parserbabel/parser介绍babel/parser实现源码字符后词法分析和语法分析String-to-AbstractSyntaxTree(AST)转换操作,即工具的输入是源代码字符串,输出是AST对象。具体应用parse方法导入获取待转换的源码字符串。使用parse方法获取抽象语法树(AST)。图1显示了由“leta=100”转换的语法树的部分结构。可以看到源代码字符串已经被转换为一个等价的AST对象,该对象使用不同的字段来区分不同的语法、变量和代码结构;例如,type用于表示当前节点对应的源代码字符串是函数声明、变量声明或属性表达式。constfs=require('fs')const{join}=require('path')//解析方法importconstparser=require("@babel/parser").parse;//通过读取文件代码获取源字符串const=fs.readFileSync(join(__dirname,'./src.js')).toString()//codeis'leta=100'//通过解析方法获取ASTconstast=parser(code)//打印结果console.log(ast.program.body)图1:AST对象提示AST:它是源代码语法结构的抽象表示。它以树的形式表示编程语言的语法结构,树上的每个节点表示源代码中的一次结构词法分析:将一个字符序列转换为词(Token)序列的过程。leta-2//UncaughtSyntaxError:Unexpectedtoken'-'//表示词法分析失败,你的代码语法有问题。“程序”、“语句”、“表达式”等短语。解析器判断源程序的结构是否正确。@babel/traverse的引入提供节点遍历和增删改查操作,实现AST更新转换。这个方法接受一个AST对象和一个访问者对象。//traverse(ast,visitors)的具体应用为了完成源码到源码的转换,@babel/parser将代码字符串转换为JS中易于操作的对象,间接实现了对源码的转换源代码通过对象的转换。为了能够转换AST节点的结构,我们需要能够访问该节点。Babel提供了一个遍历AST的工具@babel/traverse。通过遍历方法,开发者可以方便的访问语法树上的每个节点;同时,遍历访问在原有模式下增加了对访问节点的操作方法,用于增删改AST节点。当然你也可以按照自己的方式遍历AST。众所周知,深度优先(栈)和广度优先(队列)都可以完成对一棵树的遍历。traverse采用深度优先的策略。traverse为我们提供了两次访问节点的机会,即开始遍历当前节点时的访问(enter)和遍历结束时的退出访问(exit)。通过遍历在指定的AST节点完成日志输出操作:consttraverse=require('@babel/traverse').default//traverse(ast,visitors)//ast语法树对象//visitors用于访问指定的syntaxtreeobject节点的对象traverse(ast,{//astnode//FunctionDeclaration函数声明node,FunctionDeclaration(path,state){constname='state.file.opts.filename'console.log(name)},//通过对象Configure开始访问和退出访问节点操作Identifier:{enter(path){console.log(322)//path.toString会调用generator方法将节点转换成代码字符串console.log(path.toString())},exit(){console.log(3224)}},}但是通常我们访问AST节点的目的并不是简单的实现日志操作,而是为了更新当前的AST节点。为此,遍历在parser方法得到的节点上添加节点操作相关的方法,可以通过这些操作方法在节点上实现节点的更新操作,只要将上面的日志操作换成的更新操作即可节点。主要操作方法如下:insertBefore(nodes_:t.Node|t.Node[]):将提供的节点插入到当前节点之前当前节点之后的节点unshiftContainer(listKey:string,nodes:Nodes,):pushContainer(listKey:string,nodes:Nodes,):replaceWith(node:t.Node|NodePath):替换当前节点remove():移除节点visitor对象的遍历方法接受一个访问者对象,其中定义了一个以节点类型命名的方法,实现对该类型节点的访问和更新操作。//codestringtemplateconstinsert=template(`console.log(PATH+'=====>'+NAME)`)//访问者对象{//用于访问对象属性类型的节点,例如{a:100}a对应的AST节点类型为ObjectPropertyObjectProperty(path,state){const{value}=path.node//判断属性的类型if(!(t.isFunctionExpression(value)||t.isArrowFunctionExpression(value)))返回;constname=get(value,'node.key.name')||''constfilePath=get(state,'file.opts.filename')//节点插入操作path.get('body').insertAfter(insert({PATH:t.stringLiteral(filePath),NAME:t.stringLiteral(name)}))}}访问者对象名称的起源是访问者模式(访问者设计模式是一种将算法与其运行的对象结构分离的方法。这种分离的实际结果是能够addnewoperationstoexistingobjectstructureswithoutmodifyingthestructures.Itisonewaytofollowtheopen/closedprinciple.):provideanobjectwithaseriesofnewmethodisusedtochangetheoriginalobject,但不会改变结构的原始对象。@babel/traverse的相关源码如下://traverse对AST节点进行重新封装,增加节点操作相关函数//相关源码如下Object.assign(NodePath.prototype,NodePath_ancestry,NodePath_inference,NodePath_replacement,//节点替换NodePath_evaluation,NodePath_conversion,NodePath_introspection,NodePath_context,NodePath_removal,//节点删除NodePath_modification,//节点修改相关操作NodePath_family,NodePath_comments,);//节点的二次封装//classNodePath
