前端AST详解,手写babel插件

news2025/1/16 0:57:18

🐱个人主页:不叫猫先生
🙋‍♂️作者简介:专注于前端领域各种技术,热衷分享,关注我会给你带来一些不一样的认知和成长。
📝个人签名:不破不立

🍬本文目录

  • 🥙一、前言
  • 🥪二、节点介绍
  • 🌮三、Babel基础
  • 🍰四、案例展示
  • 🍔五、手写babel插件

🥙一、前言

抽象语法树(Abstract Syntax Tree,AST),是源代码(不仅限于JavaScript,同时还应用于其他语言,例如: Python,Rust等)语法结构的⼀种抽象表示。它以树状的形式表现编程语⾔的语法结构,树上的每个节点都表示源代码中的⼀种结构。
AST 运⽤⼴泛,⽐如:

  • ⾼级语⾔的编译、机器码的⽣成⼀些⾼级编辑器的错误提示、代码⾼亮、代码⾃动补全;
  • 对于前端来说很多⼯具,例如 elint 、 pretiier 对代码错误或⻛格的检查,babel、typescript对代码的编译处理等等。

AST在线预览网站
Bable AST官网

🥪二、节点介绍

本文示范数据:

window.a = 3;
let a = 2, b = 3;
let obj = {
	name: '张三',
	age: "18",
	interest: ["篮球", "羽毛球"],
	add: function (a, b) {
		setTimeout(() => {
		})
		return a + b + 1000
	},
	multiplication: function (a, b) {
		if (a) {
			b = a
		} else {
		}
		return a * b + 1000
	}
}
  • type:标识节点的类型。
  • Identifier(标识符):简单来说就是我们写 JS 时自定义的名称,如变量名,函数名,属性名,都归为标识符,值存放于字段name中。
    在这里插入图片描述
  • CallExpression(函数表达示):比如:setTimeout(()=>{})。callee 属性是一个表达式节点,表示函数,arguments 是一个数组,元素是表达式节点,表示函数参数列表.
    在这里插入图片描述
  • MemberExpression(成员表达式节点):即表示引用对象成员的语句,object是引用对象的表达式节点,property 是表示属性名称,computed 如果为 false,是表示 . 来引用成员,property 应该为一个 Identifier 节点,如果 computed 属性为 true,则是 [] 来进行引用,即 property 是一个Expression 节点,名称是表达式的结果值。window.a对应的AST如下:
    在这里插入图片描述
  • AssignmentExpression(赋值表达式节点):operator 属性表示一个赋值运算符,left 和 right是赋值运算符左右的表达式
  • ArrayExpression(数组表达式节点): interest:[“篮球”,“羽毛球”],elements 属性是一个数组,表示数组的多个元素,每一个元素都是一个表达式节点。
    在这里插入图片描述
  • VariableDeclaration(变量声明表达式):kind 属性表示是什么类型的声明,值可能是var/const/let。declarations表示声明的多个描述,因为我们可以这样:let a = 2,b=3
    在这里插入图片描述
  • VariableDeclarator(变量声明的描述):id 表示变量名称节点,init 表示初始值的表达式,可以为 null
    在这里插入图片描述
  • IfStatement(if表达式):if(true),test 属性表示 if (…) 括号中的表达式。
    • consequent 属性是表示条件为 true 时的执行语句,通常会是一个块语句。
    • alternate 属性则是用来表示 else 后跟随的语句节点,通常也会是块语句,但也可以又是一个 if 语句节点,即类似这样的结构:if (a) { //… } else if (b) { // … }。alternate 当然也可以为 null。

在这里插入图片描述

  • Literals字面量
    • StringLiteral 字符串字面量(“foo”)
    • NumericLiteral 数值字面量(123)
    • BooleanLiteral 布尔字面量 (true)
    • TemplateLiteral 模板字面量 (${obj})

🌮三、Babel基础

Babel 是一个 JavaScript 的转译器,其执行过程就是一个编译转换的过程。作为一个js转译器,babel暴露了很多 api,利用这些 api 可以完成源代码到 AST 的 parse,AST 的遍历与处理以及目标代码的生成。babel将这些功能的实现放到了不同的包里面,下面逐一介绍。

  • @babel/parser 解析源码得到AST
  • @babel/traverse 遍历 AST节点
  • @babel/types 用于构建AST节点和判断AST节点类型
  • @babel/generate 打印 AST,生成目标代码和 sorucemap(即将ast转换成js代码)

babel的处理步骤:主要有三个阶段:解析(parse), 转换 (transform),生成(generate)。

  • parse
    将源码转成 AST,用到@babel/parser模块。

  • transform
    对AST 进行遍历,在此过程中对节点进行添加、更新及移除等操作。因此这是bebel处理代码的核心步骤,是我们的讨论重点,主要使用@babel/traverse@babel/types模块。

  • generate
    打印 AST 成目标代码并生成 sourcemap,用到@babel/generate模块。

接下来我们来重点了解转换这一步,上面我们提到,转换的第一步是遍历AST。说到这里就不得不提到一个设计模式——访问者模式。

访问者模式,即将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式,简单来说,就是定义了用于在一个树状结构中获取具体节点的方法。当访问者把它用于遍历中时,每当在树中遇见一个对应类型时,都会调用该类型对应的方法。

🍰四、案例展示

从 babel7 开始,所有的官方插件和主要模块,都放在了 @babel 的命名空间下。从而可以避免在 npm 仓库中 babel 相关名称被抢注的问题,并且采用了Babel Monorepo风格的仓库。在测试之前需要安装@babel/core@babel/cli@babel/preset-env

yarn add @babel/core @babel/cli -D

@babel/core 是Babel 实现转换的核心,他是依赖能力更底层的 @babel/parser @babel/code-frame@babel/generator@babel/traverse@babel/types等。

  • @babel/parser: 接受源码,进行词法分析、语法分析,生成AST。
  • @babel/traverse:接受一个AST,并对其遍历,根据preset、plugin进行逻辑处理,进行替换、删除、添加节点。
  • @babel/generator:接受最终生成的AST,并将其转换为代码字符串,同时此过程也可以创建source map。
  • @babel/types:用于检验、构建和改变AST树的节点

@babel/cli 是 Babel 提供的命令行,它可以在终端中通过命令行方式运行,编译文件。
@babel/preset-env' Babel 只是一个’编译器’你需要告诉他转换规则,需要在transformer,利用我们配置好的 plugins/presets把 Parser生成的 AST转变为新的 AST,即@babel/preset-env'就是一套转换规则集合。
下图为转换流程let声明转换为var声明
在这里插入图片描述

const parser = require('@babel/parser');
const traverse = require('@babel/traverse');
const generator = require('@babel/generator');
const transToLet = code => {
  const ast = parser.parse(code);
  // 访问者对象
  const visitor = {
    // 遍历声明表达式
    VariableDeclaration(path) {
      if (path.node.type === 'VariableDeclaration') {
        // 替换
        if (path.node.kind === 'var') {
          path.node.kind = 'let';
        }
      }
    },
  };
  traverse.default(ast, visitor);
  // 生成代码
  const newCode = generator.default(ast, {}, code).code;
  return newCode;
};
const code = `const a = 1
var b = 2
let c = 3`;
console.log(transToLet(code)) 

通过parse解析得到了ast,具体如下:

Node {
  type: 'File',
  start: 0,
  end: 31,
  loc: SourceLocation {
    start: Position { line: 1, column: 0, index: 0 },
    end: Position { line: 3, column: 9, index: 31 },
    filename: undefined,
    identifierName: undefined
  },
  errors: [],
  program: Node {
    type: 'Program',
    start: 0,
    end: 31,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: undefined
    },
    sourceType: 'script',
    interpreter: null,
    body: [ [Node], [Node], [Node] ],
    directives: []
  },
  comments: []
}

