最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 回顾 babel 6和7,来预测下 babel 8

    正文概述 掘金(zxg_神说要有光)   2021-04-29   623

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

    babel 来自巴别塔的典故:

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

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

    babel 的编译流程

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

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

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

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

    babel 的编译流程和目的从没有变过,但是完成这个目的的方式却变化很大,我们来回顾一下 babel 6babel 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和7,来预测下 babel 8

    并集的结果就是所有支持的特性。

    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和7,来预测下 babel 8

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

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

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

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

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

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

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

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

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

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

    配置方式比如:

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

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

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

    不再手动引入 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 了。

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

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

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

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

    看起来,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 的环境支持情况:

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

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

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

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

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

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

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

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

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

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

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

    于是我就去问了下作者,提了个 feature request,作者说可以用最新的 @babel/polyfills 解决了这个问题.

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

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

    这个包估计在 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,也就是说你可以指定 corejs2、corejs3、es-shims 等 polyfill,还可以自定义 polyfil,也就是你可以使用自己的 polyfill。

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

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

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

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

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

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

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

    其实这三种方式 babel 7 也支持,但是现在不再需要插件了,而且还支持了 polyfill provider 的配置,所以到了 babel 8 的阶段, @babel/preset-env 才是功能完备的。

    那么插件如果想用 targets 该怎么用呢?

    因为我最近在写 《babel 插件通关秘籍》 的小册,所以比较关注对插件的影响,我就问了一下 babel 维护者是不是需要在 @babel/core 调用插件的时候注入到 api 中,让插件可以拿到 targets。

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

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

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

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

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

    分别对应我们前面聊到的需要 先通过 query 确定目标环境,然后对目标环境做过滤,之后判断某个插件是否需要的 3个阶段。

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

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

    这样,不管是内置 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!


    起源地下载网 » 回顾 babel 6和7,来预测下 babel 8

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元