导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

Babel 的原理是什么?

Babel 的转译过程分为三个阶段,这三步具体是:

  1. 解析 Parse : 将代码解析生成抽象语法树(即AST),即词法分析与语法分析的过程
  2. 转换 Transform: 对于 AST 进行变换一系列的操作,Babel 接受得到 AST 并通过 babel-traverse 对其进行遍历,在此过程中进行添加、更新及移除等操作
  3. 生成 Generate: 将变换后的 AST 再转换为 JS 代码, 使用到的模块是 babel-generator

Untitled

如何写一个 Babel 插件?

Babel 解析成 AST,然后插件更改 AST,最后由 Babel 输出代码 。

那么 Babel 的插件模块需要你暴露一个 functionfunction 内返回 visitor

module.exports = function(babel) {
  return {
    visitor: {}
  }
}

visitor 是对各类型的 AST 节点做处理的地方,那么我们怎么知道 Babel 生成了的 AST 有哪些节点呢?

很简单,你可以把 Babel 转换的结果打印出来,或者这里有传送门: AST explorer

Untitled

这里我们看到 const result = 1 + 2 中的 1 + 2 是一个 BinaryExpression 节点,那么在 visitor 中,我们就处理这个节点:

const t = require('babel-types');
const visitor = {
  BinaryExpression(path) {
    const node = path.node;
    let result;
		// 判断表达式两边,是否都是数字
    if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
      switch(node.operator) {
        case "+":
          result = node.left.value + node.right.value;
          break;
        case "-":
          result = node.left.value - node.right.value;
          break;
        case "*":
          result = node.left.value * node.right.value;
          break;
        case "/":
          result = node.left.value / node.right.value;
          break;
        case "**":
          let i = node.right.value;
          while (--i) {
            result = result || node.left.value;
            result = result * node.left.value;
          }
          break;
        default:
      }
    }
		// 如果上面的运算有结果的话
    if (result !== undefined) {
			// 把表达式节点替换成number字面量
      path.replaceWith(t.numericLiteral(result));
    }
  }
}
module.exports = function(babel) {
  return {
    visitor
  }
}

插件写好了,我们运行下插件试试:

const babel = require('babel-core');
const result = babel.transform("const result= 1 + 2;", {
  plugins: [
    require('./index')
  ]
})

console.log(result.code); // const result = 3;

与预期一致,那么转换 const result = 1 + 2 + 3 + 4 + 5; 呢?

结果是: const result = 3 + 3 + 4 + 5;

这就奇怪了,为什么只计算了 1 + 2 之后,就没有继续往下运算了? 我们看一下这个表达式的 AST 树

Untitled

你会发现 Babel 解析成表达式里面再嵌套表达式。

表达式( 表达式( 表达式( 表达式(1 + 2) + 3) + 4) + 5)

而我们的判断条件并不符合所有的,只符合 1 + 2

那么我们得改一改

第一次计算 1 + 2 之后,我们会得到这样的表达式

表达式( 表达式( 表达式(3 + 3) + 4) + 5)

其中 3 + 3 又符合了我们的条件, 我们通过向上递归的方式遍历父级节点

又转换成这样:

表达式( 表达式(6 + 4) + 5)

表达式(10 + 5)

15

修改代码:

if (result !== undefined) {
	// 把表达式节点替换成 number 字面量
	path.replaceWith(t.numericLiteral(result));
	let parentPath = path.parentPath;
	// 向上遍历父级节点
	parentPath && visitor.BinaryExpression.call(this, parentPath);
}

到这里,我们就得出了结果 const result = 15;

让编辑器识别 alias 别名路径

对于 JavaScript 项目(jsconfig.json):

如果你没有 jsconfig.json,可以在项目根目录下创建:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "exclude": ["node_modules", "dist"]
}

对于 TypeScript 项目(tsconfig.json):

如果你使用的是 TypeScript,可以在 tsconfig.json 中添加:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "exclude": ["node_modules", "dist"]
}