动起来之练练babel插件

417 阅读3分钟

前言

在如今前端项目开发中,已经离不开babel的参与,很多时候我们都只是用了babel的预设presets,几乎都没有修改过它的配置,也不曾去自己手写一个babel plugin,对它的了解都只是停留在使用的地步,在此,让我们动起来,自己去实现一个babel plugin去感受一下babel是如何处理。

babel

babel是什么?

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

babel能做什么?

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过引入第三方 polyfill 模块,例如 core-js
  • 源码转换(codemods)

详情请点击 >> babel官网走起

babel原理

babel的转码过程其实就是三步走:解析(parse)、转换(transform)、和生成(gennerate)。这三步分别由 @babel/parser@babel/core(大集合一个) 和 @babel/generator 来完成。

解析(parse)

在此阶段,babel会把源码解析生成AST(抽象语法树),此步骤由两部分组成:词法分析与语法分析。词法分析会将字符串形式的代码转换成 tokens 流, 语法分析会将 tokens 流转换成AST。

code -> tokens -> AST

babel解析器以前使用的是 Babylon,到了 babel7 使用的是由 Babylon 发展而来的 @babel/parser

转换(transform)

重点: 经过了上一步的处理,我们得到了 AST,开始开刀,做我们需要的调整。

  1. 通过 @babel/traverse 对AST进行遍历。
  2. 找到我们需要修改/替换.....的点。
  3. 通过 @babel/types (babel的一个工具库) 对AST进行修改操作。
  4. 生成新的 AST。

生成(gennerate)

我们得到修改后的 AST 然后需要用 @babel/generator 把 AST 转换成代码。

实现一个 babel 插件

说了babel的转换过程,现在是时候来动手来实现一个插件,我们在开发的工程中,不免会使用 console 来输出某些信息,但是在线上环境,一般都是会把这些信息输出给删除掉。

我们随意创建一点小代码

function foo() {
    const num = 123;
    console.log(num);
}

然后通过这里在线转成 AST

观察它的结构

image.png

创建一个babel插件文件,直接在babel的plugin使用我们新创建的plgun文件

image.png

然后简单实现,我们可以通过 @babel/types里面提供的方法去检测 AST 的某些节点,然后找到相应的节点删除

module.exports = function({ types: t }) {
  return {
    name: "clear-console",
    visitor: {
      CallExpression(path) {
        const callee = path.node.callee;

        if (!t.isMemberExpression(callee)) {
          return;
        }

        if (t.isIdentifier(callee.object, { name: 'console' })) {
          path.remove();
        }
      }
    }
  }
}

至此,简单的插件就写好了,当然,我们也可以拓展,可传入参数,也可以获取 process.env.NODE_ENV 的值判断是否在正式环境启动删除 console

我们传入一个 isRemove 参数来控制是否删除 image.png

module.exports = function({ types: t }) {
  return {
    name: 'clear-console',
    visitor: {
      CallExpression(path, state) {
        const callee = path.node.callee;
        const { opts } = state;

         // 我们获取参数做判断
        if (!opts.isRemove) {
          return;
        }

        if (!t.isMemberExpression(callee)) {
          return;
        }

        if (t.isIdentifier(callee.object, { name: 'console' })) {
          path.remove();
        }
      }
    }
  }
}

结束

以上直接在 react 项目中,写一个babel插件。

第一次写文章,写得不好请多见谅。