Babel”新的配置方法“,你知道吗?

前言

在上一篇文章——Babel配置不要再“复制粘贴”了,带你自己配一个Babel 中,我们知道:

  • 如果我们开发的项目是应用程序,或者大型的项目,我们可以利用@babel/preset-env的配置功能,对ES6+ API进行全局垫平。所以我们一般这么配置:

    // babel.config.js
    const presets = [
        [
            '@babel/preset-env',
            {
                modules: false,
                useBuiltIns: 'entry', // 或者 useBuiltIns: 'usage'
                corejs: { version: '3.27.2', proposals: true }
            }
        ]
    ];
    
    const plugins = [
        '@babel/plugin-transform-runtime'
    ]
    module.exports = {plugins, presets};
  • 如果我们是想开发一个第三方库,我们可以利用@babel/plugin-transform-runtime的配置功能,对ES6+ API进行局部垫平,而不影响全局的环境。所以我们一般这么配置:

    // babel.confing.js
    const presets = [
        [
            '@babel/preset-env',
            { modules: false }
        ]
    ];
    const plugins = [
        [
            '@babel/plugin-transform-runtime',
            {
                corejs: {
                    version: 3,
                    proposals: true
                }
            }
        ]
    ];
    module.exports = {plugins, presets};

以上是我们平时关于Babel见得最多的配置方法。但是这么配:

  • 有时我们要关注@babel/preset-env配置,有时又要关注@babel/plugin-transform-runtime配置,并且配置项那么多,这种配置方法其实还是挺繁琐
  • Babel内容知识点本来就十分多,如果我们本身对Babel就“懵懵懂懂”的话,这无疑会很容易加深我们对Babel的疑惑,让我们觉得更懵圈。

但其实还有一种新的Babel配置方法,它既能实现以上的配置,配置起来更加简洁,并且功能比上面的配置还要强大,同时它也是Babel成员更推荐的配法

学习是个循序渐进的过程,所以这篇文章接下来会带大家:

  1. 大致了解这种新的配置方法一些背景故事,为什么诞生
  2. 了解这种新的配置方法如何配置
  3. 答疑以前我们学习关于Babel配置的疑问
  4. 目前新配置方法存在的问题

PS:

  • 为了大家阅读方便,每个模块相对独立,大家可以挑选自己喜欢的模块阅读
  • 想直接看怎么用的朋友,可以直接跳到使用模块

特别说明

本着写文章注重质量跟严谨性的态度,也为了大家可以更放心的使用这个新的配置方法,因此为了这个新的配置方法,我也阅读了大量关于Babel的文章,也在github上提了一些issuesBabel的开发成员求证学习。

Babel的开发成员也十分友好,也回应了我说:

  • 新的配置方法文档由两部分组成
  • 目前这个新的配置方法是他们更推荐的配法,完全可以用来替换@babel/preset-env@babel/plugin-transform-runtime这两个包的配置功能,达到一样的配置效果
  • 新的配置方法有一个对应的包,这个包在@babel/preset-env内部也稳定使用了

详细的可以看看我在Babel提的issues

Babel”新的配置方法“,你知道吗?_第1张图片

Babel”新的配置方法“,你知道吗?_第2张图片

issues链接:

目前Babel官网并没有对这个新的配置方法有更详细的说明,相信后续这个方法可能也会成为Babel配置的主流。因此,大家学习到这种方法,也算是对Babel的一种拓展,但是最重要的是我们可以利用这个新的方法,更方便的配置我们的Babel

毕竟得到了官方Babel开发成员的回答与肯定,所以这个新的配置方法,大家是可以放心“食用”的

准备

在文章开始之前,如果你对Babel已经有一个系统的了解,也明白Babel怎么配置,那么这篇文章会让你学习到Babel一种更强大的新的配置方法;

但是如果你才刚接触Babel,或者对Babel似懂非懂,还处于迷迷糊糊的状态,那我还是强烈建议你先阅读之前的两篇文章(尤其是第二篇):