执行

node babel.js

输出

const a = 1;
let b = 2;
let c = 3;

可见var都变成了let

🍔五、手写babel插件

该插件为superLog,源码如下:

const generator = require('@babel/generator');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse');
const types = require('@babel/types');
const trans = require('./trans.js')
const addNode = code => {
  const ast = parser.parse(code);
  // 访问者对象
  const visitor = {
    // 遍历调用表达式
    CallExpression(path) {
      const { callee } = path.node;
      if (types.isCallExpression(path.node) && types.isMemberExpression(callee)) {
        const { object, property } = callee;
        if (object.name === 'console' && property.name === 'log') { 
          const newArg = trans(path.node.arguments);
          path.node.arguments = [...newArg];
        }
      }
    },
  };
  traverse.default(ast, visitor);
  // 生成代码
  const newCode = generator.default(ast, {}, code).code;
  return newCode;
};

//callee

Node {
  type: 'MemberExpression',
  start: 86,
  end: 97,
  loc: SourceLocation {
    start: Position { line: 8, column: 0, index: 86 },
    end: Position { line: 8, column: 11, index: 97 },
    filename: undefined,
    identifierName: undefined
  },
  object: Node {
    type: 'Identifier',
    start: 86,
    end: 93,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: 'console'
    },
    name: 'console'
  },
  computed: false,
  property: Node {
    type: 'Identifier',
    start: 94,
    end: 97,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: 'log'
    },
    name: 'log'
  }
} 

