babel插件实战:console.log()在周四的时候,

前言

上期文章有个小伙伴提出了一个很有意思的提议

image.png

这提议很有创意,很有意思呀,那必须安排

需求描述

我们在本地开发的时候会打印一些console.log()来方便调试,和打印执行结果

比如

console.log('你好');

现在我们要实现的结果是,在周四的时候,console.log变成如下

console.log('疯狂星期四,v我50🐶','你好');

实现思路拆解

1. 首先我们要在astexplorer网站查看console.log的AST:

image.png

program 是代表整个程序的节点,它有body属性代表程序体,存放statement(语句)数组,就是具体执行的语句的集合。因为一个程序就是由不同的语句组成的,所以statement是一个数组。

CallExpression(函数调用表达示):callee 属性是一个表达式节点,表示函数体,arguments 是一个数组,元素是表达式节点,表示函数参数列表.

其他的我们不用知道太多,知道太多也记不住,我们主要找我们想改变的东西所在的type

我们要做的是在遍历AST的时候对console.logconsole.infoapi自动插入一些参数,也就是通过visitor指定对callExpression的AST做一些修改。

2. 打开transform,开始编写插件

image.png

在这里我们穿插讲一下@babel/types

@babel/types是做什么用的呢?

我们看一下文档上的解释

This module contains methods for building ASTs manually and for checking the types of AST nodes.(这个模块包含一些手动创建AST和检测AST类型的方法)

这个模块是非常常用的

@babel/types文档地址

比如我们想创建一个字符串,那么我们就可以使用

t.stringLiteral(value);

如果我们想判断是不是字符串 可以使用

t.isStringLiteral(node, opts)

还是很好记的

首先我们判断函数的类型是否是isMemberExpression,然后判断函数体的名称等

image.png

现在我们实现如下,还是比较简单的,但是右侧显示我们输入的字符串都是unicode形式,问题不大,稍后我们会配置,然后把写的plugin内容,放到我们项目中

./pluginDemo

module.exports = (babel) => {
  const { types: t } = babel;

  return {
    name: "ast-transform", // not required
    visitor: {
      CallExpression(path) {
        if (t.isMemberExpression(path.node.callee)
          && path.node.callee.object.name === 'console'
          && ['log', 'info', 'error', 'debug'].includes(path.node.callee.property.name)
        ) {

          path.node.arguments.unshift(t.stringLiteral('疯狂星期四,v我50🐶'))
        }
      }
    }
  };
}

.babelrc中配置generatorOpts,这个主要是解决显示中文问题的

{
  "plugins": ["./pluginDemo.js"],
  "generatorOpts" :{
    "jsescOption": {
      "minimal": true
    }
  }
}

测试一下,看一下dist中的输出,没有问题

image.png

3.判断是否是周四

如果获取当前是否是周四呢?

const today = new Date().getDay(); // 获取当前星期几,0表示星期日,1表示星期一,以此类推

所以最后完整代码

module.exports = (babel) => {
  const { types: t } = babel;
  const today = new Date().getDay();
  return {
    name: "ast-transform", // not required
    visitor: {
      CallExpression(path) {
        if (today !== 4) {
          return;
        }
        if (t.isMemberExpression(path.node.callee)
          && path.node.callee.object.name === 'console'
          && ['log', 'info', 'error', 'debug'].includes(path.node.callee.property.name)
        ) {
          path.node.arguments.unshift(t.stringLiteral('疯狂星期四,v我50🐶'))
        }
      }
    }
  };
}

需求变更

当我们这样子打印console.log的时候,有时候因为console.log的日志因为比较乱,反而影响观看,所以我们决定在consol.log之前打印---疯狂星期四,v我50🐶

在讲解代码之前我们先来讲解一下@babel/template

@babel/template

通过 @babel/types 创建 AST 还是比较麻烦的,要一个个的创建然后组装,如果 AST 节点比较多的话需要写很多代码,这时候就可以使用 @babel/template 包来批量创建。

这个包有这些 api:

const ast = template(code, [opts])(args);
const ast = template.ast(code, [opts]);
const ast = template.program(code, [opts]);

比如我们要创建一个console.log(), 因为console.log是表达式,所以我们要创建一个expression

const string = "疯狂星期四,v我50🐶";
const newNode = template.expression(`console.log("${string}")`)();

添加console.log注意点

因为我们要往console.log前面添加console.log,而我们新添加的console.log他也会遍历,这样就会出现一个无限循环的问题

如何解决呢?

我们可以在新添加的节点上加上一个标识,如何使新添加的console.log,那么就不用遍历

 newNode.isNew = true;

最终我们的代码

const template = require('@babel/template');

module.exports = (babel) => {
  const { types: t } = babel;
  const today = new Date().getDay();
  return {
    name: "ast-transform", // not required
    visitor: {
      CallExpression(path) {
        if (today !== 4) { return; }
      
        if (
          t.isMemberExpression(path.node.callee) &&
          path.node.callee.object.name === "console" &&
          ["log", "info", "error", "debug"].includes(path.node.callee.property.name)
        ) {
          if (path.node.isNew) {
            return;
          }
          const string = "疯狂星期四,v我50🐶";
          const newNode = template.expression(`console.log("${string}")`)();
          newNode.isNew = true;
          path.insertBefore(newNode);
        }
      }
    }
  };
}

总结

  1. 今天我们学习了@babel/types @babel/template的使用
  • @babel/types主要用于创建AST节点和判断AST节点类型
  • @babel/template主要是因为babel/types创建AST节点比较复杂,所以使用babel/template来进行创建
  1. 然后我们学习了如何插入节点,如何防止死循环的问题

参考

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务