回顾 babel 6和7,来预测下 babel 8

babel 最开始叫 6to5,顾名思义,功能是 es6 转 es5。咱们知道,es 版本一年一个,有了 es7(es2016)、es8(es2017)等等。显然,6to5 的名字已经不合适了,因此 6to5 更名为了 babel。javascript

babel 来自巴别塔的典故:java

当时人类联合起来兴建但愿能通往天堂的高塔,为了阻止人类的计划,上帝让人类说不一样的语言,令人类相互之间不能沟通,计划所以失败,人类自此各散东西。此事件,为世上出现不一样语言和种族提供解释。这座塔就是巴别塔。node

这个巴别塔的典故很符合 babel 的转译器的定位。react

babel 的编译流程

babel 从最初到如今一直的目的都很明确,就是把源码中的新语法和 api 转成目标浏览器支持的。它采用了微内核的架构,整个流程比较精简,全部的转换功能都是经过插件来完成的。git

babel 的编译流程就是 parse、transform、generate 3步, parse 是把源码转成 AST,transform 是对 AST 的转换,generate 是把 AST 转成目标代码,而且生成 sourcemap。es6

在 transform 阶段,会应用各类内置的插件来完成 AST 的转换。内置插件作的转换包括两部分,一是把不支持的语法转成目标环境支持的语法来实现相同功能,二是不支持的 api 自动引入对应的 polyfill。github

babel 的编译流程和目的从没有变过,可是完成这个目的的方式却变化很大,咱们来回顾一下 babel 6babel 7 都是怎么设计的,babel 8 又会怎么作,或许能帮你真正理解 babel。数据库

babel 6

es 的标准一年一个版本,也就意味着 babel 插件要实时的去跟进,一年实现一系列插件。json

新的语法和 api 进入 es 标准也是有个过程的,这个过程分为这几个阶段:api

  • 阶段 0 - Strawman: 只是一个想法,可能用 babel plugin 实现
  • 阶段 1 - Proposal: 值得继续的建议
  • 阶段 2 - Draft: 创建 spec
  • 阶段 3 - Candidate: 完成 spec 而且在浏览器实现
  • 阶段 4 - Finished: 会加入到下一年的 es20xx spec

有这么多特性要 babel 去转换,每一个特性用一个 babel 插件来作。可是特性多啊,也就是说插件多,总不能让用户本身去配一个个插件吧,因此 babel 6 引入了 preset 的概念,就是 plugin 的集合。

若是咱们想用 es6 语法就用 babel-preset-es2015,es7 就在引入 babel-preset-es2016 等等。若是是想用还没加入标准的特性,则分别用 babel-preset-stage0、babel-preset-stage1 等来引入。这样经过选择不一样的 preset,加上手动引入一些插件,就是全部 babel 会作的转换。

能够把这个过程理解为集合求并集的过程。

并集的结果就是全部支持的特性。

babel 6 就是经过这样的方式来支持各类目标环境不支持的特性转换的配置。

细想一下,这样的方式有没有问题?

这样虽然能达到目的,可是是有问题的,主要有两点:

  • es 的标准每一年都在变,如今的 stage-0 可能很快就 stage-2 了,那 preset 怎么维护,要不要跟着变,用户怎么知道这个 stage-x 都支持什么特性?

  • 只能转成 es5,那目标环境支持一些 es6 特性了,那这些转换和 polyfill 岂不是无用功? 并且还增长了产物的体积。

  • polyfill 手动引入,比较麻烦,有没有更好的方式

这两个问题是 babel 6 的时候一直存在的。因此这种方案算是及格,可是仍是有问题的,咱们给 70 分不过度吧。 (能完成功能就能够给 60 分,多加 10 分是给 babel 6 引入的 preset,确实简化了不少配置)

那怎么解决 babel 6 的问题呢?babel 7 给出了答案。

babel 7

babel 7 改动挺大的,好比全部的包都迁移到了 @babel 的 scope 下,也就是 @babel/xxx,这些咱们无论,只看 babel 7 是怎么解决 babel 6 的问题的,

