前端AST详解,手写babel插件(二)

简介: 前端AST详解,手写babel插件

🌮三、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());
目录
相关文章
|
3月前
|
前端开发 数据可视化 数据挖掘
前端开发者的福音:这些插件让你轻松应对各种复杂需求!
前端开发领域充满创意与挑战,面对复杂需求,开发者常感力不从心。本文通过三个真实案例,介绍如何利用Chart.js、ESLint和Ant Design等强大插件,解决数据可视化、代码质量和UI组件复用等问题,提高开发效率,创造更出色的前端作品。
46 3
|
3月前
|
缓存 前端开发 JavaScript
前端性能优化:Webpack与Babel的进阶配置与优化策略
【10月更文挑战第28天】在现代Web开发中,Webpack和Babel是不可或缺的工具,分别负责模块打包和ES6+代码转换。本文探讨了它们的进阶配置与优化策略,包括Webpack的代码压缩、缓存优化和代码分割,以及Babel的按需引入polyfill和目标浏览器设置。通过这些优化,可以显著提升应用的加载速度和运行效率,从而改善用户体验。
106 6
|
4月前
|
前端开发 JavaScript
乾坤qiankun(微前端)样式隔离解决方案--使用插件替换前缀
乾坤qiankun(微前端)样式隔离解决方案--使用插件替换前缀
752 8
|
4月前
|
存储 前端开发 Java
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
本文介绍了使用Kaptcha插件在SpringBoot项目中实现验证码的生成和验证,包括后端生成验证码、前端展示以及通过session进行验证码校验的完整前后端代码和配置过程。
561 0
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
|
5月前
|
运维 前端开发
前端使用antdesign导出插件跨域问题
前端使用antdesign导出插件跨域问题
69 1
|
4月前
|
资源调度 前端开发 安全
前端实战:基于Verdaccio搭建私有npm仓库,轻松上传与下载自定义npm插件包
前端实战:基于Verdaccio搭建私有npm仓库,轻松上传与下载自定义npm插件包
225 0
|
6月前
|
前端开发 安全 测试技术
[译]一种基于模块联邦的插件前端
[译]一种基于模块联邦的插件前端
|
6月前
|
Web App开发 前端开发 JavaScript
灵魂拷问-前端的作用--chrome插件篇
本文会从浏览器插件应用场景切入,穿插插件基础能力和常见入口的介绍,核心回答如下三个问题:插件可以被使用在哪些场景?不同的使用场景我们的主要代码实现思路是怎样的?我们可以从哪些角度入手自己开发一款可以落地实用的浏览器插件?
|
6月前
|
前端开发 开发者
在前端开发中,webpack 作为模块打包工具,其 DefinePlugin 插件可在编译时动态定义全局变量,支持环境变量定义、配置参数动态化及条件编译等功能。
在前端开发中,webpack 作为模块打包工具,其 DefinePlugin 插件可在编译时动态定义全局变量,支持环境变量定义、配置参数动态化及条件编译等功能。本文阐述 DefinePlugin 的原理、用法及案例,包括安装配置、具体示例(如动态加载资源、配置接口地址)和注意事项,帮助开发者更好地利用此插件优化项目。
186 0
|
7月前
|
前端开发 JavaScript
webpack 和 babel 实用教程【前端必备】
webpack 和 babel 实用教程【前端必备】
73 0

热门文章

最新文章

  • 1
    【Java若依框架】RuoYi-Vue的前端和后端配置步骤和启动步骤
  • 2
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 4
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 5
    详解智能编码在前端研发的创新应用
  • 6
    巧用通义灵码,提升前端研发效率
  • 7
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 8
    智能编码在前端研发的创新应用
  • 9
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 10
    抛弃node和vscode,如何用记事本开发出一个完整的vue前端项目
  • 1
    以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
    24
  • 2
    大前端之前端开发接口测试工具postman的使用方法-简单get接口请求测试的使用方法-简单教学一看就会-以实际例子来说明-优雅草卓伊凡
    43
  • 3
    【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
    25
  • 4
    巧用通义灵码,提升前端研发效率
    84
  • 5
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    137
  • 6
    详解智能编码在前端研发的创新应用
    92
  • 7
    智能编码在前端研发的创新应用
    75
  • 8
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    35
  • 9
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    112
  • 10
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    73