参考文章连接 —>

@符号是什么?

这是babel 7 的一大调整,原来的 bebel-xxx 包统一迁移到 babel域下(域由 @ 符号来标识); 一来便于区别官方与非官方的包; 二来避免可能的包命名冲突

babel-core

babel-core 的作用是把js代码分析成 ast,方便各个插件分析语法进行相应的处理


@babel/cli

是 babel 提供的命令行工具,用于命令行下编译源代码

安装方法:

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

babel插件

@babel/plugin-transform-arrow-functions

将箭头函数转换为 ES5函数

@babel/plugin-transform-block-scoping

转换 es6中的 **const** 为 v**ar**

@babel/plugin-transform-classes

转换 class 为es5函数

.babelrc文件中配置

{
  "plugins": [
    "@babel/plugin-transform-arrow-functions",
    "@babel/plugin-transform-block-scoping",
    "@babel/plugin-transform-classes"
  ]
}

test.js文件

const alertMe = (msg) => {
  window.alert(msg)
}
class Robot {
  constructor (msg) {
    this.message = msg
  }
  say () {
    alertMe(this.message)
  }
}
const marvin = new Robot('hello babel')
npx babel test.js

注:

只是,这样安装插件、配置 .babelrc 的过程非常乏味,而且容易出错。通常,我们不会关心到具体的某个ES2015特性支持情况这个层面,我们更关心浏览器版本这个层面。

比如,我不想关心 babel 插件的配置,只是希望给 babel 一个 我想支持IE10 的提示,babel 就帮我编译出能在 IE 10 上正常运行的 javascript 代码。

—-> 此时就需要


@babel/preset-env

它可以根据开发者的配置,按需加载插件 默认配置下,它跟babel-preset-latest是等同的,会加载从es2015开始的所有preset

.babelrc 中配置:

{
 "presets": ["@babel/preset-env"],  // 此时 上面的那些 plugins 就不需要再配置了
}
  • 只想支持最新版Chrome
{
 "presets": [
   ["@babel/preset-env", {
     "targets": {
        "browers": ["last 1 Chrome versions"]
     }
   }]
 ],
}

 // 编译完包含箭头函数 和 const、class 的文件后,发现编译后的文件和之前一样,那是因为最新版的Chrome
 // 已经支持该语法,也就不会编译了, 因此,@babel/preset-env 称为 JavaScript 的 Autoprefixer

历史背景:

  • 最初,为了让开发者能够尽早用上新的JS特性,babel团队开发了babel-preset-latest。这个preset比较特殊,它是多个preset的集合(es2015+),并且随着ECMA规范的更新更增加它的内容

  • 随着时间的推移,babel-preset-latest 包含的插件越来越多,这带来了如下问题

  1. 加载的插件越来越多,编译速度会越来越慢
  2. 随着用户浏览器的升级,ECMA规范的支持逐步完善,编译至低版本规范的必要性在减少(比如ES6 -> ES5),多余的转换不单降低执行效率,还浪费带宽。
  • 因为上述问题的存在,babel官方推出了babel-preset-env插件。它可以根据开发者的配置,按需加载插件。配置项大致包括:
  1. 需要支持的平台:比如node、浏览器等。
  2. 需要支持的平台的版本:比如支持node@6.1等。

@babel-polyfill

作用:

比如 IE 11 不支持 数组的 findIndex 这个方法,加入代码中写了 findIndex ,浏览器会报错, 如果使用 @bebel/polyfill ,他将会给 IE11 浏览器 添加如下 polyfill, 进而是 IE11 支持

// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
if (!Array.prototype.findIndex) {
  Object.defineProperty(Array.prototype, 'findIndex', {
    value: function(predicate) {
     // 1. Let O be ? ToObject(this value).
      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }

      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
      var thisArg = arguments[1];

      // 5. Let k be 0.
      var k = 0;

      // 6. Repeat, while k < len
      while (k < len) {
        // a. Let Pk be ! ToString(k).
        // b. Let kValue be ? Get(O, Pk).
        // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
        // d. If testResult is true, return k.
        var kValue = o[k];
        if (predicate.call(thisArg, kValue, k, o)) {
          return k;
        }
        // e. Increase k by 1.
        k++;
      }

      // 7. Return -1.
      return -1;
    }
  });
}

