回顾babel历程,展望babel8

babel 来自巴别塔的典故:

当时人类联合起来兴建希望能通往天堂的高塔,为了阻止人类的计划,上帝让人类说不同的语言,使人类相互之间不能沟通,计划因此失败,人类自此各散东西。此事件,为世上出现不同语言和种族提供解释。这座塔就是巴别塔。
这个巴别塔的典故很符合 babel 的转译器的定位
在这里插入图片描述

babel 的编译流程

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

在这里插入图片描述

babel 的编译流程就是 parse、transform、generate 3步, parse 是把源码转成 AST,transform 是对 AST 的转换,generate 是把 AST 转成目标代码,并且生成 sourcemap
在 transform 阶段,会应用各种内置的插件来完成 AST 的转换。内置插件做的转换包括两部分,一是把不支持的语法转成目标环境支持的语法来实现相同功能,二是不支持的 api 自动引入对应的 polyfill。

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

babel 6

es 的标准一年一个版本,也就意味着 babel 插件要实时的去跟进,一年实现一系列插件。
新的语法和 api 进入 es 标准也是有个过程的,这个过程分为这几个阶段:

  • 阶段 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。

babel 8

babel 8 还没出来,但是我们知道 babel 再怎么更新也是围绕主线来的,也就是对目标环境不支持的特性自动进行精准的转换和
polyfill。每个版本都是解决了上个版本的问题的,babel 8 的 @babel/polyfills 包就解决了 babel 7 的
@babel/plugin-transform-runtime 的遗留问题,可以通过 targets 来按需精准引入 polyfill 了。
它支持配置一个 polyfill provider,也就是说你可以指定 corejs2、corejs3、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 才是功能完备的。

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 一直在发展,但是目标和本质从未变过。我们去学习一个东西,也要去抓住它的本质来学。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值