配置Babel

发布时间 2023-09-08 11:55:27作者: zc-lee

配置Babel

参考资料

https://mp.weixin.qq.com/s/UcLHGzZdPoS8B14X7RhDEA

问题

我们在使用各种打包工具,需要配置Babel的时候,相信大家一开始都是直接在网上复制粘贴一段配置过来,然后能跑通就万事大吉了吧?因此,我们有时会遇到打包部署后,手机运行出现白屏问题;或者是,打包后代码包过大,加载缓慢等等问题。

其实这一切,大部分原因是因为我们对Babel各项配置没有一个系统的理解,所以即使从网上复制粘贴了配置,出现问题了,不知道怎么去分析出现的问题。

准备

如果你已经对Babel已经有一个大概的了解了,那阅读这篇文章,会让你对配置有一个更系统的了解;如果你才刚接触Babel,或者对Babel处于懵懵懂懂的状态,那我强烈建议你先阅读这篇文章——想弄懂Babel?你必须得先弄清楚这几个包[1],我们学习配置这块会更容易理解一些。

备注

  • 当前@babel/core最新版本是:7.20.12
  • 当前@babel/preset-env最新版本是:7.20.2

再谈core-js

我们知道:core-js是一种polyfill,它提供了旧版本浏览器缺失的所有的ES6+ API的方法与实现。

在这里,以及下文,我们把通过引入core-js的某个模块,来实现旧版本浏览器不支持的某个ES6+ API的过程,叫做垫平。微信搜索公众号:架构师指南,回复:架构师 领取资料 。

我们看看core-js这个包里面的主要一些模块:

  • es:里面只包含有稳定的ES功能。
  • proposals:里面包含所有stage阶段的API
  • stable:它里面包含了,只有稳定的ES功能跟网络标准

所以,我们可以这么使用:

  • 当我们只需要垫平某个稳定的ES6+ API,我们可以用es这个文件夹里的polyfill来垫平 (import X from 'es/xx')
  • 当我们需要用到提案阶段的API时,我就用proposals这个文件夹里的polyfill来垫平(import X from 'proposals/xx')
  • 当我们想垫平所有稳定版本的ES6+ API,可以导入用stable文件夹(import 'core-js/stable')
  • 当我们想垫平所有的ES6+ API(包括提案阶段),可以直接import 'core-js'

具体的介绍可以看看参考文章。

参考文章:core-js

https://github.com/zloirock/core-js#commonjs-api

再谈@bable/preset-env

我们知道:

  • Babel大体由两个功能组成:
    编译ES6+最新语法(let、class、() => {}等)
    实现旧版本浏览器不支持的ES6+的API(Promise、Symbol、Array.prototype.includes等)
  • @babel/preset-env有以下两个功能:
    它只编译ES6+语法
    它并不提供polyfill,但是可以通过配置我们代码运行的目标环境,从而控制polyfill的导入跟语法编译,使ES6+的新特性可以在我们想要的目标环境中顺利运行
  • @babel/plugin-transform-runtime也有以下两个功能:
    @babel/runtime跟@babel/plugin-transform-runtime两者配合,可以减少打包体积
    也有一个配置功能,用来处理polyfill如何垫平
    *如果我们想要在旧浏览器用到ES6+ API时,我们应该安装3版本的core-js(或者后续更高版本的);

那我们可以很清楚的知道:

  • 实现Babel第一个功能:我们用@babel/preset-env就可以了
  • 实现Babel第二个功能:我们就需要用core-js这个包来提供polyfill,并与@babel/preset-env或者@babel/plugin-transform-runtime的配置功能相互配合使用
    我们先来看看@babel/preset-env的配置项有哪些:

// babel.config.js
const presets = [
    [
        '@babel/preset-env',
        {
            modules,
            targets,
            corejs,
            useBuiltIns,
            
            spec,
            loose,
            debug,
            bugfixes,
            include,
            exclude,
            forceAllTransforms,
            configPath,
            ignoreBrowserslistConfig,
            browserslistEnv,
            shippedProposals
        }
    ]
];
module.exports = {presets};

我们可以看到配置项还是蛮多的(有一些配置项,后期可能会废弃),但是,其实我们平时项目中主要用到前四个配置,所以在这里我们重点来看看前四个配置(能不学的尽量不学,太累了)。

参考文章:@babel/preset-env

https://babeljs.io/docs/en/babel-preset-env

modules

功能:启用ES模块语法向另一种模块类型的转换

  • 默认值:auto
  • 可取的值:"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
  • 当我们设置成false的时候,Babel编译产生的一些辅助函数的引入方式会变成ES6的模式引入(import A from 'B')。

我们把 use-transform-runtime[2] 这个案例Babel配置改成以下配置,感受一下modules这个配置的功能。

// babel.config.js

const plugins = [
    '@babel/plugin-transform-runtime'
]

const presets = [
    [
        '@babel/preset-env',
        {
            modules: false
        }
    ]
];

module.exports = {plugins, presets};

在没设置modules配置项时,编译后的文件是:

我们会发现辅助函数都是以require的方式引入的;

在设置了modules配置项后,编译后的文件是:

