# 一、@babel/preset-env

preset-env根据配置的浏览器的列表,自动加载当前浏览器所需要的插件,然后对ES语法做转换。

# 1. targets

string | Array<string> | { [string]: string }

描述项目所在环境,比如

{
  "targets": "> 0.25%, not dead"
}

或者

{
  "targets": {
    "chrome": "58",
    "ie": "11"
  }
}

babel中内置了browserslist (opens new window),也可以用.browserslistrc配置文件配置,比如

> 0.25%, not dead

或者在package.json中指定:

"browserslist": "> 0.25%, not dead"

可以用下面命令查看> 0.25%, not dead代表的浏览器列表:

$ npx browserslist "> 0.25%, not dead"
and_chr 94
and_ff 92
and_uc 12.12
chrome 94
chrome 93
chrome 92
chrome 91
chrome 87
edge 94
...

如果不指定targets,将会转化所有的ES2015-ES202语法到ES5。不推荐这么做,这样不能发挥preset-env针对性转化的作用。

# 2. loose

boolean,默认false。设为true的时候是会preset的插件开启宽松模式。loose模式会生成简单的ES5代码,正常模式则会尽可能遵循ECMAScript 6的语义,一般不建议开启。

举个例子,对于下面的代码:

class Test {
    say(){}
}

如果开启loose模式,会十分接近我们的ES5写法,直接在Test.prototype上增加属性:

var Test = /*#__PURE__*/function () {
  function Test() {}

  var _proto = Test.prototype;

  _proto.say = function say() {
    console.log('say');
  };

  return Test;
}();

如果未开启loose,会利用Object.definePropertyTest上增加属性:

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var Test = /*#__PURE__*/function () {
  function Test() {
    _classCallCheck(this, Test);
  }

  _createClass(Test, [{
    key: "say",
    value: function say() {
      console.log('say');
    }
  }]);

  return Test;
}();

# 3. moduels

"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认为"auto".

是否把ESM语法转为其他类型,如果是false就不转化。

比如对于以下代码:

import { a } from './a.js'
var b = 1;
export default b;
export { a }

如果是modulescjs,会转为:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
Object.defineProperty(exports, "a", {
  enumerable: true,
  get: function get() {
    return _a.a;
  }
});
exports["default"] = void 0;

var _a = require("./a.js");

var b = 1;
var _default = b;
exports["default"] = _default;

默认情况下,@babel/preset-env会根据caller data确定是否转换ES6的模块,比如使用babel-loader@rollup/plugin-babel时。

# 4. debug

boolean,默认为false

开启后,会在控制台输出当前的targets、使用的插件列表等信息。

# 5. include/exclude

Array<string|RegExp>,默认为[]

表示增加或排除的插件列表

# 6. useBuiltIns

"usage" | "entry" | false,默认为false

@babel/preset-env使用polyfill的配置。

什么是polyfill?polyfill可翻译为垫片,就是为旧浏览器提供新的功能(API)。

Babel只转换不兼容的新语法,比如箭头函数和class,对于新的API,比如IteratorGeneratorSetMapProxyReflectSymbolPromiseObject.assign()等,是不会转换的。这时候就需要polyfill了。

babel7.4.0版本后弃用了@babel/polyfill,建议我们直接使用core-js,并且设置corejs的版本来替换polyfill

# (1)false

useBuiltInsfalse的话,不对polyfill做操作。如果引入了core-js,则无视配置的浏览器兼容,引入所有的polyfill

# (2)entry

useBuiltInsentry的话,引入浏览器不兼容的polyfill,需要在入口文件手动添加import 'core-js',会自动根据browserslist替换为浏览器不兼容的polyfill。这里需要指定core-js的版本:

"useBuiltIns": "entry",
"corejs": 2,

比如对于下面代码:

import "core-js";

new Promise(() => {})

babel转化结果为:

"use strict";