//path.node.arguments的值

[
  Node {
    type: 'Identifier',
    start: 98,
    end: 99,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: 'a'
    },
    name: 'a'
  },
  Node {
    type: 'MemberExpression',
    start: 101,
    end: 108,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: undefined
    },
    object: Node {
      type: 'MemberExpression',
      start: 101,
      end: 106,
      loc: [SourceLocation],
      object: [Node],
      computed: false,
      property: [Node]
    },
    computed: false,
    property: Node {
      type: 'Identifier',
      start: 107,
      end: 108,
      loc: [SourceLocation],
      name: 'b'
    }
  },
  Node {
    type: 'CallExpression',
    start: 110,
    end: 118,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: undefined
    },
    callee: Node {
      type: 'MemberExpression',
      start: 110,
      end: 116,
      loc: [SourceLocation],
      object: [Node],
      computed: false,
      property: [Node]
    },
    arguments: []
  }
]

新建trans,js文件

const types = require('@babel/types');
// 获取父辈节点并拼接
const getNodeName = node => {
  const getPreValue = node => {
    if (node.object && node.property) {
      return `${node.property.name}.${getPreValue(node.object)}`;
    } else {
      return node.name;
    }
  };
  return getPreValue(node)
    .split('.')
    .reverse()
    .map((item, index, arr) => (index === arr.length - 1 ? item : `${item}.`))
    .join('');
};
const actionMap = {
  // 调用表达式
  CallExpression: node => getNodeName(node.callee),
  // 标识符
  Identifier: node => node.name,
  // 成员表达式
  MemberExpression: node => getNodeName(node),
  // 字符串
  StringLiteral: node => '',
};
const trans = list => {
	//初始化一个数组长度为传的参数2倍
  let res = new Array(list.length * 2).fill(null);
  list.forEach((node, index) => {
    res[index * 2 + 1] = node;
	console.log(node.type,'1111111111111111')
	console.log(node,'22222222222222222')
    const strNodeName = actionMap[node.type](node);
    res[index * 2] = strNodeName ? types.stringLiteral(`${strNodeName}`) : '';
  });
  return res;
};
module.exports = trans;

其中node.type分别是:

Identifier
MemberExpression
CallExpression

最后打印结果为

const obj = {
  a: {
    b: 'xiaom'
  },
  fn: () => null
};
const a = 2;
console.log("a", a, "obj.a.b", obj.a.b, "obj.fn", obj.fn());

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

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

相关文章

C++课堂整理--第二章内容

提前声明: 本文内容为华北水利水电大学研究生C课程,如有 侵权请告知,作者会予以删除 1程序控制结构 语句是程序的基本语法成分。程序设计语言的语句按功能可以分成三类:声明语句 指示编译器分配内存,或者提供程序…

Windows服务器配置证书

以windows server 2012为列 1.打开服务器管理器 2.添加角色和功能 3.点击下一步 4.继续下一步 5.继续下一步 6.选择证书服务 7.添加该功能 8.继续下一步 9.继续下一步 10.继续下一步 11.添加证书颁发机构和证书颁发机构web注册 ,然后点击下一步 12.点击安装 13.再次…

Linux|centos二进制方式安装系统和网络监控神器prometheus+grafana(装逼神器它来了)

Prometheus简单介绍: Prometheus使用Go语言开发,是Google BorgMon监控系统的开源版本,怎么产生的就不在这讨论了,反正就是香,简单易用。 2016年由Google发起Linux基金会旗下的原生云基金会(Cloud Native Computing F…

apache-atlas-hive-hook-源码分析

Atlas Hook类图 Hive 元数据变更有2种实现: 1)基于 Hook 函数实现,实现类为 HiveHook 2)基于MetaStoreEventListener 实现, 实现类为HiveMetastoreHookImpl 所以提供2 种配置,即配置钩子函数或监听器,我们目前采用的是…

nvm包管理工具下载安装

1,去github官网,输入nvm-windows,点击第一个nvm项目,在右侧点击releases,选择箭头指向的安装包 2,下载很快,但是安装前,得先卸载本机的nodejs,并且为nvm的包创建一个英文文件夹,…

Java---Map双列集合

目录 一、双列集合的介绍 二、Map的使用 1:Map中常见的API (1)put方法 (2)remove方法 2:Map的遍历 (1)通过键找值的方式遍历 (2)通过键值对对象遍历 &…

