Babel 入门指南

1,331 阅读14分钟

Babel 入门指南

Babel 学习总结,有理解错误的地方,希望大佬指正。

插件版本

以下为本文所提及的所有插件版本:

分类模块版本
工具包@babel/core^7.14.6
@babel/cli^7.14.5
presets@babel/preset-env^7.14.7
@babel/preset-typescript^7.14.5
@babel/preset-react^7.14.5
plugins@babel/plugin-transform-runtime^7.14.5
@babel/plugin-transform-arrow-functions^7.14.5
polyfill@babel/polyfill^7.12.1
runtime@babel/runtime^7.14.6
@babel/runtime-corejs2^7.14.6
@babel/runtime-corejs3^7.14.7
webpackwebpack^5.43.0
webpack-cli^4.7.2
babel-loader^8.2.2

基本概念

你需要的所有 Babel 模块都将作为单独的 npm 包发布,其范围为 @babel(自版本 7 开始)。这种模块化设计允许每种工具都针对特定用例设计。

@babel/core

Babel 的核心功能容纳于 @babel/core 模块。作为终端用户我们无需关注该库,该库将作为其他工具库的依赖项。

npm i -D @babel/core

@babel/cli

@babel/cli 是一个允许你在终端使用 babel 的工具:

npm i -D @babel/cli
npx babel src --out-dir lib

以上命令,将 src 中的 .js 文件通过 babel 编译后,输出至 lib 文件夹。由于我们还没有设置转换规则,这里输出代码将与输入保持一致。

我们可以指定我们想要的转换规则,通过把它们作为选项传给 CLI,接下来会在 plugins presets 中讲解。

我们使用上面的 --out-dir 选项。你可以通过使用 npx babel --help 运行它来查看 cli 工具接收的其余选项。但对我们来说最重要的是 --plugins --presets

转换规则 syntax/api

babel 在转译的时候,会将源代码分成 syntaxapi 两部分来处理:

  • syntax-对新特性语法糖进行转换,如:const, let -> var () => {} -> function(){} 等;
  • api-对 Promise Map 等新的内置对象进行转换,如:babel/polyfill 会在全局挂在一个 window.Promise 补丁方法。

一般情况,plugins presets 用于 syntax 转换,polyfill 用于 api 转换。

plugins

转换规则会体现为插件的形式,插件是小型 JavaScript 程序,它指示 Babel 如何进行代码转换。你甚至可以编写自己的插件,来应用你想要的任何转换规则。

想要将 ES2015+ 语法转换为 ES5,我们可以依赖类似 @babel/plugin-transform-arrow-functions 这样的官方插件,该插件会将 箭头函数 转换为 普通函数,如:

npm i -D @babel/plugin-transform-arrow-functions
npx babel src --out-dir lib --plugins=babel/plugin-transform-arrow-functions

运行以上代码:

In

// src/index.js
const fn = () => console.log('hello babel');

Out

// lib/index.js
const fn = function () {
  return console.log('hello babel');
};

presets

前面我们介绍了如何将 箭头函数 转换为 普通函数,当我们的项目中使用了大量 ES2015+ 的新语法时,就需要配置大量的 plugins

presets 包含着一组预先设定的插件,而不是逐一添加我们想要的所有插件。

就像使用 plugins 一样,你也可以创建自己的 preset,分享你需要的任何插件组合。在这个例子中,我们使用了 @babel/preset-env

npm i -D @babel/preset-env
npx babel src --out-dir lib --presets=@babel/preset-env

这个 preset 包括支持现代 JavaScript(ES2015,ES2016 等)的所有插件。

polyfill

该插件因污染全局作用域,自 Babel v7.4.0 起,@babel/polyfill 已被弃用。推荐使用 @babel/plugin-transform-runtime 方案。

polyfill 即补丁的意思。@babel/polyfill 模块包括:

  • core-js-提供新语法 API 的集合库(缺 Promise API,那么就手写实现一个);
  • regenerator-runtime-提供 generator 函数 API。

这意味着你可以使用像 PromiseWeakMap 这样的新内置函数,像 Array.fromObject.assign 这样的静态方法,像 Array.prototype.includes 这样的实例方法,以及 generator 函数。