备注

  • 我们把前言提到的以前的配置方法统称为:旧的配置方法
  • 我们把这篇文章讲的新的配置方法统称为:新的配置方法
  • 我们把通过引入core-js某个模块(或其他polyfill),来实现旧版本浏览器不支持的某个ES6+ API的过程,叫做垫平
  • 当前@babel/core最新版本是:7.21.8
  • 当前@babel/preset-env最新版本是:7.21.5

历史问题

通过前两篇文章,我们知道:

  • 我们可以把所有这种存放了ES6+ API方法与实现的集合叫做polyfill
  • core-js只是一种polyfill,还有很多其他polyfill。例如: promise-polyfillproxy-polyfill 、es-shims
  • 如果我们是用core-js这个polyfill来垫平的话,应该用core-js的最新版本来垫平,越新的版本,包含的ES6+ API实现就越多

OK,我们接着往下看。

Babel项目中有这样一个包——babel-polyfills

注意:大家看清楚,不是@babel/polyfill这个包,它跟@babel/polyfill是两码事
不了解@babel/polyfill这个包的朋友,之前的文章——想弄懂Babel?你必须得先弄清楚这几个包中有专门的说明

它的出现,有一段故事,主要是为了解决以下问题(更详细的故事,可以看看这里):

  • 以前@babel/preset-env这个包的targets选项,不能与@babel/plugin-transform-runtime这个包共享,@babel/plugin-transform-runtime设置不了targets
  • 上面我们说过,core-js只是一种polyfill,还有很多其他polyfill。所以有可能不是所有人都想用core-js这个polyfill
  • 为了更好的用户体验,旧的配置方法太繁琐

补充

关于问题一,我们再做一些补充:

在旧的配置方法中,如果我们想开发一个第三方库的话,我们是利用@babel/plugin-transform-runtime的配置方法来开发的(因为配置@babel/plugin-transform-runtime,会以局部方式垫平ES6+ API)但是在以前,这会导致库的体积十分庞大。

这是因为之前@babel/plugin-transform-runtime(或者别的插件)不能识别到我们要兼容的目标环境(即targets,或者是packjsonbrowserslist),所以会把我们代码中使用到的ES6+ API都垫平,即使我们目标环境支持这些ES6+ API

但是在我们后续学习Babel配置的时候,我们会发现@babel/plugin-transform-runtime(或者别的插件)是可以识别targets的,不能识别targets这个问题并不能很好的体会到。这就让我们十分疑惑。

如果你在学习Babel过程中对这个问题有过疑惑的话,可以去相关答疑模块查看原因

结构

babel-polyfills 可以看作是一个总的资源包,它存放了三种用于垫平ES6+ API的插件包:

OK,从上面关于三个包的描述,我们可以清楚的知道它们的作用分别是什么。

