babel 7 插件开发相关

1,636 阅读3分钟

准备工作

此文前,需要先阅读

  1. 抽象语法树 AST 与 编译器 Compiler
  2. babel 7 全套
  3. Babel 6 插件手册 (PS:尽管大部分 API 已过时,但是大概的理念仍然适用)

概念

  • 树形遍历
  • Visitors(访问者)
  • Paths(路径)
  • State(状态)
  • Scopes(作用域)
  • Bindings(绑定) —— 或者叫变量引用?
  • 转换操作(插件 / transform / 节点操作) —— 类似 jQuery

PS:如果不开发插件,其实看插件相关内容意义不大,但对于理解 babel 整套流程,还是有一定帮助。

@babel/helper-xxxx-xxxx

各种各样的辅助函数、方法 / 功能。例如:

  1. @babel/helper-plugin-utils index.js 辅助插件开发,但只是做了一层 wrapper
  2. @babel/helper-builder-react-jsx index.js 用于 @babel/plugin-transform-react-inline-elements index.js@babel/plugin-transform-react-jsx-compat index.js 转换插件
  3. 等等 ...

@babel/plugin-xxxx-xxxx

为什么会有 plugin-syntax-xxxx-xxxxplugin-transform-xxxx-xxxx 插件? —— Transform plugin vs Syntax plugin in Babel

plugin-syntax-xxxx-xxxxplugin-transform-xxxx-xxxx 的前提,或者说,就是用来给 plugin-transform-xxxx-xxxxplugin-proposal-xxxx-xxxx 继承用的。

需要有 plugin-syntax-xxxx-xxxx,设定语法解析的方式,对应的语法,才能被 @babel/parser 正确地处理,类似的: 如果不设定解析 generator 函数,function*(x) { } 肯定是报错的

再例如:

  1. @babel/plugin-proposal-function-bind index.js 作为转换插件,其继承了
  2. @babel/plugin-syntax-function-bind index.js,而 @babel/plugin-syntax-function-bind index.js 内部,通过 parserOpts.plugins.push("functionBind"); 设定了 functionBind 的解析
  3. parser 阶段,根据 functionBind 的设定,进行不同的 词法分析(tokenizer/index.js) (其他插件可能也会有不同的语法分析),最终生成 AST
  4. @babel/plugin-proposal-function-bind 对 AST 进行转换

所以,如果我们想编写 babel 插件来将 @@@hello 转换为 world.hello 是做不到的,因为 @@@hello 在 babel 的 parser 上不支持。其内置的,可以进行配置的额外插件见:@babel/parser/typings/babel-parser.d.ts

而如果我们想编写一些插件,又依赖一些语法(可能该新语法未启用),需要进行:

manipulateOptions(opts, parserOpts) {
  // 类似的 parser 插件处理
  parserOpts.plugins.push("functionBind");
}

@babel/plugin-proposal-xxxx-xxxx

@babel/plugin-transform-xxxx-xxxxx,只是一些未确定落地的草案 AST transform 实现。

@babel/types

包含功能

  • definitions —— 定义 (包括一些节点名的别名)
  • builders —— 节点生成工具
  • validators —— 节点判断工具
  • asserts —— 节点断言工具,就是 validators 的包装,如果判断不通过,会报错
  • converters
  • modifications
  • ...

用于生成 AST、AST 节点类型检查等等,类似 lodash 的工具库

详细内容见:@babel/types docs

@babel/template

在编写 babel 插件时,如果涉及到大规模代码变动 / 转换,如果只使用 @babel/types,大概率会累死

此工具使用 @babel/parser 将字符串处理成 AST,并替换掉占位符内容

例如:@babel/helper-function-name

开发个最简单的插件

const pluginUtils = require('@babel/helper-plugin-utils')
const babel = require('@babel/core')
const t = babel.types

const pluginDemo = pluginUtils.declare(api => {
  api.assertVersion(7);

  return {
    name: "plugin-demo",
    visitor: {
      BinaryExpression(path) {
        if (path.node.operator !== "===") {
          return;
        }
        path.node.left = t.identifier("sebmck");
        path.node.right = t.identifier("dork");
      },
    }
  };
});

// config
module.exports = function (api) {
  api.cache(true)

  const presets = [
    ['@babel/env', {
      useBuiltIns: 'usage',
      corejs: 3,
    }]
  ]

  return {
    presets,
    plugins: [
      ['@babel/transform-runtime', {
        corejs: 3,
      }],
      pluginDemo,
    ]
  };
}

其他