为了做到这一点,@babel/polyfill 会在全局作用域和类似 String 这样的内置对象的原型对象上添加对象或方法(同时也会造成全局作用域污染)。

以下完整示例:

# 注意:使用 --save 选项,而不是 --save-dev,这是因为 polyfill 需要在运行时中在源代码之前执行。
npm i --save @babel/polyfill
// .babelrc.json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage"
      }
    ]
  ]
}

.babelrc.json@babel/preset-env 的配置包含 useBuiltIns 属性,该属性有三个取值:

  • false-Boolean 类型,默认为 false,会将整个 polyfill 引入;
  • entry-String 类型,会将 core-js regenerator-runtime目标浏览器缺少的所有方法引入;
  • usage-String 类型,按需引入,当前项目使用过的新语法,但目标浏览器版本不支持的方法。

In

// src/index.js
import '@babel/polyfill';

const map = new Map([
  ['name', 'muzi'],
  ['age', 28],
]);

Out

"use strict";

require("core-js/modules/es6.map.js");
require("core-js/modules/es6.string.iterator.js");
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.array.iterator.js");
require("core-js/modules/web.dom.iterable.js");

var map = new Map([['name', 'muzi'], ['age', 28]]);

core-js@3 之后,@babel/polyfill 就被弃用了,示例如下:

npm install --save core-js@3
// .babelrc.json
module.exports = {
  presets: [
    ['@babel/preset-env', {
      targets: {
        ie: '8',
      },
      useBuiltIns: 'usage',
      corejs: '3',
    }],
  ],
}

In

// src/index.js
import 'core-js/stable';
import 'regenerator-runtime/runtime';

const map = new Map([
  ['name', 'muzi'],
  ['age', 28],
]);

Out

// lib/index.js
"use strict";

require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/stable");
require("regenerator-runtime/runtime");

var map = new Map([['name', 'muzi'], ['age', 28]]);

配置文件

根据需要,我们可以将 plugins presets 的配置从命令行中抽离出来,甚至可以对 plugins presets 传参,以定制转换规则,以下列出了多种格式的 babel 配置文件。

babel.config.json & .babelrc.json

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        }
      }
    ]
  ]
}

将该配置置于项目根目录,执行 npx babel 命令时,babel(v7.8.0+) 会自动识别该文件,该文件表示使用 @babel/preset-env 并将 targets 中浏览器没有的特性转换为 ES5 代码。

查阅 babel.config.json 文档 以查看更多配置选项。

babel.config.js & .babelrc.js

我们可以在 js 文件中定义 babel 配置:

module.exports = (api) => {
  api.cache(true);

  const presets = [
    [
      '@babel/preset-env',
      {
        targets: {
          edge: '17',
          firefox: '60',
          chrome: '67',
          safari: '11.1',
        },
      },
    ],
  ];

  return {
    presets,
  }
}

使用 js 配置 babel,使我们可以访问任何 Nodejs API。例如基于 process 环境变量的动态配置:

const presets = [ ... ];
const plugins = [ ... ];

if (process.env["ENV"] === "prod") {
  plugins.push(...);
}

module.exports = { presets, plugins };

package.json

可以在 package.babel 中编写配置,规则与 babel.config.json .babelrc.json 一致:

{
    // ...
    "babel": {
        "presets": ["@babel/preset-env"]
    }
    // ...
}

环境差异化配置

babel 支持根据不同的环境配置,运用不同的转译规则,可以通过 BABEL_ENVNODE_ENV 对环境进行赋值,如果没有环境变量,babel 默认取 development。示例如下:

cross-env BABEL_ENV=production npx babel src --out-dir lib
# or
cross-env NODE_ENV=production npx babel src --out-dir lib
{
    "env": {
        "development": {
            "presets": [...]
        },
        "production": {
            "presets": [...]
        }
    }
}

小结

  • @babel/cli 从终端运行 Babel
  • plugins presets 用于指定目标浏览器转换规则,syntax 转换;
  • polyfill 即补丁,用于填充目标浏览器中缺少的功能,如 ES6 新的内置函数 Promise Map 等,api 转换;
  • 我们可以通过 BABEL_ENVNODE_ENVbabel 添加环境差异化配置。