我们重点关注 babel-plugin-polyfill-corejs3 这个包,因为:

  • Babel推荐的polyfillcore-js,目前core-js最新版本已经是3+的版本了
  • 我自己也试了一下用es-shims来垫平ES6+ API,然而并没有core-js那么方便(大家也可以自己试试)
  • 通过babel-plugin-polyfill-corejs3这个包的名字,以此类推,后续如果core-js升级到4+版本的话,应该会再出babel-plugin-polyfill-corejs4这个包等
  • babel-plugin-polyfill-corejs3这个包也已经在@babel/preset-env内部使用了(相关链接
  • 官方所说的新的配置方法,也是基于这个包

回顾

在讲解如何使用新配置方法前,我们先大致回顾分析一下旧的配置方法是怎么配置,方便后续我们更好的理解如何使用。

在这里只是大概说明,更详细的分析,建议大家去阅读上一篇文章——Babel配置不要再“复制粘贴”了,带你自己配一个Babel

笼统的来说,平时我们做开发,不是开发大型项目、应用程序,就是开发第三方库。
而这些都是需要配置Babel来垫平我们不支持ES6 API的目标环境的。所以在这里,我们可以笼统的把垫平方式看成两大方式:

  • 全局方式垫平 —— 适用于大型项目或者应用程序
  • 局部方式垫平 —— 适用于开发第三方库

全局方式垫平

全局的方式垫平ES6+ API,则都是利用@babel/preset-env的配置功能,它有两种配法(配置代码可见前言模块):

  1. useBuiltIns: 'entry'
  2. useBuiltIns: 'usage'

useBuiltIns: 'entry'

这种方式比较简单粗暴。它需要我们在入口文件手动import所有或者某块polyfill,这样Babel会引入我们目标环境(targets)不支持的polyfill模块。

这种方式可以避免一些奇奇怪怪的问题,但某种程度来说,也会增大我们打包后的体积。

useBuiltIns: 'usage'

这种方式则不需要我们手动引入polyfillBabel会根据我们当前代码中用到的ES6+ API,并判断当前的targets支不支持我们用到的这个ES6+ API,不支持的话则自动导入这个ES6+ API对应的polyfill

这种方式虽然比较方便,但是它是根据我们当前代码中用到的ES6+ API来做判断的。所以如果我们使用的第三方库中,有用到我们当前targets不支持的ES6+ API,但是我们自己的代码又没有用到这个API的话,那就会出问题了。

具体问题可以看看这个例子 use-third-party-library-problem

局部方式垫平

如果我们开发的是一个第三方库,我们当然要把我们的代码与使用者的代码做隔离,所以我们是需要以局部方式来垫平ES6+ API的。

而以局部的方式垫平ES6+ API,则是利用@babel/plugin-transform-runtime的配置功能(配置代码见前言模块)。

它会判断我们想要兼容的目标环境(很久以前判断不了),并根据我们代码中使用到的ES6+ API,最后再将polyfill局部变量的方式进行垫平。

使用

上面我们的大概说了:

  • 新的配置方法诞生的一些历史
  • 新的配置方法是基于 babel-plugin-polyfill-corejs3 这个插件包
  • 回顾了一下旧的配置方法

我们先来看看新的配置方法是怎么配置的,先有个大概印象:

const presets = [
    '@babel/preset-env',
]
const plugins = [
    [
        'babel-plugin-polyfill-corejs3',
        {
            method: "entry-global", // usage-global || usage-pure
            version: '3.20.2',
            proposals: true
        }
    ],
    '@babel/plugin-transform-runtime',
];

我们可以跟前言模块旧的配置方法对比一下,这简直简便的不能再简便

OK,接下来我们具体看看到底怎么配置。

配置项

在提了 issues 询问了关于文档的配置项,并得到Babel贡献者的回答后,可以知道 babel-plugin-polyfill-corejs3 这个包有以下配置项:

  • method(重点)
  • version
  • proposals
  • targets
  • ignoreBrowserslistConfig
  • configPath
  • debug
  • include
  • exclude
  • shouldInjectPolyfill
  • absoluteImports
  • missingDependencies

大家不要被那么多配置项吓到,平常用到的就前三个targets也会讲解一下),其他的在我们需要用到的时候在学(太累了,能不学的尽量不学)。

method

这个配置项就是用来控制我们垫平ES6+ API的方式。

它一共有三个值:

  • entry-global
  • usage-global
  • usage-pure

我们可以这么拆解,这样更方便我们记忆使用:

  • 我们可以把-前面当做第一部分,它指的是控制polyfill引入的方式。是手动在入口引入,还是根据我们代码中用到的ES6+ API自动引入
  • 我们可以把-前面当做第二部分,它指的是polyfill是以全局还是局部的方式垫平
  • 我们也会发现它们的值,跟旧的配置方法有所对应

entry-global

分析

我们可以把值拆成两部分来看:entryglobal。从这两个十分语义化的词我们可以知道:

  • entry入口。它指的是我们要在入口引入我们的polyfill
  • global全局。指的是,polyfill全局的方式垫平

我们以ie 11为目标浏览器,用 entry-global 这个例子,来感受一下它跟旧的配置方法有没有什么区别:

Babel”新的配置方法“,你知道吗?_第3张图片

根据上面的结果,我们会发现:

  • 使用了新旧两种配置方法,发现编译后,注入的polyfill都是234行
  • 编译后的文件都是一模一样的。
总结

method: 'entry-global'相当于旧的配置方法中的useBuiltIns: 'entry'

