babel:core.js和preset-env

发布于 2024-05-03  2972 次阅读


@babel/preset-env

在Babel6时代,这个预设名字是 babel-preset-env,在Babel7之后,改成@babel/preset-env。本节单独讲解@babel/preset-env,不涉及transform-runtime的内容,二者结合使用的内容会在讲解了transform-runtime之后进行。

@babel/preset-env是整个Babel大家族最重要的一个preset。不夸张地说,所有配置项仅需要它自己就可以完成现代JS工程所需要的所有转码要求。

在使用它之前,需要先安装

npm install --save-dev @babel/preset-env

@babel/preset-env是Babel6时代babel-preset-latest的增强版。该预设除了包含所有稳定的转码插件,还可以根据我们设定的目标环境进行针对性转码。

在Babel快速入门一节,我们简单使用过@babel/preset-env的语法转换功能。除了进行语法转换,该预设还可以通过设置参数项进行针对性语法转换以及polyfill的部分引入。

@babel/preset-env的参数项,数量有10多个,但大部分我们要么用不到,要么已经或将要弃用。这里建议大家掌握重点的几个参数项,有的放矢。重点要学习的参数项有targets、useBuiltIns、modules和corejs这四个,能掌握这几个参数项的真正含义,就已经超过绝大部分开发者了。

对于preset,当我们不需要对其设置参数的时候,其写法是只需要把该preset的名字放入presets对于的数组里即可,例如

module.exports = {
    presets: ["@babel/env"],
    plugins: []
  }

注意,@babel/env是@babel/preset-env的简写。

如果需要对某个preset设置参数,该preset就不能以字符串形式直接放在presets的数组项了。而是应该再包裹一层数组,数组第一项是该preset字符串,数组第二项是该preset的参数对象。如果该preset没有参数需要设置,则数组第二项可以是空对象或者不写第二项。以下几种写法是等价的:

 module.exports = {
    presets: ["@babel/env"],
    plugins: []
  }
 module.exports = {
    presets: [["@babel/env", {}]],
    plugins: []
  }
 module.exports = {
    presets: [["@babel/env"]],
    plugins: []
  }

如果你使用过vue或react的官方脚手架cli工具,你一定会在其package.json里看到browserslist项,下面该项配置的一个例子:

 "browserslist": [
    "> 1%",
    "not ie <= 8"
  ]

上面的配置含义是,目标环境是市场份额大于1%的浏览器并且不考虑IE8及以下的IE浏览器。Browserslist叫做目标环境配置表,除了写在package.json里,也可以单独写在工程目录下.browserslistrc文件里。我们用browserslist来指定代码最终要运行在哪些浏览器或node.js环境。Autoprefixer、postcss等就可以根据我们的browserslist,来自动判断是否要增加CSS前缀(例如'-webkit-')。我们的Babel也可以使用browserslist,如果你使用了@babel/preset-env这个预设,此时Babel就会读取browserslist的配置。

如果我们的@babel/preset-env不设置任何参数,Babel就会完全根据browserslist的配置来做语法转换。如果没有browserslist,那么Babel就会把所有ES6的语法转换成ES5版本。

在本教程最初的例子里,我们没有browserslist,并且@babel/preset-env的参数项是空的,ES6箭头函数语法被转换成了ES5的函数定义语法。

转换前

var fn = (num) => num + 2;

转换后

 "use strict";
  var fn = function fn(num) {
    return num + 2;
  };

如果我们在browserslist里指定目标环境是Chrome60,我们再来看一下转换结果

"browserslist": [
    "chrome 60"
  ]

转换后

 "use strict";
  var fn = num => num + 2;

我们发现转换后的代码仍然是箭头函数,因为Chrome60已经实现了箭头函数语法,所以不会转换成ES5的函数定义语法。

现在我们把Chrome60改成Chrome38,再看看转换后的结果

"browserslist": [
    "chrome 38"
  ]

