美文网首页
理解 Babel 及其插件机制

理解 Babel 及其插件机制

作者: cooody | 来源:发表于2018-06-07 11:27 被阅读0次

    简介

    Babel 是 JavaScript 编译器,更确切地说是源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。 意思是说你为 Babel 提供一些 JavaScript 代码,Babel 更改这些代码,然后返回给你新生成的代码。

    Babel 处理的三个步骤

    • 解析 parse
    • 转换 transform
    • 生成 generate

    相关包和 API

    • @babel/parser (babylon)
    • babel-core
    • babel-types
    • babel-traverse
    • babel-generator

    解析

    该步骤接收代码并输出 AST。 这个步骤分为两个阶段:词法分析(Lexical Analysis)语法分析(Syntactic Analysis)

    const parser = require('@babel/parser')
    const code = 'function square(n) {return n * n;}';
    const node = parser.parse(code) // 解析代码返回AST树
    

    要方便的查看代码的 AST 结构,可以访问 http://astexplorer.net/ 进行测试。Babel 使用一个基于 ESTree 并修改过的 AST,它的内核说明文档可以在 这里 找到。

    function square(n) {
      return n * n;
    }
    

    该函数的 AST 结构如下 (简化了一些属性)

    {
      type: "FunctionDeclaration",
      id: {
        type: "Identifier",
        name: "square"
      },
      params: [{
        type: "Identifier",
        name: "n"
      }],
      body: {
        type: "BlockStatement",
        body: [{
          type: "ReturnStatement",
          argument: {
            type: "BinaryExpression",
            operator: "*",
            left: {
              type: "Identifier",
              name: "n"
            },
            right: {
              type: "Identifier",
              name: "n"
            }
          }
        }]
      }
    }
    

    每一个节点都有如下所示的接口(Interface):

    interface Node {
      type: string;
    }
    

    AST 就是由多层嵌套的 Node 结合而成。

    转换

    转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程 同时也是插件将要介入工作的部分。

    babel-core 向外暴露出 babel.transform 接口

    const result = babel.transform(code, {
        plugins: [
            myPluginName
        ]
    })
    

    生成

    代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。.
    代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。利用 babel-generator 将 AST 输出为转码后的代码字符串

    babel-traverse

    Babel Traverse(遍历)模块维护了整棵树的状态,并且负责替换、移除和添加节点。

    我们可以和 Babylon 一起使用来遍历和更新节点:

    import * as babylon from "@babel/parser";
    import traverse from "babel-traverse";
    
    const code = `function square(n) {
      return n * n;
    }`;
    
    const ast = babylon.parse(code);
    
    traverse(ast, {
      enter(path) {
        if (
          path.node.type === "Identifier" &&
          path.node.name === "n"
        ) {
          path.node.name = "x";
        }
      }
    });
    

    babel-types

    Babel Types模块是一个用于 AST 节点的 Lodash 式工具库,它包含了构造、验证以及变换 AST 节点的方法。 该工具库包含考虑周到的工具方法,对编写处理AST逻辑非常有用。

    简单的说就是封装了一系列方法,方便操作变换 AST 的节点。

    import traverse from "babel-traverse";
    import * as t from "babel-types";
    
    traverse(ast, {
      enter(path) {
        if (t.isIdentifier(path.node, { name: "n" })) {
          path.node.name = "x";
        }
      }
    });
    

    写一个简单的 babel 插件

    使用 Visitor 模式遍历访问 Node 节点

    利用深度遍历每个节点的时候,包含两个时机:进入节点enter 和离开节点 exit,类似 koa 的一个洋葱模型。我们也应该在这两个时机的时候去对 AST 树做对应的转换。以下是Visitor 访问的例子。

    const MyVisitor = {
      // 简化 enter 的形式
      Identifier() {
        console.log("Called!");
      }
      // 完整形式
      //Identifier() {
      //  enter() {...}
      //  exit() {...}
      //}
    };
    
    // 对于这个函数
    function square(n) {
      return n * n;
    }
    
    // 遍历包含了四个 Identifier (1个 square 和 3个 n)
    path.traverse(MyVisitor);
    // 输出四次结果
    Called!
    Called!
    Called!
    Called!
    

    Path

    path 的概念是为了简化对 AST 树的操作。Path 是表示两个节点之间连接的对象。
    在某种意义上,路径是一个节点在树中的位置以及关于该节点各种信息的响应式 Reactive 表示。 当你调用一个修改树的方法后,路径信息也会被更新。 Babel 帮你管理这一切,从而使得节点操作简单,尽可能做到无状态。

    首先编写插件

    这个插件的作用就是简单的把所有变量名为 foo 的变量改为 bar,具体实现如下:

    module.exports = function myPlugin(babel) {
      return {
        visitor: {
          Identifier(path) {
            if (path.node.name === 'foo') {
              path.node.name = 'bar';
            }
          }
        }
      };
    };
    

    调用插件

    const babel = require('babel-core');
    const myPlugin = require('./myPlugin');
    const example = 'const foo = 1; console.log(foo);'
    const res = babel.transform(example, {plugins: [myPlugin]});
    // res.code = "const bar = 1; console.log(bar);"
    

    总结

    简单介绍了 babel 转换代码的基本内容,实现了一个简单的插件。在具体开发插件的过程中,最重要的还是对 AST 的转换,所以需要对 AST 的操作和相关 API 有更加具体的理解。详细信息可以看babel-notebook的介绍。

    相关文章

      网友评论

          本文标题:理解 Babel 及其插件机制

          本文链接:https://www.haomeiwen.com/subject/jaiwsftx.html