babel知识体系漫谈

2,019 阅读4分钟

钉钉前端团队原创,点击右上角关注我们,了解更多前端技术

作者: 烛象

引言

在JavaScript蓬勃发展的今天,ES6/7、typescript已经成为代码编写的标配。

上一篇文章,我们介绍了钉钉IDL和自动生成typescript定义的工具,本文将会介绍AST相关js知识:babel。

关于babel

一句话阐述什么是babel:

babel是一个主要用于将ES2015+版本的代码编译成向下兼容(比如ES5/ES3)js版本的编译器。

// Babel Input: ES2015 arrow function
[1, 2, 3].map((n) => n + 1);
// Babel Output: ES5 equivalent
[1, 2, 3].map(function(n) {
  return n + 1;
});

结合实际使用场景,我们接触到的babel使用方式一般为

  • .babelrc/babel.config.json (babel配置文件)
  • babel-loader (webpack/rollup等)

然而,.babelrc的每一块配置后面究竟代表着babel怎样的处理方式,这个估计很少有人能讲得清楚。

babel知识体系

1、主要组件

  • 源代码到AST: babel/parser(前身为babylon)

依赖acorn/acorn-jsx,用于将源码(如ES2015代码) 编译成 AST(抽象语法树)

  • AST到输出代码: babel/generator

用于将AST转换为最终代码,根据不同的参数option,实现代码功能(比如sourceMap的实现)

2、结构转换

  • 对AST实现颗粒化改造: @babel/traverse

通过AST节点遍历,方便使用方对AST节点进行逻辑重组。

import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
// 源代码
const code = `function square(n) {
  return n * n;
}`;
const ast = parser.parse(code);
traverse(ast, {
  enter(path) {
  // 节点转换
    if (path.isIdentifier({ name: "n" })) {
      path.node.name = "x";
    }
  }
});
// 新代码 function square(x) {\n  return x * x;\n}
console.log(generate(ast))

3、工具组件

  • 代码行列定位: @babel/code-frame

对代码行列进行定位

import { codeFrameColumns } from '@babel/code-frame';
const rawLines = `class Foo {
  constructor()
}`;
const location = { start: { line: 2, column: 16 } };
const result = codeFrameColumns(rawLines, location, { /* options */ });
console.log(result);
// 结果如下
  1 | class Foo {
> 2 |   constructor()
    |                ^
  3 | }
  • 运行时优化(比如重复代码等): @babel/runtime(包含regenerator-runtime)

对代码重复率进行优化,比如优化如下class语法转换

class Circle {}
function _classCallCheck(instance, Constructor) {
  //...
}
var Circle = function Circle() {
  _classCallCheck(this, Circle);
};

通过@babel/runtime,将class语法以模块化的方式替换具体的实现,以达到减少重复代码的目的。

var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() {
  _classCallCheck(this, Circle);
};
  • 代码模板: @babel/template

模板代码,可以对比printf、Mustache的语法

import template from "@babel/template";
import generate from "@babel/generator";
import * as t from "@babel/types";
const buildRequire = template(`
  var %%importName%% = require(%%source%%);
`);
const ast = buildRequire({
  importName: t.identifier("myModule"),
  source: t.stringLiteral("my-module"),
});
// 最终的代码变为var myModule = require("my-module");
console.log(generate(ast).code);

除了以上几个大件之外,babel工具体系还有types、helpers等来优化AST和generate最终代码。接下来要分享的是babel中非常重要的一环: plugins和presets。

4、babel plugins和 babel presets

babel作为编译器,它对代码的转换工作全部依赖于plugins(插件)。如果开发者连babelrc都没有配置的话,代码转换将什么都不做。

对于babel来说plugins的作用是转换代码。然而,js语法何其多,箭头函数、class语法、async/await等等,这么多语法需要非常庞大的插件体系。对于开发者来说,极其不友好。

babel在插件的概念基础上新增了个插件列表的概念,叫做presets(预设)。 比如@babel/preset-stage-0,代表的是支持stage-0语法的插件列表,通过presets解放了开发者极大的配置babel的工作量。

关于babel-polyfill

对于babel7.4.0,这个库官方已经废弃了,取而代之的是core-js/stableregenerator-runtime/runtime。 对于很多babel-polyfill的使用方而言,这个库确实对bundle的最终大小产生了影响。官方推荐的是采用@babel/preset-envuseBuiltIns这个option配合起来,以便只引入你所需要的polyfill。

来看下这个能力有多酷炫:

var a = new Promise();
=转换为=>
import "core-js/modules/es.promise";
var a = new Promise();

var b = new Map();
=转换为=>
import "core-js/modules/es.map";
var b = new Map();

写在后面

关于babel更多的知识,欢迎大家移步babel官网。 希望读完本文你有一定的收获。

招人时间:

钉钉前端团队社招全面开启,求各路技术达人加入。

简历投递邮箱: xiaogang.hxg@alibaba-inc.com

笔者钉钉号: huangxiaogang

扫码关注更多钉钉技术

钉钉技术圈

😊钉钉欢迎扫码关注《钉钉技术圈》

钉钉微信公众号

😊欢迎扫码关注《钉钉大前端团队》微信公众号