注意

文档version配置项,只有在使用usage-global或者usage-pure时才有用。

但是我试了一下,entry-global也是可以使用version,所以我有提 issues,目前没有答复

usage-global

分析

我们可以把值拆成两部分来看:entryglobal。从这两个十分语义化的词我们可以知道:

  • usage用法。它指的是我们代码中用到ES6+ API
  • global全局。指的是polyfill全局的方式垫平

我们以ie 11为目标浏览器,使用两个ES6+ API

  • array-grouping:它是个可以让数组分组更方便的API,目前它处于stage-3阶段
  • Promise:我们最熟悉的异步API

并且用 usage-global 这个例子来感受一下它跟旧的配置方法有没有什么区别:

Babel”新的配置方法“,你知道吗?_第4张图片

根据上面的结果,我们会发现:

  • ie 11中,只垫平了我们用到的那两个ES6+ API
  • 编译后的文件都是一模一样的
总结

method: 'usage-global'相当于旧配置方法中的useBuiltIns: 'usage'

usage-pure

分析

我们可以把值拆成两部分来看:usagepure。从这两个十分语义化的词我们可以知道:

  • usage用法。它指的是我们代码中用到ES6+ API
  • pure纯净。指的是polyfill局部的方式垫平,并不会污染全局,因此它是纯净的

我们以ie 11为目标浏览器,还是使用上面两个ES6+ API

  • array-grouping:它是个可以让数组分组更方便的API,目前它处于stage-3阶段
  • Promise:我们最熟悉的异步API

并且用 usage-pure 这个例子来感受一下它跟旧的配置方法有没有什么区别:

Babel”新的配置方法“,你知道吗?_第5张图片

根据上面的结果,我们会发现:

旧的配置方法,是无法垫平一些提案阶段的ES6+ API。因为旧的配置设置的version只能是33代表的是[email protected]的版本,那它肯定不能垫平一些比较新的提案

总结
  • method: 'usage-pure'相当于旧配置方法中,配置@babel/plugin-transform-runtime的方式
  • 新的配置方法比旧的配置方法更简便,更强
注意

我们需要npm i core-js-pure -S,因为:

  • 全局变量方式垫平是基于core-js这个包
  • 局部变量方式垫平是基于core-js-pure这个包

文档没有说,不安装相关依赖的话,会报错

targets

它指的是我们垫平ES6+ API目标环境

这个配置项设计的初衷是为了解决历史问题模块提到的第一个问题:

  • 利用@babel/plugin-transform-runtime配置开发第三方库时,以前@babel/plugin-transform-runtime是不能识别targets的,这会导致库的体积很大。

所以有了targets这个配置项,会帮我们大大的减少体积。

我们进行以下步骤:

  • 安装< 7.13.0版本的Babel@babel/[email protected]
  • 使用PromiseArray.includes两个ES6+ API

然后用 target-configuration 这个案例,感受一下以前targets这个配置项在以前是怎么发挥作用的。

我们先看第一个结果(不配置targets,但设置了browserslist: ["chrome 100"]):

Babel”新的配置方法“,你知道吗?_第6张图片

按照最新版本的Babel来说,设置了browserslist: ["chrome 100"],我们的代码只会垫平chrome 100不支持的ES6+ API
但是现在我们安装的是比较旧的版本(7.12.0),根据上图我们会发现,即使我们设置的目标浏览器(chrome 100)是支持这些API的,但Babel依旧垫平了这些API,所以我们的库会变得十分大,这是历史问题模块问题一很好的体现。

我们再来看第二个结果(配置targets):

Babel”新的配置方法“,你知道吗?_第7张图片

我们会发现,因为我们新的配置方法设置了targets,所以此时Babel不会垫平targets支持的API,这大大节约了我们库的体积。这就很好的解决了历史问题模块的问题一。

Babel >= 7.13.0的版本以后,在Babel配置文件新增了一个顶级的targets,并且Babel也支持识别browserslist
所以在新的配置方法里targets这个配置项就不怎么用了。

version