babel 7 废弃了 preset-20xx 和 preset-stage-x 的 preset 包,而换成了 preset-env,preset-env 默认会支持全部 es 标准的特性,若是没进入标准的,再也不封装成 preset,须要手动指定 plugin-proposal-xxx。

它的集合是这样的:

是否是比起 babel 6 更简单了。

(preset-react 等不是 es 标准语法,也没有啥变化,就不包括在里面了)。

可是 preset 和 plugin proposal 的改变只是解决了以前的 preset 常常变的问题。那么多转换了一些环境支持的特性,这个问题是怎么解决的呢?

答案是 compat-table,它给出了每一个特性在不一样浏览器或者 node 环境中的最低支持版本,babel 基于这个本身维护了一份数据库,在 @babel/compat-data 下。

其中有每一个特性在不一样环境的什么版本支持的数据:

有了这些数据,那么只要用户指定他的目标环境是啥就能够了,这时候能够用 browserslist 的 query 来写,好比 last 1 version, > 1% 这种字符串,babel 会使用 brwoserslist 来把它们转成目标环境具体版本的数据。

有了不一样特性支持的环境的最低版本的数据,有了具体的版本,那么过滤出来的就是目标环境不支持的特性,而后引入它们对应的插件便可。这就是 preset-env 作的事情。

配置方式好比:

{
    "presets": [["@babel/preset-env", { "targets": "> 0.25%, not dead" }]]
}
复制代码

这样就经过 preset-env 解决了转换了目标环境已经支持的特性的问题。其实 polyfill 也能够经过 targets 来过滤。

再也不手动引入 polyfill,那么怎么引入? 固然是用 preset-env 自动引入了。可是也不是默认就会启用这个功能,须要配置。

{
    "presets": [["@babel/preset-env", { 
        "targets": "> 0.25%, not dead",
        "useBuiltIns": "usage",// or "entry" or "false"
        "corejs": 3
    }]]
}
复制代码

配置下 corejs 和 useBuiltIns。

  • corejs 就是 babel 7 所用的 polyfill,须要指定下版本,corejs 3 才支持实例方法(好比 Array.prototype.fill )的 polyfill。

  • useBuiltIns 就是使用 polyfill (corejs)的方式,是在入口处所有引入(entry),仍是每一个文件引入用到的(usage),或者不引入(false)。

配置了这两个 option 就能够自动引入 polyfill 了。

polyfill 默认是全局引入的,有的时候不想污染全局变量就要用 @babel/plugin-transform-runtime 转换下。(这个插件 babel 6 就有了)。

这样就再也不污染全局环境了,而是使用一个惟一的标识符来引入。

看起来,babel 7 好像已经很完美了,能够打 90 多分了?

不是的,babel 7 有 babel 7 的问题。

babel 7 的问题

@babel/plugin-transform-runtime 是不支持配置 targets 的,由于不知道目标环境支持啥,它只能所有作转换。你可能说不是有 preset-env 么?

babel 中插件的应用顺序是:先 plugin 再 preset,plugin 从左到右,preset 从右到左,这样 plugin-transform-runtime 是在 preset-env 前面的。

等 @babel/plugin-transform-runtime 转完了以后,再交给 preset-env 这时候已经作了无用的转换了。

咱们来试验一下:

咱们先看一下 Array.prototype.fill 的环境支持状况:

能够看到在 Chrome 45 及以上支持这个特性,而在 Chrome 44 就不支持了。

咱们先单独试一下 preset-env:

当指定 targets 为 Chrome 44 时,应该自动引入polyfill:

当指定 targets 为 Chrome 45 时,不须要引入polyfill:

结果都符合预期,44 引入,45 不引入。

咱们再来试试 @babel/plugin-transform-runtime:

是否是发现问题了,Chrome 45 不是支持 Array.prototype.fill 方法么,为啥仍是引入了 polyfill。

因而我就去问了下做者,提了个 feature request,做者说能够用最新的 @babel/polyfills 解决了这个问题.