我们会发现辅助函数变成了我们熟悉的ES6模块方式import引入。

这样有一个好处,就是我们用一些像Webpack打包工具时,可以对代码静态分析,很好地tree shaking减少代码体积,所以我们配置Babel的时候建议设置modules: false

参考文章:[modules]

https://babeljs.io/docs/en/babel-preset-env#modules

targets

作用

它的用法与 browserslist[3] 一致。它可以用来设置我们的代码需要兼容的目标环境,因此它:

  • 可以有效地减少ES6+的语法编译
  • 可以有效控制polyfill导入多少

注意

第一点

如果我们没有设置这个配置项时,它会去我们的Babel配置文件找顶层的targets;如果顶层没有设置targets,则会去我们的package.json里的browserslist或者根目录找.browserslistrc;如果还没有则默认值为{}。查找过程大致如下,序号代表查找顺序:

// Babel配置文件
{
    targets: 'ie 10', // 2. 如果presets里面没有设置targets,会来这里查找
    presets: [
        [
            '@babel/preset-env',
            {
                targets: 'ie 9' // 1. 先在这里查找,没的话去顶层targets查找
            }
        ]
    ]
}

// package.json
{
    ...,
    browserslist: [
        'ie 11' // 3. 如果顶层targest里面没有设置,会来这里查找;如果这里也没设置,则为默认值{}
    ]
}

第二点

如果我们没有设置这个配置项时,Babel会假设我们要兼容的目标环境是最旧的浏览器,所以会将所有的ES6+语法代码转化为ES5。所以我们配置Babel的时候,要设置targets以减少输出代码大小。

针对这点,我们用这个案例 preset-env-targets-config[4] 来感受一下:

我们会发现ES6+的写法全部被转成了ES5,还加入了一些辅助函数(白色框)。

ok,我们设置targets: 'chrome 80'。这表示,我们的代码是要在chrome 80上运行的,再看看打包后的结果:

我们会发现编译出来的代码,跟我们入口文件写的代码基本没差。因为chrome 80已经实现了入口文件代码的写法了。所以,如果我们的代码不需要在一些比较低端的浏览器跑的话,设置targets就十分有必要。

参考文章:[targets]

https://babeljs.io/docs/en/options#targets

corejs

当useBuiltIns不为false的时候,需要设置这个配置项

配置

它有两种配置方式:

  1. 直接设置core-js版本号
...
{
useBuiltIns: 'usage',
corejs: '3.27.2'
}
...
  1. 配置corejs
...
{
useBuiltIns: 'usage',
corejs: {
    version: '3.27.2',
    // 是否编译提案阶段ES6+ API
    proposals: false
},
}
...

注意

  1. 当我们的useBuiltIns不为false的时候,需要设置corejs这个配置项
  2. 2版本的core-js已经不建议使用了;我们用当然要用最新的,目前最新的版本是core-js@3.27.2
  3. 我们安装的core-js要尽量保持最新,因为越新的包,包含的polyfill才会越多
  4. 我们设置corejs的版本号时,不要直接指定2或者3,它会被解析为2.0或者3.0。所以,我们应该带上子版本号(3.27.2),这样才会有最新的polyfill
  5. core-js默认用稳定版的polyfill来垫平,但如果有时我们想用还处在提案阶段的API怎么办?
    如果我们配置的是useBuiltIns: entry,我们得手动引入core-js提案的polyfill来垫平。提案的polyfill放在core-js/proposals文件夹中(import 'core-js/proposals/array-last')
    如果我们配置的是useBuiltIns: 'usage',则我们用上面说的corejs模块里面提到的第二种配置方式,把proposals设为true就可以了
    它的作用会结合下面的useBuiltIns一起讲。

参考文章:[targets]

https://babeljs.io/docs/en/babel-preset-env#targets

useBuiltIns

上面我们提到了,我们把通过引入core-js的某个模块,来实现旧版本浏览器不支持的某个ES6+ API的过程,叫做垫平。

这个配置就是用来设置我们core-js的垫平方式的。它有下面三个值:

为了更好的理解,我们用Promise这个ES6+ API作为下面的例子。我们都知道:

IE 11并不支持Promise
Promise这个对象其实还有很多方法,例如Promise.any、Promise.all、Promise.finally等。
我们用这个案例 preset-env-useBuiltIns-config[5] 来讲解

entry

我们可以这么理解,entry中文是“进入”的意思,这个值说明我们的polyfill应该是需要从某个入口引入来垫平。

表现
我们把配置设置为:useBuiltIns: 'entry'

我们先来看看这个配置在IE 11的表现形式,我们设置targets: 'ie 11':

分析

  • 在IE 11的表现

我们import 'core-js/es/promise'(相当于import某块polyfill来垫平),由于我们的IE 11不支持Promise,所以useBuiltIns: 'entry'配置把我们所有不支持的Promise方法都垫平了(打印的window.Promise.any有值)。

  • 在chrome 80表现

我们import 'core-js/es/promise'(相当于import某块polyfill来垫平), 因为在chrome 80中,Promise大部分方法已经实现,只有Promise.any没有实现,所以此时只垫平了promise.any方法。