转换后

"use strict";
  var fn = function fn(num) {
    return num + 2;
  };

我们发现转换后的代码是ES5的函数定义语法,因为Chrome38不支持箭头函数语法。

注意,Babel使用browserslist的配置功能依赖于@babel/preset-env,如果Babel没有配置任何预设或插件,那么Babel对转换的代码会不做任何处理,原封不动生成和转换前一样代码。

既然@babel/preset-env可以通过browserslist针对目标环境不支持的语法进行语法转换,那么是否也可以对目标环境不支持的特性API进行部分引用呢?这样我们就不用把完整的polyfill全部引入到最终的文件里,可以大大减少体积。

答案是可以的,但需要对@babel/preset-env的参数项进行设置才可以,这个我们接下来讲。

参数项

targets

该参数项可以取值为字符串、字符串数组或对象,不设置的时候取默认值空对象{}。

该参数项的写法与browserslist是一样的,下面是一个例子

module.exports = {
    presets: [["@babel/env", {
      targets: {
        "chrome": "58",
        "ie": "11"
      }
    }]],
    plugins: []
  }

如果我们对@babel/preset-env的targets参数项进行了设置,那么就不使用browserslist的配置,而是使用targets的配置。如不设置targets,那么就使用browserslist的配置。如果targets不配置,browserslist也没有配置,那么@babel/preset-env就对所有ES6语法转换成ES5的。

正常情况下,我们推荐使用browserslist的配置而很少单独配置@babel/preset-env的targets。

useBuiltIns

useBuiltIns项取值可以是"usage" 、 "entry" 或 false。如果该项不进行设置,则取默认值false。

useBuiltIns这个参数项主要和polyfill的行为有关。在我们没有配置该参数项或是取值为false的时候,polyfill就是我们上节课讲的那样,会全部引入到最终的代码里。

useBuiltIns取值为"entry"或"usage"的时候,会根据配置的目标环境找出需要的polyfill进行部分引入。让我们看看这两个参数值使用上的不同。

useBuiltIns:"entry"

我们在入口文件用import语法引入polyfill(也可以在webpack的entry入口项)。此时的Babel配置文件如下:

module.exports = {
    presets: [["@babel/env", {
      useBuiltIns: "entry"
    }]],
    plugins: []
  }

需要安装的npm包如下:

npm install --save-dev @babel/cli @babel/core @babel/preset-env
npm install --save @babel/polyfill

我们指定目标环境是火狐58,package.json里的browserslist设置如下:

 "browserslist": [
      "firefox 58"
    ]

转换前端的代码如下:

import '@babel/polyfill';
  var promise = Promise.resolve('ok');
  console.log(promise);

使用npx babel a.js -o b.js命令进行转码。

转码后:

"use strict";
  require("core-js/modules/es7.array.flat-map");
  require("core-js/modules/es7.string.trim-left");
  require("core-js/modules/es7.string.trim-right");
  require("core-js/modules/web.timers");
  require("core-js/modules/web.immediate");
  require("core-js/modules/web.dom.iterable");
  var promise = Promise.resolve('ok');
  console.log(promise);

我们可以看到Babel针对火狐58不支持的API特性进行引用,一共引入了6个core-js的API补齐模块。同时也可以看到,因为火狐58已经支持Promise特性,所以没有引入promise相关的API补齐模块。你可以试着修改browserslist里火狐的版本,修改成版本26后,会引入API模块大大增多,有几十个。

useBuiltIns:"usage"

"usage"在Babel7.4之前一直是试验性的,7.4之后的版本稳定。

这种方式不需要我们在入口文件(以及webpack的entry入口项)引入polyfill,Babel发现useBuiltIns的值是"usage"后,会自动进行polyfill的引入。

我们的Babel配置文件如下:

module.exports = {
    presets: [["@babel/env", {
      useBuiltIns: "usage"
    }]],
    plugins: []
  }

