Babel内部模块概述

本文会对babel文档文档从一个推导角度来阐述每个babel模块的作用,尝试理清其中脉络,方便快速理解。

本文不是官网的copyer或者中文翻译

<a name="xVQ3u"></a>

核心

babel的核心功能在@babel/core包中,核心api为transform系列函数:

babel.transform(code, options, function(err, result) {
  result; // => { code, map, ast }
});

该函数可以将es6+代码转译成es5代码,所以被广泛集成在其他工具里面,完成代码的转译工作,如babel-loader内部就是调该api。

在babel中,还提供了@babel/cli@babel/register两个工具,前者提供命令行工具函数对文件进行转译;后者提供require钩子:对node的require函数改造,对后续require函数在执行时自动对模块进行源码转译后在导入。

babel的目标是对代码进行转译,这个过程可以拆解为:解析源码,遍历ast改造代码,重新生成代码这三个过程。为了提高使用范围,在v7+版本中,babel将功能拆解出来了多个工具,主要有:

  • 解析源码: @babel/parser;
  • 遍历ast改造代码: @babel/traverse@babel/plugin-*;
  • 重新生成代码:@babel/generator;
    <a name="Wh9AE"></a>

解析源码

将源码解析,生成ast(抽象语法树),会将:

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

解析成:

{
  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"
        }
      }
    }]
  }
}

ast可以简单的理解为源码字符串进行语法分析后的结构化数据,方便后续进行检查或者改造。

ast中的节点一般还会包含坐标位置,如字符串下标,行数,列数等,更多详细内容请参考官方文档。

老版本babel中使用的是 acornacorn-jsx,在v7以上时,进行了fork改造为@babel/parser

另外@babel/core也集成了@babel/parser功能,可以直接从@babel/core中导出api直接使用:

babel.parse(code: string, options?: Object, callback: Function)

当前的解析器默认只支持最新的es6代码,如果需要兼容一些新语法(非语法糖之类的新特性,新表达式和新操作服,如对象解构,可选表达式,类型等),需要扩展babel语法插件

很多工具其实只需要解析代码即可,如代码检验,如语法高亮,源码中数据收集。
<a name="rzqMT"></a>

遍历ast改造代码

讲过解析器已经将源码解析成更好处理的结构化数据ast,如果需求是对代码进行调整,只需对ast数据进行调整,然后使用生成器生成新的代码即可。但整个babel需要解决的是将所有最新的es6+特性转译成向后兼容的浏览器可执行代码(es5),需要处理的情况众多,如果直接对ast进行改造,那么代码将非常臃肿。且es规范还在不停的迭代中,臃肿的代码的对后续维护迭代也带来巨大的挑战。针对这种困境,必须需要进行架构上的调整,使用插件化架构。

babel即是处于这样一个原因,采用了访问者模式。可以简单的理解为,在对ast进行一个遍历时,每次进入一个新的节点或者退出一个节点时,都会拜访每一个插件,咨询它们是否需要对当前的情况进行处理。这钟架构证了性能,也保证了扩展性。

另一种插件化架构,也就是流式架构,如gulp。也就是插件队列依次对上一个插件处理后的ast对象进行更深一层次的改造。但这种架构,需要多次循环ast, 在实际使用中,一般一个生产项目,文件内容巨大,文件数量居多,会导致性能崩溃。

所以babel核心框架中,只包含了访问节点和调用插件的逻辑。实际对ast的改造,全部转交给了插件。这也是为什么babel自带了那么多@babel/plugin-*插件。同样社区也拥有非常多的插件,从能能够支持flow, typescipt这些新语法。

插件化的架构,也允许使用者进行拔插式配置,根据当前使用场景进行高度定制。这也就是在配置文件中如babrlrc.js可以配置插件的原因。

为了支持高度动态配置化来适配复杂的场景,babel会将每个插件负责的功能划分足够小,一般每个插件只会负责一个特性。这会导致使用时,需要去了解每一个插件的作用,然后在配置文件中配置超长的插件列表,带来巨大的心智负担和维护难度。为了解决这个问题,babel提供了预设的机制。简单的理解就是一个babel配置可以继承另一个配置,那么我们只需要继承社区上或者官方专业人员配置的预设即可,如:

  • @babel/preset-env
  • @babel/preset-react
  • @babel/preset-typescript
  • @babel/preset-flow