我去看了下,这个包还在试验阶段,确实解决了这个问题。

这个包估计在 babel 8 会内置到 babel。

那么给 babel 7 打个分吧,原本 preset-env 的引入使咱们能更精准的转换代码和引入 polyfill,想给 90 分,可是 plugin-transform-runtime 的问题让我给它减了 10 分,综合给 80 分吧。

babel 8

babel 8 还没出来,可是咱们知道 babel 再怎么更新也是围绕主线来的,也就是对目标环境不支持的特性自动进行精准的转换和 polyfill。每一个版本都是解决了上个版本的问题的,babel 8 的 @babel/polyfills 包就解决了 babel 7 的 @babel/plugin-transform-runtime 的遗留问题,能够经过 targets 来按需精准引入 polyfill 了。

它支持配置一个 polyfill provider,也就是说你能够指定 corejs二、corejs三、es-shims 等 polyfill,还能够自定义 polyfil,也就是你可使用本身的 polyfill。

而后有了 polyfill 源以后,使用 polyfill 的方式也把以前 transform-runtime 作的事情内置了,也就是从以前的 useBuiltIns: entry、 useBuiltIns: usage 的两种,变成了 3 种:

  • entry-global: 这个和以前的 useBuiltIns: entry 对标,就是全局引入 polyfill。

  • usage-entry: 这个和 useBuiltIns: usage 对标,就是具体模块引入用到的 polyfill。

  • usage-pure:这个就是以前须要 transform-runtime 插件作的事情,使用不污染全局变量的 pure 的方式引入具体模块用到的 polyfill.

其实这三种方式 babel 7 也支持,可是如今再也不须要插件了,并且还支持了 polyfill provider 的配置,因此到了 babel 8 的阶段, @babel/preset-env 才是功能完备的。

那么插件若是想用 targets 该怎么用呢?

由于我最近在写 《babel 插件通关秘籍》 的小册,因此比较关注对插件的影响,我就问了一下 babel 维护者是否是须要在 @babel/core 调用插件的时候注入到 api 中,让插件能够拿到 targets。

上午问的,下午我就惊喜的发现 babel 文档补充了 @babel/helper-compilation-targets 的文档。helper 是用于插件之间复用代码的方式,也就是给插件开发用的库。

我看了下,这个库提供了 3 个 api:

  • 根据 query 查询目标环境版本: getTargets
  • 过滤目标环境: filterItems
  • 判断某个插件是否须要:isRequired

分别对应咱们前面聊到的须要 先经过 query 肯定目标环境,而后对目标环境作过滤,以后判断某个插件是否须要的 3个阶段。

插件里面经过 api.targets() 拿到环境的配置,而后经过 isRequired 来肯定某个插件有没有必要用。

这样,不论是内置 plugin 和 preset 的实现方式也好,仍是插件所能用的 api 也好,都完美支持了 targets,到了这个阶段 targets 才算真正融入进了 babel 中。

这个阶段的 babel,我以为已经能够给出 90 分的分数了:

支持按照配置的目标环境按需进行 polyfill 和 transform,支持 polyfill 的切换和自定义,配置方式也足够简单,插件中也能够用 targets,并且提供了方便的 helper 包。

babel 发展规律

babel 8 还在路上,可是咱们已经可以隐约看到他会是什么样子了,其实 babel 从最开始到如今,核心的思路始终没有变过,就像最开始的名字 6to5 同样,就是为了 把目标环境中不支持的语法和 api 进行转换或 polyfill,尽可能的准确、配置尽可能的简单、插件更容易书写能作到更多事情

因此针对这个目标,babel 一路发展而来, 设计出了 preset(babel 6)、preset-env (babel 7)、polyfill provider(babel 8),plugin-transform-runtime (babel 6)等。

插件可以用的 api、helper 等也愈来愈丰富。

babel 一直在发展,可是目标和本质从未变过。咱们去学习一个东西,也要去抓住它的本质来学,因此我写了《babel 插件通关秘籍》 的小册(即将上线),但愿能帮你“通关” babel!

相关文章
相关标签/搜索