ES6 发布至今真的已经很久了,但不管是浏览器环境还是 Node 环境都没有完全支持,想要原汁原味地使用这些新特性,就必须用到转码神器 Babel。这篇文章就来聊一聊如何使用 Babel。

初探

新建一个项目,在其中新建一个 src 文件夹用于存放我们的源码,源码很简单,一个 javascript 文件 a.js,其内容如下:

let entries = [['name', 'mike'], ['age', 22]];

entries.forEach((item) => {
  console.log(item)
});


 


主要使用了 let 关键字和箭头函数。

安装 @babel/cli@babel/core

npm install --save-dev @babel/core @babel/cli

Babel 的核心功能位于 @babel/core 中,所以使用 Babel 时一定要安装它,它的使用方式是:

const babel = require("@babel/core");

babel.transform("code", optionsObject);

可以看出,其使用方式非常不便,因此 Babel 提供了脚手架工具 @babel/cli,方便用户使用。

package.jsonscripts 中添加命令:

{
  "scripts": {
    "build": "babel src --out-dir dist"
  }
}

此命令用于通过使用 babelsrc 文件夹中的文件编译后输出到 dist 文件夹。

运行 npm run build,稍等片刻,打开 dist 文件夹中的 a.js,其内容如下:

let entries = [['name', 'mike'], ['age', 22]];
entries.forEach(item => {
  console.log(item);
});

可以看到,除了少了一行空行外,代码没有任何变化,这是因为,使用 Babel,我们需要对其进行配置,否则它形同虚设。

配置

配置 Babel 的方式有两种(本质上来说其实就一种),一种是使用插件(plugins),另一种是使用预设(presets)。通常我们会将配置写在一个 .babelrc 文件中,我们也在项目的根目录下新建一个 .babelrc 文件备用。

插件

Babel 基本上为每一种新的语法(包括某些流行的非标准语法)都提供了一个对应的语法插件和转换插件,前者用于解析对应语法,如 react-jsx 用于解析 jsx 语法;后者用于转换对应语法,转换插件会自动启动语法插件,因此不需要同时指定两者。

示例中,转换箭头函数需要使用 @babel/plugin-transform-arrow-functions,首先安装该插件:

npm install --save-dev @babel/plugin-transform-arrow-functions

然后在 .babelrc 中添加该插件:

{
  "plugins": [
    "@babel/plugin-transform-arrow-functions"
  ]
}

得到的结果如下:

let entries = [['name', 'mike'], ['age', 22]];
entries.forEach(function (item) {
  console.log(item);
});

 


可以看到,箭头函数已经被转换成了普通函数。

注意前文的措辞,是语法转换插件,不是方法转换插件。什么意思呢?如果我们在代码中使用了新的方法:

let object = Object.fromEntries(entries);
console.log(object);

就需要使用 polyfill(通常译为垫片)来模拟这些新方法,因为这些方法在老环境中是不存在的。Babel 也提供了插件 @babel/polyfill,但自 Babel7.4.0 之后,官方已经不推荐使用它了,取而代之的是直接在文件中引入 core-js/stableregenerator-runtime/runtime

import "core-js/stable";
import "regenerator-runtime/runtime";

运行 npm run build,稍等片刻,会发现编译的结果除了比源码少几行空行外没有任何区别,但如果将 import 改为 require()(因为 Node 环境不支持 import),然后运行 node dist/a.js,控制台将输出下列内容:

[ 'name', 'mike' ]
[ 'age', 22 ]
{ name: 'mike', age: 22 }


 

说明 fromEntries 方法正常执行了。

这其实是直接使用 core-js 来进行的方法转换,如果我们有多个 JavaScript 文件,就需要在每个文件中引入上述代码,除非应用程序只有一个入口文件。

插件还有配置项,可以对其功能早进一步设置,详情请查阅相应文档。

从上面的配置方式可以看出,随着使用的新语法的增多,配置步骤会非常繁琐,通常我们并不会这样去进行配置。Babel 也考虑到了这一点,因此提供了预设。

预设

预设其实就是一堆插件的组合,前文中我们每使用一种新特性,就需要手动添加一个相应的插件,这个过程是非常繁琐的,而粗暴地引入所有插件又会增加文件体积,因此 Babel 官方将部分插件组合在一起,形成了各种“预设”,让用户可以直接使用。

官方提供的预设有:

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

其中最通用的预设是 @babel/preset-env,其余的预设从命名上就可以看出是针对特定环境的,本文将不做讲解。

@babel/preset-env

安装 @babel/preset-env

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

修改 .babelrc 文件:

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

运行 npm run buidl,编译的结果如下:

"use strict";

require("core-js/stable");

require("regenerator-runtime/runtime");

var entries = [['name', 'mike'], ['age', 22]];
entries.forEach(function (item) {
  console.log(item);
});
var object = Object.fromEntries(entries);
console.log(object);

可以看到,文件头部添加了 use strict,将 import 转为了 require(),将 let 转为了 var,和前文的结果基本相同,但配置就简洁多了。