为了方便插件中的复用,babel将遍历ast的工具也开放出来为一个单独的模块@babel/traverse。将节点类型的判断和创建节点的工具库,放在了@babel/types

另外对于一些babel中多个模块公用的一些工具,都封装成工具模块,也就是@babel/helper-*系列模块,如:

  • @babel/helper-compilation-targets
  • @babel/helper-module-imports

由于babel自带了那么多插件,所以很多helper其实是插件的辅助工具,如helper-module-imports就是辅助生成一些导入节点。

在es6+转成es5的过程中,很多语法糖语法(语法上的细微调整),如let, const等实现直接用插件调整代码即可解决。但对于其他的需要大端代码才能实现的特性,如Array#includes,生成器,迭代器,async/await, promise等,如果每次都通过代码展开,那么编译后的代码将会巨大。为了解决这个问题,会将includes的实现放在补丁(polyfill)中,然后直接使用补丁中的实现。如生成器,迭代器,async/await, promise等都是通过这种机制支持。

这些的补丁(polyfill)的导入方式也有两种,一种是全量导入,也就是导入@babel/polyfill模块。一种是按需导入,需要使用预设@babel/preset-env,根据实际使用情况,在使用的模块中按需导入@babel/runtime中的补丁(polyfill)。如:

var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");

var Circle = function Circle() {
  _classCallCheck(this, Circle);
};

@babel/polyfill@babel/runtime的底层实现都是core-js

实际情景下,还是存在插件无法解决的情况:一个无法用老代码补丁实现,也无法使用语法糖替换代码的特性,如Proxy对象,这种特性一般需要js引擎从底层提供。在使用这些特性时,需要注意浏览器兼容性。

<a name="uaLlx"></a>

生成代码

使用@babel/generator即可对一个ast树重新生成为代码。

<a name="S8g5j"></a>

配置

我们通常见到的babel配置就是就是用于指导babel行为的配置文件,可以简单的理解为@babel/coretransform函数的选项支持使用配置文件配置。

更多的配置详细使用等请看官网。

<a name="sRLPS"></a>

其他官方工具

babel还提供了一些其他工具,用于扩展babel生态链:

  • @babel/standalone: 支持浏览器上运行的babel版本,用于一些在线编辑网站,如JS Bin
  • @babel/code-frame: 代码窗口,用于输出类似这种:
  1 | class Foo {
> 2 |   constructor()
    |                ^
  3 | }
  • @babel/template: babel插件开发工具,支持根据代码字符串创建ast节点。因为ast节点携带信息较多,且结构较深,在手动创建复杂的代码节点时十分不便。使用官方提供的这个工具,可以快速创建一整段代码节点,并且还支持占位符:
const buildRequire = template(`
  var IMPORT_NAME = require(SOURCE);
`);

const ast = buildRequire({
  IMPORT_NAME: t.identifier("myModule"),
  SOURCE: t.stringLiteral("my-module"),
});

//  ||   ||   ||
// \\// \\// \\//

const myModule = require("my-module");
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容

  • 了解 Babel 各个模块 本文所研究的是 babel 6 版本。babel 6 是 2015年10月30号 发布...
    shianqi阅读 4,554评论 0 7
  • 引言 babel是一个非常强大的工具,作用远不止我们平时的ES6 -> ES5语法转换这么单一。在前端进阶的道路上...
    AlienZHOU阅读 2,856评论 0 5
  • 前端工程化之前 前端工程化之前,我们编写代码 html js css . 因为浏览器只能读懂这几个代码因为单独的h...
    川九阅读 532评论 0 0
  • Babel是前端很常用的转码器,更准确地说是转译器,是从源码到源码的转换编译器,例如可以将我们按照ES6标准写的代...
    拉面头_7c92阅读 1,194评论 4 2
  • Babel 是一个编译器,和其他编译器一样,编译过程分为三个阶段,分别是解析(parsing)、转换(transf...
    暖A暖阅读 117评论 0 0