上面这段代码的意思是,如果目标环境中已经存在 findIndex,我们什么都不做,如果没有,我们就在 Array 的原型中定义一个。这便是 polyfill 的意义。babel-polyfill 同理。

背景介绍:

浏览器的特性支持情况千差万别,可以概括为两类:

  1. 大家都有,只是 A 语法与B 语法的区别(实现方式不同)
  2. 不是大家都有: 有的有,有的没有

babel 编译过程处理第一种情况,统一语法的形态,通常是高版本语法编译成低版本的,比如ES6 语法编译成 ES5 或 ES3。而babel-polyfill 处理第二种情况,让目标浏览器支持所有特性,不管它是全局的,还是原型的,或是其他。只要通过 babel-polyfill,不同浏览器在特性支持上就站到统一起跑线。

安装:

npm install --save @babel/polyfill 
// --save 表明 上线后也是需要用的,不像 babel 只在编译阶段使用

使用方法:

需要在程序入口文件的顶部引用,且不可多次引用,如果代码中多次引用,执行时会抛错, 这是因为引入的 babel-polyfill 会在全局写入一个 _babelPolyfill 变量。第二次引入时,会检测该变量是否已存在,如果已存在,则抛出错误。

only one instance of @babel/polyfill is allowed
import "@babel/polyfill";

注意:

babel-polyfill 其实是 regenerator runtimecore-js,如果你的代码只需要其中一部分 polyfill,那么你可以考虑直接引入 core-js 下特定 polyfill, 不必使用 babel-polyfill 这样的庞然大物。

另一种方法是配合 @babel/preset-envuseBuiltIns 配置。


babel-runtime

举个🌰:

如, IE11 不支持 Object.assign ,此时有两种方案:

  1. 引入 @bebel/polyfill ,补丁一打, Object.assign 就被创造出来
  2. 配置 @babel/plugin-transform-object-assign

第二种方案中,babel 会将所有的 Object.assign 替换成 _extends 这样一个辅助函数,但是,如果你的项目里面有 100 个文件,其中有 50 个里面写了 Object.assign ,那么,_extends 辅助函数会出现 50 次。

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

_extends({}, {}); 

// 如上代码 引用几次,就会出现几次

此时我们首先想到的就是,需要将 _extends 分离出去,然后在每一个文件中引入,那么这就是 @babel/runtime 的作用:

var _extends = require("@babel/runtime/helpers/extends");

_extends({}, {});

非常nice,但是上面代码块中的代码,是需要人为的去使用 bebel-runtime 去转换的,但是是没有人去手动转换的。

于是就会就出现了 @babel/plugin-transform-runtime 来替我们做这些转换。

@babel/plugin-transform-runtime

安装:

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

npm install @babel/runtime

配置:

{
 "plugins": [
   "@babel/plugin-transform-object-assign",
   "@babel/plugin-transform-runtime"
 ]
}

和@babel/polyfill 区别:

  1. @babel/polyfill 改造目标浏览器,让你的浏览器拥有本来不支持的特性;
  2. babel-runtime 改造你的代码,让你的代码能在所有浏览器上运行,但不改造浏览器。

babel-register

经过 babel 的编译后,我们的源代码与运行在生产下的代码是不一样的; 然而 babel-register 则提供一种动态编译,即可以让我们的 源代码(包含 新语法的 代码) 能真正运行在生产环境下,而不需要 经过 babel 的编译

安装:

npm install --save-dev @babel/register

使用:

require('@babel/register')
require('./app')

在入口文件中引用后, 我们的 app 文件中即可使用任意 es2015 的特性

弊端:

动态编译,导致程序在速度、性能上有所损耗


babel-node

我们上面说,babel-register 提供动态编译,能够让我们的源代码真正运行在生产环境下 - 但其实不然,我们仍需要做部分调整,比如新增一个入口文件,并在该文件中 require('@babel/register')。而 babel-node 能真正做到一行源代码都不需要调整:

npm install --save-dev @babel/core @babel/node
npx babel-node app.js

只是,请不要在生产环境中使用 babel-node,因为它是动态编译源代码,应用启动速度非常慢。


代码升级到 babel 7

—-> 请移步 babel 官网