不过,@babel/preset-env 能做的不只如此,它提供了 useBuiltInscorejstargets 配置项来优化编译结果。

useBuiltIns 和 corejs

useBuiltIns 有三个值,分别为 entryusagefalse,默认值为 false

useBuiltIns 值不为 false 时,需要配合 corejs 配置项一起使用,它用于指定所使用的 core-js 的版本,通常都设置为 3,因为 2 已不再添加新特性了,需要单独安装一下:

npm install core-js@3 --save

useBuiltIns 值为 entry 时:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry",
        "corejs": 3
      }
    ]
  ]
}

Babel 会在编译结果中引入所有的 polyfill:

"use strict";

require("core-js/modules/es.symbol");

require("core-js/modules/es.symbol.description");

require("core-js/modules/es.symbol.async-iterator");

require("core-js/modules/es.symbol.has-instance");

//此处略去几百行···

var entries = [['name', 'mike'], ['age', 22]];
entries.forEach(function (item) {
  console.log(item);
});
var object = Object.fromEntries(entries);
console.log(object);

这无疑会增加文件体积,因为我们并不一定会使用到所有的新特性。

useBuiltIns 值为 usage 时:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}

编译的结果如下:

"use strict";

require("core-js/modules/es.array.for-each");

require("core-js/modules/es.array.iterator");

require("core-js/modules/es.object.from-entries");

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

require("core-js/stable");

require("regenerator-runtime/runtime");

var entries = [['name', 'mike'], ['age', 22]];
entries.forEach(function (item) {
  console.log(item);
});
var object = Object.fromEntries(entries);
console.log(object);

可以看到,Babel 只会按需引入。

targets

targets 可以指定项目所需的运行环境,进一步优化编译结果:

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

不过,使用 .browserslistrc 配置文件通常是更好的选择,因为一些其它的工具可能也需要用到这些配置,可以通用。

不足

@babel/preset-env 也有不足的地方,我们在 a.js 中再添加一些新特性:

let arr = [2, 5, 8];
console.log(arr.includes(5));

async function asyncFunc() {
  console.log("async2");
}

let symbol = new Symbol()

运行 npm run build,得到的结果如下:

"use strict";

require("core-js/modules/es.symbol");

require("core-js/modules/es.symbol.description");

require("core-js/modules/es.array.for-each");

require("core-js/modules/es.array.includes");

require("core-js/modules/es.array.iterator");

require("core-js/modules/es.object.from-entries");

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

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

require("regenerator-runtime/runtime");

require("core-js/stable");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

var entries = [['name', 'mike'], ['age', 22]];
entries.forEach(function (item) {
  console.log(item);
});
var object = Object.fromEntries(entries);
console.log(object);
var arr = [2, 5, 8];
console.log(arr.includes(5));

function asyncFunc() {
  return _asyncFunc.apply(this, arguments);
}

function _asyncFunc() {
  _asyncFunc = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            console.log("async");

          case 1:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _asyncFunc.apply(this, arguments);
}

var symbol = new Symbol();






















 
 
 








 
























可以看到第 23-25 行,Babel 注入了两个针对 async 的辅助函数,这无疑会增加文件的体积,尤其是在很多文件都用到了新特性的时候;而第 33 行的 includes 方法是添加在 Array 原型链上的,会对全局环境造成污染。

Babel 也考虑到了这一点,因此提供了 @babel/plugin-transform-runtime 这个插件。

@babel/plugin-transform-runtime

此插件可以创建一个沙盒环境,解决上述问题。

首先安装它:

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

同时还需要安装它的生产版本,因为它本身仅用于开发环境:

npm install --save @babel/runtime

修改 .babelrc

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

注意 corejs 所在的位置,运行 npm run build,编译的结果如下:

"use strict";

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

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

require("regenerator-runtime/runtime");

var _symbol = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/symbol"));

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

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

var _fromEntries = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/from-entries"));

var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each"));

require("core-js/stable");

var entries = [['name', 'mike'], ['age', 22]];
(0, _forEach["default"])(entries).call(entries, function (item) {
  console.log(item);
});
var object = (0, _fromEntries["default"])(entries);
console.log(object);
var arr = [2, 5, 8];
console.log((0, _includes["default"])(arr).call(arr, 5));

function asyncFunc() {
  return _asyncFunc.apply(this, arguments);
}

function _asyncFunc() {
  _asyncFunc = (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:
            console.log("async");

          case 1:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _asyncFunc.apply(this, arguments);
}

var symbol = new _symbol["default"]();

可以看到,原本的辅助函数变成了一个方法引用,这样就解决了上述问题。

注意点

使用插件和预设时,有下列几点需要注意:

  • 插件是在预设前运行的,如果将前例中 corejs 配置项改回到 presets 中,得到的编译结果会不一样;
  • 插件的运行顺序是从前往后的;
  • 预设的运行顺序是从后往前的。

在配置时需要留意,详情可以查阅官方文档。

结语

通常,如果没有特殊的需求,使用 @babel/preset-env@babel/plugin-transform-runtime 就满足了,而且它们的配置也并不复杂。

最近更新:
作者: MeFelixWang