在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,我们再来看一下转换结果,github配套代码babel09。
"browserslist": [ "chrome 60" ]
转换后
"use strict"; var fn = num => num + 2;
我们发现转换后的代码仍然是箭头函数,因为Chrome60已经实现了箭头函数语法,所以不会转换成ES5的函数定义语法。
现在我们把Chrome60改成Chrome38,再看看转换后的结果,github配套代码babel10。
"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"
配套的代码github是babel11例子。
我们在入口文件用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"
配套的代码github是babel12例子。
"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 等优化措施。
最后一行tree shaking
感谢,已修改
如何我们对@babel/preset-env的targets参数项进行了设置 =>
如果我们对@babel/preset-env的targets参数项进行了设置
感谢,已修改
目标环境是市场份额大于1%的浏览器并且不考虑IE8及一下的IE浏览器
一下-->以下
文章最后说,modules字段默认是'auto',会把所有的ESModule(import语法)转为commonjs(require语法),可是webpack我记得是可以做Tree shaking的啊,而且Tree shaking的前提就是必须使用ESModule,babel都把ESModule给转没了,最后竟然还能做Tree shaing,这块感觉想不通,我感觉我的逻辑无法自洽了,可否请大佬解答一下呢?
老哥,怎么把最新的特性转化到指定的es版本,比如将es2022的语法转换成es2017
@babel/preset-env和@babel-plugin-transform-runtime是不是只用一个就够了?
真棒,还说明了重点
好文章!赞