presets

@babel/preset-env

npm i -D @babel/preset-env

@babel/preset-env 支持所有 ES2015+ 新语法的 syntax 转换,即对新特性语法糖的转换,如 let, const -> var () => {} -> function(){} 等。

@babel/preset-env 是整个 Babel 大家族最重要的一个 preset

除了进行语法转换,该预设还可以通过设置参数项进行针对性语法转换以及 polyfill 的部分引入。

@babel/preset-env 的参数很多,这里重点讲解最为重要的四个参数 targets useBuiltIns corejs modules

targets

该参数项用来确定目标浏览器环境,而目标浏览器版本是否具备某语法特性的数据来自 Can I use

babel 会根据目标浏览器对语法的支持程度,来进行对应的语法转换

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

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          // browsers 与 browserslist 写法完全一致
          // browsers: ['last 2 versions'], 
          chrome: '58',
          ie: '11',
        },
      },
    ],
  ],
}

useBuiltIns

useBuiltIns 主要与 polyfill 行为相关,决定代码转换后,需要引入哪些补丁包:

  • false-Boolean 类型,默认为 false,会将整个 polyfill 引入;
  • entry-String 类型,会将 core-js regenerator-runtime目标浏览器缺少的所有方法引入;
  • usage-String 类型,按需引入,当前项目使用过的新语法,但目标浏览器版本不支持的方法。

PS:该属性需配合 @babel/polyfill 使用。

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          chrome: '58',
          ie: '11',
        },
        useBuiltIns: 'usage',
      },
    ],
  ],
}

corejs

前面我们讲解过,core-js 是新 api 的集合包。

corejs 属性只有当 useBuiltIns 值为 usageentry 时生效。

corejs 属性用于指定 babel 转码使用的 core-js 版本,取值有两个:

  • 2-Number 类型,默认值为 2,babel 转码使用 core-js@2 版本;
  • 3-Number 类型,babel 转码使用 core-js@3 版本。因为某些新 API 只有 core-js@3 里才有,根据实际需求,决定是否使用 3。

modules

modules 可选 "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认为 "auto"

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

cjscommonjs 的缩写,如果将 modules 设置为 falsebabel 将不会对 ES Module 进行转换,由于 ES Module 是编译时静态编译,在使用 Webpack 一类的打包工具,可以进行静态分析,从而可以做 tree shaking 等优化措施。


@babel/preset-typescript

npm i -D @babel/preset-typescript

@babel/preset-typescript@babel/preset-react 类似,是将特殊语法转换为 JS

@babel/preset-typescript 的原理是直接将 typescript 的代码移除,所以我们不需要 typescript 库,这使得编译速度变得更快。

@babel/preset-typescript 不会对 ts 代码做类型检查,而是将类型检查交给了 IDE 去做(eslint tslint)。

npx babel index.ts --out-file index.js --presets=@babel/preset-typescript

In

// index.ts
interface SumI {
  (a: number, b: number): Number;
}
const sum: SumI = (a, b) => a + b;

Out

// index.js
const sum = (a, b) => a + b;

未关注 @babel/preset-typescript 的其他参数,babel 通过 @babel/preset-typescript 转换为 js 后,再将其交给 @babel/preset-env 处理;

@babel/preset-env 会帮助我们完成 syntax 语法转换,以及 polyfill/runtime 的处理。


@babel/preset-react

npm i -D @babel/preset-react

@babel/preset-reactJSX 语法转换为 React.craeteElement() JS 语法。

npx babel index.jsx --out-file index.js --presets=@babel/preset-react

In

// index.jsx
import React, { useState } from 'react';

const Example = () => {
  const [count, setCount] = useState(0);
  return (
    <div onClick={() => setCount}>
      {count}
    </div>
  );
};

Out

// index.js
import React, { useState } from 'react';

const Example = () => {
  const [count, setCount] = useState(0);
  return /*#__PURE__*/React.createElement("div", {
    onClick: () => setCount
  }, count);
};

