Babel配置详解

2,205 阅读9分钟

Babel6 与Babel7 配置具有一定的差异,在Babel的发展历程中,我们有必要对其配置有一定的了解,才能在项目中正常合理的配置Babel的编译,配置错误会导致出现冗余代码、项目构建产物过大或浏览器不兼容等问题。

依赖安装

pnpm i @babel/core @babel/cli @babel/preset-env -D

默认转换规则

默认只转语法,不转API

新的API

  • 新增的全局对象:PromiseMapSymbolProxyIterator
  • 新增的实例方法:[].find(), ''.includes, Object.assign()

Babel的配置文件

默认会在当前项目根目录查找 .babelrc.babelrc.js.babelrc.jsonbabel.config.jsonbabel.config.jspackage.json

babel.config.js

module.exports = {
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage",
        "corejs": "3.6.5"
      }
    ]
  ]
}

Babel7.8及以下版本支持直接json配置

babel.config.json

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

package.json 配置方式

{
  "name": "my-package",
  "version": "1.0.0",
  "babel": {
    "presets": ["@babel/preset-env"],
    "plugins": [ ... ],
  }
}

预设和插件

babel的配置主要分为 presets预设 和 plugins插件

每一个ES版本都会新增一些新特性,这些新特性,babel 会提供相应的插件 plugins-list,每一个插件对应一个npm包

比如 ES2017新增了 asyncawait,babel提供了 @babel/plugin-transform-async-to-generator 用于将 async/await 转换为 generator 函数,

async function foo() {
  await bar();
}

// ==>

var _asyncToGenerator = function (fn) {
  ...
};
var foo = _asyncToGenerator(function* () {
  yield bar();
});

ES2018 新增了对象...展开,babel 提供 @babel/plugin-proposal-object-rest-spread 来转换

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }

// ===>
let n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }

由于插件太多,Babel提供的了预设功能,用于简化配置,预设是一系列插件的集合,常用预设有:

@babel/preset-env
@babel/preset-typescript
@babel/preset-react
@babel/preset-flow

预设和插件的的短名称

  • @babel/preset-env 简写 @babel/envenv, 可省略 preset-
  • @babel/plugin-proposal-object-rest-spread 简写 @babel/proposal-object-rest-spread,可省略 plugin-
// babel.config.js
module.exports = {
  "presets": ['@babel/env'],
  "plugins": ["@babel/transform-decorators-legacy"]
}

预设和插件的执行顺序

  • 插件比预设先执行
  • 插件执行顺序是插件数组从前向后执行
  • 预设执行顺序是预设数组从后向前执行

预设和插件版本

Babel7以前的版本,使用年代preset,如 babel-preset-es2015babel-preset-es2016babel-preset-es2017babel-preset-latestbabel-preset-stage-0babel-preset-stage-1babel-preset-stage-2 等。

Babel7开始推荐使用 @babel/preset-react@babel/preset-env@babel/preset-flow@babel/preset-typescript

常用插件主要是 @babel/plugin-transform-runtime

babel-polyfill

polyfill 是大浏览器中添加缺失的新特性,补齐API和标准对齐,比如,浏览器不支持Promise,那么polyfill 会添加 window.Promise 来支持 promise

polyfill 是为当前环境提供一个垫片。所谓垫片,是指垫平不同浏览器之间差异的东西。polyfill提供了全局的ES6对象以及通过修改原型链 Array.prototype等实现对实例的实现。

Babel polyfill 使用的几种方式

  1. 直接在html中引用构建好的 polyfill.js
  2. 在工程入口文件中引用 polyfill.js
  3. 在工程入口文件中引用 @babel/polyfill
  4. 在前端工程的入口文件里引入 core-js/stableregenerator-runtime/runtime
  5. 在前端工程构建工具的配置文件入口项引入 polyfill.js
  6. 在前端工程构建工具的配置文件入口项引入 @babel/polyfill
  7. 在前端工程构建工具的配置文件入口项引入 core-js/stableregenerator-runtime/runtime
// import './polyfill.js'
// import '@babel/polyfill'
import "core-js/stable";
import "regenerator-runtime/runtime";