它指的是我们提供polyfill的集合包(像core-js,里面存放了很多polyfll集合)的版本。

因为我们是使用babel-plugin-polyfill-corejs3这个包

  • 全局方式垫平的话,是用core-js这个包来垫平,所以version指的是core-js版本
  • 局部方式垫平的话,是用core-js-pure这个包来垫平的,所以version指的就是core-js-pure的版本

我们version当然是要越高越好,这样包含的polyfill才会更多

参考文章:babel-plugin-polyfill-corejs3

proposals

它指的是,我们是否开启编译处于提案阶段的ES6+ API,那我们当然是要开启的,这个值只有在method: 'usage-global' 或者 method: 'usage-pure'时有用

相关答疑

历史问题模块中,我们有说到:

  • 在后续学习的过程中,我们发现@babel/plugin-transform-runtime(或者别的插件)是可以识别targets的,但有些文章说不能识别,这加深了我们对Babel的疑惑。

其实这一切的原因还是因为:我们后续学习Babel的时候,Babel已经进行了很多更新,我们当前学习Babel时候的版本,跟以前那会的Babel版本已经不一样了,因此问题不能很好的体现。

所以以后我们学习Babel一定要注意好版本号,这就是为什么关于Babel的文章,我都会写上Babel版本备注的原因。

OK,我们接着往下看。

Babel发布7.13.0版本时候有一些记录说到:

  • 增加了顶层的targets
  • 支持识别browserslist里面环境

这就是为什么后续我们学习Babel,有些文章说@babel/plugin-transform-runtime不能识别targets,但是我们照着例子敲,结果却截然相反的原因。因为我们学习的时候,Babel版本可能已经>= 7.13.0,并且可能配置了browserslist或者targets,这时@babel/plugin-transform-runtime已经可以识别targets

为了更好的感受这个问题,我们用 transform-runtime-targets-problem 这个案例来感受一下:

  • 我们安装一个< 7.13.0版本(7.12.0
  • 配置browerslistchrome 100
  • 使用Arrayincludes方法(chrome 100已经支持)
  • 使用Promisechrome 100已经支持)

我们来看看结果:

Babel”新的配置方法“,你知道吗?_第8张图片

chrome 100已经支持了ArrayincludesPromise,但依旧被垫平了。

因为我们安装的Babel < 7.13.0,此时还没实现插件能识别targets

根据这个案例,相信大家应该对Babel的疑惑又减少了一些了。

新配置方法目前存在的问题

我们在回顾模块有说到,使用局部变量的方式垫平ES6+ API,它会根据我们代码中使用到的ES6+ API,最后再以局部变量的方式进行垫平。

Babel在进行编译的时候,是会注入很多辅助函数的,那么会有这样一种情况:

  • 我们的代码中没有使用到Promise,但是辅助函数中用到了Promise,那这时我们的代码是不会垫平Promise的,因为我们自己的代码中没有用到Promise,那这是不是会导致代码报错呢?

这是个很现实的问题,我们用 usage-pure-problem 这个例子来看看新旧配置方法,对于这块问题有没有处理。

旧的配置方法

我们先看看旧的配置方法的表现:

Babel”新的配置方法“,你知道吗?_第9张图片

我们来分析一下这个编译过程:

  • 我们的源码里面只使用了async
  • Babel@babel/runtime-corejs3这个包里引入了辅助函数
  • ie 11正常运行

新的配置方法

我们先看看新的配置方法的表现:

Babel”新的配置方法“,你知道吗?_第10张图片

我们来分析一下这个编译过程:

  • 我们的源码里面只使用了async
  • Babel@babel/runtime这个包里引入了辅助函数
  • ie 11报错

分析

我们来分析一下,为什么新的配置方法在ie 11会报Promise的错。

我们自己的源码中没有用到Promise,所以这个Promise肯定来自辅助函数。我们可以看到新旧配置方法,引入的辅助函数是来自不同的包:

  • 旧的配置方法辅助函数来自:@babel/runtime-corejs3
  • 新的配置方法辅助函数来自:@babel/runtime

