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

JavaScrpitAST前端开发实战剖析

时间:2023-03-27 17:50:17 JavaScript

在前端培训和学习中,每一种编程语言都有自己的AST。了解AST并能够进行一些开发,将为我们的项目开发提供很大的便利。带你一探究竟:通过本文,你可以了解什么是JSAST结构和属性babel插件开发JSAST简介AST是抽象语法树。简单地说,程序是以树的形式显示的。每种语言(HTML、CSS、JS等)都有自己的AST,并且有多个AST解析器。回到JS本身,常见的AST解析器包括:?acorn?@babel/parser?Typescript?Uglify-js?等。不同解析器解析出的AST略有不同,但本质上是一样的。本文将基于@babel/parser进行示例和讲解。我们来看一个通用的代码。importajaxfrom'axios'转换后的AST结构如下:{"type":"ImportDeclaration","start":0,"end":24,"loc":{"start":{"line":1,“列”:0},“结束”:{“行”:1,“列”:24}},“说明符”:[{“类型”:“ImportDefaultSpecifier”,“开始”:7,“结束":11,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":11}},"local":{“类型”:“标识符”,“开始”:7,“结束”:11,“loc”:{“开始”:{“行”:1,“列”:7},“结束”:{“line":1,"column":11},"identifierName":"ajax"},"name":"ajax"}}],"importKind":"value","source":{"type":"StringLiteral","start":17,"end":24,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":24}},"extra":{"rawValue":"axios","raw":"'axios"},"value":"axios"}}内容是比想象的还要多?别着急,我们一点点看一个示意图:ImportDeclaration语句的类型,说明是import语句,常见的有:VariableDeclaration:varx='init'FunctionDeclaration:functionfunc(){}ExportNamedDeclaration:exportfunctionexp(){}IfStatement:if(1>0){}WhileStatement:while(true){}ForStatement:for(;;){}没有一一列出,因为它是一个importedexpression,自然分为左右两部分,左边是specifiers,右边是sourcespecifiersspecifiers节点会有一个list来保存specifier如果左边只声明了一个变量,会给出一个ImportDefaultSpecifier如果有多个左边的声明,它将是一个ImportSpecifier列表它是什么m可以在左侧有多个声明吗?请参见下面的示例。import{a,b,c}from'x'variable的声明必须是唯一的,Identifier就是这件事情的来源。源包含一个字符串节点StringLiteral,它对应于引用资源位置。例子中axiosAST是怎么转换的?以babel为例:constparser=require('@babel/parser')letcodeString=`importajaxfrom'axios'`;letfile=parser.parse(codeString,{sourceType:"module"})console.dir(file.program.body)在节点中,你可以打印出AST。通过这个小例子,你应该对AST有了初步的了解。下面说说理解应用场景和实战是什么意思。其实我们在项目中,随处可见AST技术?babel对es6语法的转换?webpack对依赖的收集?Uglify-js对代码的压缩?按需加载组件库babel-plugin?等等。为了更好的理解AST,我们定义一个场景,然后进行实践。场景:将import转换为require,类似babel的转换目标:通过AST转换,将语句importajaxfrom'axios'转换为varajax=require('axios')要达到这个效果,首先我们需要写一个babel-plugin.先把babelPlugin.js代码写成如下:constt=require('@babel/types');module.exports=functionbabelPlugin(babel){functionRequireTranslator(path){varnode=path.nodevarspecifiers=node.specifiers//获取变量名varvarName=specifiers[0].local.name;//获取资源地址varsource=t.StringLiteral(path.node.source.value)varlocal=t.identifier(varName)varcallee=t.identifier('require')varvarExpression=t.callExpression(callee,[source])vardeclarator=t.variableDeclarator(local,varExpression)//新建节点varnewNode=t.variableDeclaration("var",[declarator])//节点替换path.replaceWith(newNode)}return{visitor:{ImportDeclaration(path){RequireTranslator.call(this,path)}}};};测试代码:constbabel=require('@babel/core');constbabelPlugin=require('./babelPlugin')letcodeString=`importajaxfrom'axios'`;constplugins=[babelPlugin]const{code}=babel.transform(codeString,{plugins:plugins});控制台。目录(代码)输出:'varajax=require(“axios”);babel-plugin可以在babel官网获取开发文档,这里简单说明重点:?插件需要返回一个访问者对象?可以拦截所有节点,函数名是节点类型,入参是path,当前节点可以通过path.node获得?@babel/类型提供了大量的节点操作API。您也可以阅读官方网站上的详细说明。你熟悉这里的代码吗?没错,就是.babelrc里面的配置。我们开发的插件在.babelrc的插件中配置后可以全局运行。作者:cd2001cjm