阅读 715

从零开始写一个babel插件

背景

需要分析JS执行的效率,所以需要记录JS的执行时间,通过执行时间分析JS执行效率。但是由于aync/await使用的盛行,造成在记录函数执行时间的过程中,会记录异步执行的时长,造成时间记录不准确,无法分析JS函数真正执行的时间。

看一段代码

const asyncFn = ms => new Promise((resolve, reject) => {
  setTimeout(resolve, ms);
});

const record = {
  start: 0,
  time: 0,
};

const fn = async () => {
  record.start = performance.now();
  await asyncFn(100);
  record.time = performance.now() - record.start;
  console.log(record.time);
}

fn();
复制代码

通过浏览器控制台可以看到输出结果103.84499999781838,每次执行时间可能会不同,但应该都在这个值附近。可以看出由于aync/await函数的存在导致fn函数的计时不准确,把异步函数的执行时间也计算在内了

在Web端异步函数执行时一般是在请求外部相关接口,此时并不会阻碍Web端执行其他JS函数,不会造成JS阻塞导致页面卡顿,所以这种异步函数的执行时间并不在我们的统计范围内,我们只统计web中代码的执行时间。

修正方式

一般我们可以通过在async/await函数上下分开计时的方式来解决这个问题,具体可以看下段代码:

const asyncFn = ms => new Promise((resolve, reject) => {
  setTimeout(resolve, ms);
});

const record = {
  start: 0,
  time: 0,
};

const fn = async () => {
  record.start = performance.now();
  record.time = performance.now() - record.start;
  await asyncFn(100);
  record.start = performance.now();
  record.time = performance.now() - record.start;
  console.log(record.time);
}

fn();
复制代码

通过在浏览中运行,可以在控制台中看到结果,大概是0.010000003385357559,可以看出确实可以解决异步函数带来的影响,但是需要在每个涉及async/await的地方都要这样手动处理,你不觉得这样很麻烦吗?

目标

使用babel AST的能力,通过分析代码的AST,自动处理async/await的计时问题,避免人工输入大量重复代码,减少出错的机会。

工具

一个优秀的工具是通往目标的阶梯,而babel当之无愧是这个阶梯。

可视化AST平台

点击跳转,可以很方便查看代码AST结构,有助于理解代码和AST结构之间的关系,事半功倍。

将上面两段代码复制进AST,我们可以看到上述代码的AST结构:

对我们来说,需要重点关注的就是type为Program的部分,这里面描述了代码片段的AST结构。

babel生成AST流程介绍

写babel插件之前,需要对babel的运作周期有个清晰的认识,这样我们才能知道自己写的插件什么时候起作用、达到的效果是什么,写插件的过程中才能游刃有余。

Github上对babel的介绍点击跳转,可以查看对babel插件内容的基本介绍,包括AST中各种节点的定义类型、遍历方式、插件入口等,建议详细查看一遍。由于相关内容,该文档已经比较详细,编写插件过程中,方便进行各种API使用方法的查询手册,因此本文中就不再赘述这部分内容了。

找到处理目标节点

由于本次只是涉及async/await函数相关操作,我们将关注type为AwaitExpression类型的AST节点

babel插件编写格式

按照babel插件的格式,我们需要默认导出一个函数,该函数接收一个babel对象作为参数,返回一个具有visitor属性的对象,具体为:

export default function (babel) {
    return {
        visitor: {
            AwaitExpression(path) {},
        },
    };
}
复制代码

在visitor对象中,定义我们需要处理的节点类型进行处理即可,具体逻辑可以参考github上代码

babel插件处理流程

具体流程可以参考github上代码,单元测试部分,主要分为三个部分:

  • 解析代码字符串生成AST,主要利用babylon
  • 遍历AST,进行节点处理,主要利用babel-traverse,这里为了简便生成需要插入代码的AST,使用了babel-template这个工具,使用起来超级方便,再也不用自己手动使用babel-types去手动构建所需要的AST了
  • 根据处理后的AST生成代码,主要利用babel-generator

总结

这样一个简单的babel插件就完成了,当然要是真实环境进行使用的话,还需要考虑很多其他边界条件,这里从整体上介绍babel的插件编写模式,便于后续有相似需求时,可以快速上手。

插件代码示例

可以在github上找到本文中的代码示例,方便进行效果测试和新插件开发。

文章分类
前端