经过一番查询,我们发现:

  • 旧的配置方法,Promise来自辅助函数的这个包:@babel/runtime-corejs3/helpers/asyncToGenerator
  • 新的配置方法,Promise来自辅助函数的这个包:@babel/runtime/helpers/asyncToGenerator

我们对比一下这两个包有什么不一样的:

Babel”新的配置方法“,你知道吗?_第11张图片

通过分析对比,我们可以很清楚的知道:

旧的配置方法,引入的辅助函数都是局部变量方式的存在的,所以我们不需要担心我们辅助函数里的ES6+ API没有被垫平;而新的配置方法则不是。

总结

所以,按目前情况来说,如果我们想开发第三方库,并且想用新的配置方法来代替旧的配置方法:

  • 我们要注意,这可能会导致在旧的浏览器运行不起来的情况。因为在新的配置方法中,辅助函数可能存在ES6+ API,它不会被垫平。
  • 新的配置方法目前来说,适用于我们要兼容的一些比较新的目标环境

以上问题当然也有别的开发者发现了,目前这个问题已经解决了,估计后续的版本会修复。在修复以后,开发第三方库,我们就可以完完全全的用新的配置方法代替旧的配置方法了。

参考文章:

总结

OK,最后我们总结一下新的配置方法:

  • 如果我们开发的项目是应用程序,或者大型的项目,那我们可以这么配置:

    // Babel配置
    const presets = [
        [
            '@babel/preset-env',
            { modules: false }
        ]
    ];
    const plugins = [
        '@babel/plugin-transform-runtime',
        [
            'babel-plugin-polyfill-corejs3',
            {
                method: "entry-global", // 或者method: "usage-global"
                version: '3.20.2',
                proposals: true
            }
        ]
    ];
    module.exports = {plugins, presets};
    
    // package.json
    {
        ...,
        // 设置目标环境
        "browserslist": [
            "ie 11"
        ]
    }
    
    // 入口文件
    // ---- useBuiltIns: 'entry'时,需要引入以下----
    // 垫平全部ES6+稳定版API
    import 'core-js/stable'; 
    // ---- 或者 -----
    // 垫平所有ES6+ API,包括提案阶段
    import 'core-js';
  • 如果我们是想开发一个第三方库,我们可以这么配置:

    // Babel配置
    const presets = [
        [
            '@babel/preset-env',
            { modules: false }
        ]
    ];
    const plugins = [
        '@babel/plugin-transform-runtime',
        [
            'babel-plugin-polyfill-corejs3',
            {
                method: "usage-pure",
                version: '3.20.2',
                proposals: true
            }
        ]
    ];
    module.exports = {plugins, presets};
    
    // package.json
    {
        ...,
        // 设置目标环境
        "browserslist": [
            "ie 11"
        ]
    }
    
    // 入口文件
    const Method = {
        wait(delay) {
            return new Promise(resolve => setTimeout(() => resolve(), delay);
        }
    }
    ...

我们可以对比一下旧的配置方法,新的配置方法更加的简洁,更加的强大。我们相当于:

  • 只利用@babel/preset-env进行ES6+语法编译
  • 利用babel-plugin-polyfill-corejs3来垫平ES6+ API
  • 我们根本不需要在@babel/preset-env@babel/plugin-transform-runtime这两个包切来切去;我们只需要关注babel-plugin-polyfill-corejs3如何配置就可以了

另外:

  • 如果想用新的配置方法用来开发第三方库,需要注意一些旧版本的浏览器可能会报错,因为它不会垫平辅助函数里面的ES6+ API。所以想开发第三方库,新的配置方法更适合用于一些比较新的浏览器
  • 如果想用新的配置方法用来开发应用项目或大型项目,用全局方式垫平ES6+ API,那么新的配置方法完全可以直接代替旧的配置方法

最后

文章涉及到的例子,已经上传 Github,觉得有帮助的话,欢迎Star或者Fork学习(写文章真的很辛苦)。

如果读完这篇文章的你,觉得真的有帮助到,欢迎点赞收藏;如果有异同点,欢迎在评论区讨论

你可能感兴趣的:(Babel”新的配置方法“,你知道吗?)