require("core-js/modules/es6.array.copy-within.js");
require("core-js/modules/es6.array.every.js");
// 省略很多require('core-js/modules/es6...')

new Promise(function () {}); 

# (3)usage

useBuiltInsusage的话,会根据配置的浏览器的兼容性,以及代码中用到的API来进行polyfill,实现了按需添加。

"useBuiltIns": "usage",
"corejs": 2,

比如,对于以下代码:

new Promise(() => {})

转化结果为:

"use strict";

require("core-js/modules/es6.object.to-string.js");

require("core-js/modules/es6.promise.js");

new Promise(function () {}); 

# 7. corejs

string or { version: string, proposals: boolean },默认为"2.0"

执行core-js的版本,该选项仅在与useBuiltInsentryusage时有效。

# 二、@babel/plugin-transform-runtime

如果我们有很多需要编译的文件的时候,每个文件中都会有这些方法的定义,这样整个包就会很大,runtime把这些方法抽离到一个公共的地方@babel/runtime/helpers,所以可以让我们打包出来的源码变小。

注意安装依赖的时候,@babel/runtime--save而不是--save-dev,因为它是运行时引入的,其实上面的core-js也是一样:

npm install --save-dev @babel/plugin-transform-runtime

npm install --save @babel/runtime

# 1. corejs

false, 2, 3 or { version: 2 | 3, proposals: boolean },默认false

指定core-js的版本,注意core-js3版本才包含[].includes语法。

corejs 安装命令
false npm install --save @babel/runtime
2 npm install --save @babel/runtime-corejs2
3 npm install --save @babel/runtime-corejs3

举个例子,对于以下代码:

class Test {
}
const c = [1, 2, 3].includes(1)

如果设置如下:

{
  "presets": [
    [
      "@babel/preset-env",
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 3
      }
    ]
  ]
}

转换结果:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));

var _context;

var Test = function Test() {
  (0, _classCallCheck2["default"])(this, Test);
};

var c = (0, _includes["default"])(_context = [1, 2, 3]).call(_context, 1); 

@babel/plugin-transform-runtime中的core-js引入的是局部变量,不会污染环境,比如_includes。而@babel/preset-env中的core-js使用的方式是require("core-js/modules/es7.array.includes.js"),会污染全局变量。

如果指定了这里的core-js23,并且preset-env设置了useBuiltInsusage,那么preset-envcore-js就用不到了,因为还是使用运行时的。

# 2. helpers

boolean,默认true

是否用外部引入的方式使用帮助函数,比如classCallCheckextends等。

如果设置为false,则会在文件顶部声明这些helper,而不是引入外部文件。

# 3. regenerator

boolean,默认true

是否开启添加regenerator函数的polyfill防止全局污染。

举个例子,对于以下代码:

function* testGenerator() {
  yield '1';
  yield '2';
  return '3';
}

如果设置为true,转化结果为:

"use strict";

var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

var _marked = /*#__PURE__*/_regenerator["default"].mark(testGenerator);

function testGenerator() {
  return _regenerator["default"].wrap(function testGenerator$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return '1';

        case 2:
          _context.next = 4;
          return '2';

        case 4:
          return _context.abrupt("return", '3');

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

如果设置为false,并且文件中使用了generator,会在文件顶部require("regenerator-runtime/runtime.js"),会造成全局污染。

# 三、配置示例

// .babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        // "modules": "cjs",
        "useBuiltIns": "usage",
        "corejs": 2,
        // "loose": false,
        // "targets": "chrome 79",
        // "debug": true,
      }
    ],
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        // "corejs": 3,
        "helpers": false,
        "regenerator": false,
      }
    ],
  ]
}

# 四、系列文章

  1. Babel基础
  2. Babel源码解析之@babel/core (opens new window)
  3. Babel源码解析之@babel/parser (opens new window)
  4. Babel源码解析之@babel/traverse (opens new window)
  5. Babel源码解析之@babel/generator (opens new window)