const fn1 = (num) => num + 1;
const path = require('path');
module.exports = {
  // entry: ['./polyfill.js', './a.js'],
  // entry: ['@babel/polyfill', './a.js'],
  entry: ['core-js/stable', 'regenerator-runtime/runtime', './a.js'],
  output: {
    filename: 'b.js',
    path: path.resolve(__dirname, '')
  },
  mode: 'development'
};

以上引用方式是Babel7以前的使用方式,这种方式都是全量引用所有的新特性垫片,这样引用的 polyfill 包会大增加我们构建的js体积大小,如果我们只用到了其中两三个新特性,那就存在很多冗余的垫片。

@babel/preset-env

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

配置

 module.exports = {
    //  三种配置方式等同
    presets: ["@babel/env"],
    // presets: [["@babel/env", {}]],
    // presets: [["@babel/env"]],
    plugins: []
  }

@babel/env@babel/preset-env 的简写,核心配置项有 targetsuseBuiltInsmodulescorejs

targets

用于设置兼容到哪些目标浏览器,如果不配置,则尝试读取 package.json.browserslistrc 中的配置, browserslist 的配置也同样作用于 autoprefixerpostcss等插件。 如果没有 targetsbrowserslist 配置,则转换所有的ES6语法为ES5版本

useBuiltIns

取值有 usage/entry/false,默认为 false

  • false 不使用 polyfill,只转换语法
  • entry 会根据目标浏览器环境,引用未支持的所有的 polyfill,需要在入口文件引用 @babel/polyfill
  • usage 会先分析代码中使用到的新特性,只为用到的新特性添加 polyfill,不需要手动添加 @babel/polyfill,但需要配置 corejs 的版本,不配置会有警告。
WARNING (@babel/preset-env): We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option.

You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands:

  npm install --save core-js@2    npm install --save core-js@3
  yarn add core-js@2              yarn add core-js@3
"browserslist": [
  "chrome >= 49"
],

配置 usage,只为使用到的新特性提供 polyfill

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

源码

const configPromise = Promise.resolve({url: '/login'})

babel转换后

"use strict";

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

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

const configPromise = Promise.resolve({
  url: '/login'
}); 

corejs

取值为 2 或 3, 3 是 2 的升级版,会扩展一些新的API的polyfill,比如数组的flat方法,一般使用 3 的版本。

安装相应的模块

npm install --save core-js@2    
npm install --save core-js@3

modules

模块语法转换

用于将当前ES6的模块语法转换为其它模块化的语法

取值 amd/umd/systemjs/commonjs/cjs/auto/false,默认为 auto ,会转换为 commonjs 语法

常见的模块化语法有两种:

  • ES6的模块法语法用的是 importexport
  • commonjs模块化语法是 requiremodule.exports

一般建议设置为 false,不转换 es6 的模块语法,方便构建工具如 webpackrollup 对模块进行静态分析,实现 tree shaking 等优化措施

@babel/plugin-transform-runtime

usage 的缺点: 语法转换后,代码是会注入一些辅助函数,如果有很多个js文件,每个文件顶部都会注入相关辅助函数,有可能会注入导致辅助函数重复,最后用构建工具打包出来的产物会非常大。

 class Person {
    sayname() {
      return 'name'
    }
  }

转换后

"use strict";

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); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }

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

  _createClass(Person, [{
    key: "sayname",
    value: function sayname() {
      return 'name';
    }
  }]);

  return Person;
}();

为了实现 class 语法,添加了 _classCallCheck_createClass_defineProperties几个辅助函数。

@babel/runtime

为了在很多js转换情况下,避免辅助函数直接注入到代码中,babel提供了 @babel/runtime 来提供这些辅助函数单独引用。

@babel/runtime 只是提供了辅助函数模块,但还不能在转换过程中自动替换,这需要 @babel/plugin-transform-runtime 插件来提供自动替换辅助函数的功能。

"use strict";

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

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

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

var Person = /*#__PURE__*/function () {
  function Person() {
    (0, _classCallCheck2.default)(this, Person);
  }

  (0, _createClass2.default)(Person, [{
    key: "sayname",
    value: function sayname() {
      return 'name';
    }
  }]);
  return Person;
}();