plugins

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime 的功能:

  • 处理 helpers-自动移除语法转换后内联的辅助函数(inline Babel helpers),使用 @babel/runtime/helpers 里的辅助函数来替代;
  • 处理 core-js API-当代码里使用了 core-jsAPI,自动从 @babel/runtime-corejs{2, 3}/core-js 中引入,以此来替代全局引入的 core-js/modules
  • 处理 Generator/async-当代码里使用了 Generator/async 函数,自动引入 @babel/runtime/regenerator,以此来替代全局引入的 regenerator-runtime/runtime

配置项

属性类型默认值描述
helpersBooleantrue是否要自动引入辅助函数包
corejsBoolean|Stringfalse可选 false 2 3,是否对 core-js API 进行转换,避免全局污染。

false-不转换,假如代码中使用了 Promisebabel 转换时不会对 Promise 做任何处理;
2-使用 @babel/runtime-corejs2 进行转换。假如代码中使用了 Promisebabel 将提供 var _promise = require("@babel/runtime-corejs2/core-js/promise") 给转换后的代码使用;
3-使用 @babel/runtime-corejs3 进行转换。
regeneratorBooleantrue是否对 Generator/async 函数进行转换
useESModulesBooleanfalse是否使用 ES Modules,在用 webpack 一类的打包工具的时候,我们可以设置为 true,以便做静态分析。
absoluteRuntimeBoolean | Stringfalse该项用来自定义 @babel/plugin-transform-runtime 引入 @babel/runtime/ 模块的路径规则,取值是布尔值或字符串。没有特殊需求,我们不需要修改,保持默认 false 即可。

运行时模块

@babel/runtime @babel/runtime-corejs{2, 3} 中都包含了所有 syntax 转换时需要的 helper 函数,以及 regenerator

@babel/runtime-corejs{2, 3} 则包含了 core-js 的内容(@babel/runtime-corejs{2, 3} = @babel/runtime + core-js{2, 3});

@babel/runtime @babel/runtime-corejs{2, 3}@babel/polyfill 非常类似,都是运行时引入,所以安装需要使用 --save

npm install --save @babel/runtime
# or
npm install --save @babel/runtime-corejs2
# or
npm install --save @babel/runtime-corejs3

@babel/runtime @babel/runtime-corejs{2, 3} 功能作用基本相同,根据需要,选择安装其中一个:

  • @babel/plugin-transform-runtime corejsfalse 时,安装 @babel/runtime。该条件下,babel 不会对 core-js API 进行处理,所以不需要安装 core-js
  • @babel/plugin-transform-runtime corejs2 | 3 时,安装 @babel/runtime-corejs{2, 3}

处理 helpers

babel 在转译的过程中,对 syntax 的处理可能会使用到 helper 函数。如下所示:

// .babelrc.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          chrome: '58',
          ie: '11',
        },
      },
    ],
  ],
}

In

// lib.js
class A {
  constructor() {
    this.name = 'a';
  }
}

export default A;
// index.js
import A from './lib';

class B extends A {
  constructor() {
    super();
    this.name = 'b';
  }
}

Out

// lib.js
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

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

var A = function A() {
  _classCallCheck(this, A);

  this.name = 'a';
};

var _default = A;
exports.default = _default;
// index.js
"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

var _lib = _interopRequireDefault(require("./lib"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

var B = /*#__PURE__*/function (_A) {
  _inherits(B, _A);

  var _super = _createSuper(B);

  function B() {
    var _this;

    _classCallCheck(this, B);

    _this = _super.call(this);
    _this.name = 'b';
    return _this;
  }

  return B;
}(_lib.default);

从上面的例子可以看到,我们在两个文件中使用了 ES6 class 语法,babel 在做 syntax 引入了一堆帮助函数 _classCallCheck _getPrototypeOf _createSuper 等等,_classCallCheck 函数在 lib.js index.js 都重复引入了,在实际项目中,这会让我们编译后的包变得非常臃肿。

@babel/plugin-transform-runtime 的作用,就是将所有 helper 函数的引入统一指向一个包,而这个包就是 @babel/runtime

npm i @babel/runtime
npm i -D @babel/plugin-transform-runtime
// .babelrc.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          chrome: '58',
          ie: '11',
        },
      },
    ],
  ],
  plugins: [
    '@babel/plugin-transform-runtime',
  ],
}

