当前位置: 首页 > Web前端 > HTML5

babel基础介绍

时间:2023-04-04 23:36:35 HTML5

前言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,);//节点的二次封装//classNodePathcreate(node,obj,key,listKey?):NodePath{returnNodePath.get({parentPath:this.parentPath,parent:node,container:obj,key:key,listKey,});}@babel/template@babel/types简介这两个工具库用于生成新的AST节点。@babel/types可以生成更简单更简单的节点,比如t.identifier("a"),可以生成{type:'Identifier',name:'a'}节点,可以表示一个变量的声明。@babel/types也可以判断节点类型如constt=require('@babel/types')t.isVariableDeclarator(path)//用来判断是否是变量声明t.isFunctionDeclaration(path)//用的判断是否为函数声明@babel/template可以用来生成复杂的AST模板方法,然后通过配置对象从AST模板生成不同的AST节点。当将模板作为带有字符串参数的函数调用时,您可以提供占位符,这些占位符将在使用模板时被替换。您可以使用两种不同类型的占位符:句法占位符(例如%%name%%)或标识符占位符(例如)。具体应用//创建AST节点consttemplate=require('@babel/template').defaultconstgenerate=require("@babel/generator").default;//生成节点模板constinsert=template(`console.log(100)`)//通过模板生成一个新的AST节点constnode=insert()//生成一个可配置的节点模板consttemp=template(`letNAME=1+1`)//生成一个配置对象NewASTnodeconsttempNode=temp({NAME:'num'})console.log(generate(tempNode).code)//让num=1+1;@babel/generator简介@babel/generator该工具用于将AST转换为代码字符串,即输入为AST对象,输出为源代码字符串。具体应用使用生成工具将模板工具生成的模板节点转换为代码串。//toolimportconstgenerate=require("@babel/generator").default;//生成节点模板consttemp=template(`letNAME=1+1`)//通过配置生成新的AST节点consttempNode=temp({NAME:'num'})//通过generateconstcode=generate(tempNode).code生成转换后的源码字符串综合应用-babel插件对babel的工作原理有一个大概的了解,可以应用babel对实际代码进行打包的过程中。babel应用程序解析器源码转换为ASTtraverse遍历AST,结合template/types更新AST,generator将转换后的AST转换为源码输出babel插件的开发简化了上述过程,因为它是一个插件-in用于开发babel,所以parser和generator过程是我们不需要关心的。插件的重点是操作指定的AST节点,即visitor对象的写法;例子参考babel插件手册:在每个方法中添加logvue。打包过程会缓存插件,所以你修改完成后打包插件可能不会生效。你可以在babel-config.js中修改这个插件的配置,然后打包。//babel.config.js//导入本地编写的插件consttest=require('./src/babel-plugin/test-console')module.exports={presets:[['@vue/app',{useBuiltIns:'entry',}],],//插件配置plugins:[[//...otherplug-ins//自定义本地插件配置,比如通过修改名称清除缓存[test,{名称:'c'}]],};以下是插件示例代码constget=require('lodash.get')//直接定义访问者对象即可,通过一个函数返回该对象module.exports=functiontest(babel,ops){const{types:t,template}=babel//定义一个节点模板,用于插入指定的AST节点constinsert=template(`console.log(PATH+'=====>'+NAME)`)//返回一个包含访问者的对象return{name:'my-plugin2',visitor:{//访问对象方法属性ObjectMethod(path,state){//获取当前属性名constname=get(path,'node.key.name')||''//获取当前文件路径constfilePath=get(state,'file.opts.filename')//在节点路径末尾插入模板生成的新节点对象.get('body').insertAfter(insert({//注意不能直接配置为`${filePath}`,否则最终会被解析为变量名/*Moduleparsefailed:Invalidregularexpressionflag(119:17)*/PATH:t.stringLiteral(filePath),NAME:t.stringLiteral(name)}))},//访问对象属性ObjectProperty(path,state){const{value}=path.node//判断属性的类型if(!(t.isFunctionExpression(值)||t.isArrowFunctionExpression(值)))返回;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)}))}}};}