需要安装的npm包如下:

npm install --save-dev @babel/cli @babel/core @babel/preset-env
npm install --save @babel/polyfill

我们指定目标环境是火狐27,package.json里的browserslist设置如下:

"browserslist": [
      "firefox 27"
    ]

转换前端的代码如下:

var promise = Promise.resolve('ok');
  console.log(promise);

使用npx babel a.js -o b.js命令进行转码。

下面是转换后的代码:

"use strict";
  require("core-js/modules/es6.promise");
  require("core-js/modules/es6.object.to-string");
  var promise = Promise.resolve('ok');
  console.log(promise);

观察转换的代码,我们发现引入的core-js的API补齐模块非常少,只有2个。为什么呢?

因为我们的代码里只使用了Promise这一火狐27不支持特性API,使用useBuiltIns:"usage"后,Babel除了会考虑目标环境缺失的API模块,同时考虑我们项目代码里使用到的ES6特性。只有我们使用到的ES6特性API在目标环境缺失的时候,Babel才会引入core-js的API补齐模块。

这个时候我们就看出了'entry'与'usage'这两个参数值的区别:'entry'这种方式不会根据我们实际用到的API进行针对性引入polyfill,而'usage'可以做到。另外,在使用的时候,'entry'需要我们在项目入口处手动引入polyfill,而'usage'不需要。

需要注意的是,使用'entry'这种方式的时候,只能import polyfill一次,一般都是在入口文件。如果进行多次import,会发生错误。

corejs

该参数项的取值可以是2或3,没有设置的时候取默认值为2(还有一种对象proposals取值方法,我们实际用不到,忽略掉即可)

这个参数项只有useBuiltIns设置为'usage'或'entry'时,才会生效。

取默认值或2的时候,Babel转码的时候使用的是core-js@2版本(即core-js2.x.x)。因为某些新API只有core-js@3里才有,例如数组的flat方法,我们需要使用core-js@3的API模块进行补齐,这个时候我们就把该项设置为3。

需要注意的是,corejs取值为2的时候,需要安装并引入core-js@2版本,或者直接安装并引入polyfill也可以。如果corejs取值为3,必须安装并引入core-js@3版本才可以,否则Babel会转换失败并提示:

`@babel/polyfill` is deprecated. Please, use required parts of `core-js` and `regenerator-runtime/runtime` separately

modules

这个参数项的取值可以是"amd"、"umd" 、 "systemjs" 、 "commonjs" 、"cjs" 、"auto" 、false。在不设置的时候,取默认值"auto"。

该项用来设置是否把ES6的模块化语法改成其它模块化语法。

我们常见的模块化语法有两种:(1)ES6的模块法语法用的是import与export;(2)commonjs模块化语法是require与module.exports。

在该参数项值是'auto'或不设置的时候,会发现我们转码前的代码里import都被转码成require了。

如果我们将参数项改成false,那么就不会对ES6模块化进行更改,还是使用import引入模块。

使用ES6模块化语法有什么好处呢。在使用Webpack一类的打包工具,可以进行静态分析,从而可以做tree shaking 等优化措施。

配置实例:

module.exports = (api) => {
    api.cache(true); // 添加缓存
    return {
        babelrcRoots: [
            '.'
        ],
        presets: [
            [
                '@babel/preset-env',
                {
                    'modules': false, // 对ES6的模块文件不做转化,以便使用tree shaking、sideEffects等
                    'useBuiltIns': 'entry', // browserslist环境不支持的所有垫片都导入
                    // https://babeljs.io/docs/en/babel-preset-env#usebuiltins
                    // https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md
                    'corejs': {
                        'version': 3, // 使用core-js@3
                    },
                },
            ],
            '@vue/babel-preset-jsx',
        ],
        plugins: [
            省略...
        ],
    };
};

欢迎欢迎~热烈欢迎~