配置 @babel/plugin-transform-runtime 后,转换结果如下:

Out

// lib.js
"use strict";

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

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

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

var A = function A() {
  (0, _classCallCheck2.default)(this, A);
  this.name = 'a';
};

var _default = A;
exports.default = _default;
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _lib = _interopRequireDefault(require("./lib"));

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }

var B = /*#__PURE__*/function (_A) {
  (0, _inherits2.default)(B, _A);

  var _super = _createSuper(B);

  function B() {
    var _this;

    (0, _classCallCheck2.default)(this, B);
    _this = _super.call(this);
    _this.name = 'b';
    return _this;
  }

  return B;
}(_lib.default);

处理 core-js API & Generator/async

以下示例可以看到,转换后的代码未对 Promise 作任何处理,这是因为 @babel/plugin-transform-runtimecorejs 配置为默认的 false

// .babelrc.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          chrome: '58',
          ie: '8',
        },
      },
    ],
  ],
  plugins: [
    '@babel/plugin-transform-runtime',
  ],
}

In

// index.js
const fn = async () => {
  await new Promise((resolve) => {
    setTimeout(() => {
      resolve('done')
    }, 500);
  });
}

fn();

Out

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

var fn = /*#__PURE__*/function () {
  var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
    return _regenerator["default"].wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return new Promise(function (resolve) {
              setTimeout(function () {
                resolve('done');
              }, 500);
            });

          case 2:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));

  return function fn() {
    return _ref.apply(this, arguments);
  };
}();

fn();

当我们设置 corejs: 2

// .babelrc.js
plugins: [
    ['@babel/plugin-transform-runtime', {
        corejs: '2',
    }],
],

Out

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator"));

var fn = /*#__PURE__*/function () {
  var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
    var res;
    return _regenerator["default"].wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return new _promise["default"](function (resolve) {
              setTimeout(function () {
                resolve('done');
              }, 500);
            });

          case 2:
            res = _context.sent;
            console.log(res);

          case 4:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));

  return function fn() {
    return _ref.apply(this, arguments);
  };
}();

fn();

如上所示,@babel/polyfill 在处理 Promise 时,是直接挂载了 window.Promise 方法:

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

@babel/plugin-transform-runtime 则使用了 @babel/runtime-corejs2 中的 _promise 方法,该模块实现了与 Promise 完全相同的功能方法,供浏览器使用。这么做就避免了全局污染。


webpack 集成

我们可以使用 babel-loaderwebpack 中集成 babel。以下示例简单介绍 @babel/preset-env 集成使用,如果需要转换 typescript react 等语法,与之同理。

安装依赖:

npm i -D @babel/core @babel/cli

npm i -D @babel/preset-env @babel/plugin-transform-runtime
npm i -S @babel/runtime-corejs2

npm i -D webpack webpack-cli
npm i -D babel-loader

业务代码:

// src/index.js
const fn = async () => {
  const res = await new Promise((resolve) => {
    setTimeout(() => {
      resolve('done');
    }, 1000);
  });

  console.log(res);
}

fn();

配置 babel

// .babelrc.js
module.exports = {
  presets: [
    ['@babel/preset-env', {
      // babel 不会处理 ES Modules 语法
      // webpack 对 import 语法做 Tree Shaking 优化,一定程度减少包体积
      modules: false,
    }],
  ],
  plugins: [
    ['@babel/plugin-transform-runtime', {
      corejs: '2',
    }],
  ],
}

配置浏览器支持:

# .browserslistrc
> 1%
last 2 versions
not ie <= 8

配置 webpack

// webpack.config.js
const path = require('path');
const resolvePath = (pathstr) => path.resolve(__dirname, pathstr);

module.exports = {
  mode: 'development',
  entry: {
    main: resolvePath('./src/index.js'),
  },
  output: {
    filename: 'index.js',
    path: resolvePath('./lib'),
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        include: [resolvePath('./src')],
        use: 'babel-loader',
        exclude: /node_modules/,
      },
    ]
  },
}

执行打包:

npx webpack --config webpack.config.js

参考资料