函数注入手动改成函数引用,减少了函数直接注入造成的构建产物增大的问题

实际生产中,我们需要同时安装这两个模块,注意由于辅助函数最终是需要打包到构建产物中的,@babel/runtime 需要安装到 dependencies

npm install --save @babel/runtime
npm install --save-dev @babel/cli @babel/core  @babel/preset-env @babel/plugin-transform-runtime

plugin-transform-runtime 的作用

  1. 自动移除转换后的内联辅助函数,使用 @babel/runtime/helpers 里的辅助函数来替代
  2. 当代码里使用了 core-js 的API,自动引入 @babel/runtime-corejs3/core-js-stable/,以此来替代全局引入的 core-js/stable;
  3. 当代码里使用了 Generator/async 函数,自动引入 @babel/runtime/regenerator,以此来替代全局引入的 regenerator-runtime/runtime

2和3的核心功能是使用函数来替换原生api,以达到不污染全局对象的目的( polyfillcore-js/stableregenerator-runtime/runtime都是直接对原生对象做扩展,会污染原生对象)。

  • polyfill 的方式是直接扩展原生对象,比如浏览器不支持 Promise,那么 polyfill 的作用就是在window上实现Promise对象,生成一个 window.Promise,这样会污染原生环境。
  • plugin-transform-runtime 的方式是添加 _promise 辅助函数来替换原来的 api
// babel.config.js
module.exports = {
  presets: [['@babel/env']],
  plugins: [
    ['@babel/transform-runtime', {
      corejs: 3
    }]
  ]
}

指定 corejs 版本,才能对相关的API做替换,指定版本后,需要安装相应的corejs版本,有两种方式

  1. 安装 @babel/runtimecore-js@3
  2. 安装 @babel/runtime-corejs3
// 原始代码
var obj = Promise.resolve();

// polyfill
require("core-js/modules/es.promise.js");
var obj = Promise.resolve();

// plugin-transform-runtime
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var obj = _promise["default"].resolve();

默认配置

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "version": "7.0.0-beta.0"
      }
    ]
  ]
}
  • absoluteRuntime 默认为 false, 也就是默认从当前项目根目录下的 node_modules 下引用 @babel/runtime,当前没有的话,需要手动指定路径。
  • helpers 默认为 true,即自动引入辅助函数替换内联函数。
  • corejs 默认为 false, 不对不对Promise这一类的API进行转换,仍然使用 core-js 提供的 polyfill
  • regenerator 默认为 true,自动对 regenerator/async api 进行转换。
  • version 这里对应的是 @babel/runtime@babel/runtime-corejs2/3 的版本。如果安装了@babel/runtime 的后续版本(或者它们的 corejs 对应版本,比如@babel/runtime-corejs3) ,或者将其列为依赖项,则转换运行时可以使用更高级的特性。

依赖于 @babel/runtime-coregs2@7.7.4,则可以使用

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 2,
        "version": "^7.7.4"
      }
    ]
  ]
}

一般前端业务开发,使用默认值就好,即不用添加任何配置

// regenerator: false (直接修改全局对象,添加polyfill)
require("regenerator-runtime/runtime.js");

// regenerator: true (返回 _regenerator 工具函数)
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

使用场景

业务开发

  • 插件 @babel/plugin-transform-runtime 使用默认配置即可
  • @babel/preset-envuseBuiltIns 设置为 usage, corejs 设置为 3
module.exports = {
  "presets": [
    [
      "@babel/preset-env", 
        {
          // 不转换模块类型,仍然使用 ES Module,方便构建工具 tree shaking
          "modules": false,
          // 使用 usage,只为代码用到的新特性提供 polyfill
          "useBuiltIns": "usage",
          // 使用 corejs3版本提供的 polyfill
          "corejs": 3
        }
    ], 
  ],
  // 引入辅助函数替换内联函数
  "plugins": ["@babel/plugin-transform-runtime"]
}

npm模块开发

  • 插件 @babel/plugin-transform-runtimecorejs 配置为2或3
  • 发布为ES模块时,@babel/preset-envmodules 要设置为 false,不转换模块类型,仍然使用 ES Module,方便构建工具 tree shaking

阅读参考