FRNet代码

代码目录简简单单,令人心旷神怡。 模型框架: 数据增强包括; 接着看一下数据集: import os from PIL import Image import numpy as np from sklearn.model_selection import train_test_splitimport torch import torch.utils.data as da…

Tomcat+Maven+Servlet安装与部署

文章目录前言一、Tomcat8下载安装二、MavenServlet部署1.创键Maven项目(idea2021community)2.pom.xml下引入servlet依赖3.main下创建webapp/WEB-INF/web.xml4.验证HttpServlet是否导入(配置WebServlet路径)5.手动打包web项目6.浏览…

DAMA-CDGA/CDGP数据治理认证包括哪几个方面?

DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义,帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力,促进开展工作实践应用及实际问题解决,形成企业所需的新数字经济下的核心职业…

【夯实Kafka知识体系及基本功】分析一下消费者(Consumer)实现原理分析「原理篇」

Consumer消费者 消费者可以从broker中读取数据。 一个消费者可以消费多个topic中的数据(其中一个partion)。 Consumer Group(消费组) 每个Consumer属于一个特定的Consumer Group。 可为每个Consumer指定group name&#xff0c…

自动驾驶--定位技术

[整理自百度技术培训中心课程](https://bit.baidu.com/products?id70) 为什么无人车需要精确的定位系统 在地下车库实现自动泊车的一个非常关键的技术是什么呢?那就是定位技术。 为什么无人车需要一个精确的定位系统。为什么无人车需要精确的定位系统&#xff…

网络线缆连接器和线槽

一、信息插座 1、信息插座简介 信息插座是终端(工作站)与水平干线子系统连接的接口,在水平干线子系统中双绞线的两 端是直接压接到配线架和信息插座中的,不需要跳线。 2、信息插座的配置 综合布线系统的设计,应该根据实际情况确定 所需信息插座个数和分布位置,也就决…

计算机的另一半

本篇先介绍了计算机中数字编码,地址的概念。然后介绍了组成计算机的另外一半内容,也就是CPU,和前面的计算机一半合起来就可以组成一个简易版的计算机了。至此终于大概看到了计算机的全貌。 数字编码系统 这里我们简单说一下计算机里数字编码…

智慧环卫解决方案-最新全套文件

智慧环卫解决方案-最新全套文件一、建设背景二、思路架构三、建设方案四、获取 - 智慧环卫全套最新解决方案合集一、建设背景 城市环境卫生管理是一项复杂而系统的社会工程,是与人民群众生活联系最密切的重要工作之一。环卫水平不仅是一个城市的“脸面”&#xff0…

kotlin 之单例类详解

object 单例对象的声明: object Model{var temp "1"val temp2 "2"const val temp3 "3" }抛出疑问:使用object修饰的类,是哪种类型的单例模式 这里我们先回顾一下java六种单例模式 1. 饿汉式 public c…

String、StringBuffer和StringBuilder类的区别

在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。String 类是不可变类,即一旦一个 String 对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。 Java 提供了两个可变字符串类 Stri…

EFLFK——ELK日志分析系统+kafka+filebeat架构(3)

ELFK——ELK结合filebeat日志分析系统(2)_Evens7xxX的博客-CSDN博客 紧接上期,在ELFK的基础上,添加kafka做数据缓冲 附kafka消息队列 nginx服务器配置filebeat收集日志:192.168.116.40,修改配置将采集到的…

SoC-ZCU106求解非线性方程(一):环境安装

一、大家好久不见,本次给大家带来的是SoC求解非线性方程问题。计划发布三篇文章,这是第一篇----环境安装。 主要的解决的问题是:PL侧给PS传输数据,然后PS将数据作为已知量求解非线性方程,为了简化问题复杂度&#xff…

中睿天下实力入选2022信创产业独角兽TOP100

近日,中国科学院主管的权威媒体《互联网周刊》、德本咨询、eNet研究院联合发布了“2022信创产业独角兽100强”榜单。中睿天下凭借在网络安全攻击溯源领域的深耕、硬的技术能力和突出的产品创新力,实力入选榜单,在上榜的安全企业中&#xff0c…

Docker(四)—— 部署Nginx、Tomcat

一、部署Nginx 将Nginx后台挂载后,用curl命令访问,进行本机自测: 二、部署Tomcat 出现404页面的原因:为了缩小镜像的大小,官方下载的Tomcat镜像是精简版的,只提供了必要、核心的内容。我们进入容器内部的/w…