Skip to content

Latest commit

 

History

History
422 lines (298 loc) · 15.3 KB

File metadata and controls

422 lines (298 loc) · 15.3 KB

Babel原名叫做6to5,顾名思义,它的使命就是把ES6的代码编译成ES5,使得我们可以使用ES6的新特性与语法编写代码。

针对ES6的每一种新语法特性,Babel都提供了对应的插件以便将使用该语法的代码向下编译。比如ES6中新增了箭头函数语法,Babel提供了对应的插件transform-es2015-arrow-functionsBabel提供了几十种插件用以将ES6和ES7的诸多语法转换成ES5等语言。可以单独引入某几种插件使用,Babel还将多种插件组合成一组,形成一个预设(preset),比如将ES2015相关的插件组合成了es2015这个预设,在使用的时候只需要引入es2015这个preset,就可以玩转ES2015所有的语法糖。

babel-cli

使用Babel最简单的方式就是通过babel-cli,通过babel-cli可以在命令行中运行Babel。

我们先通过npm全局安装它来学习基础知识:

npm install -g babel-cli

我们在文件example.js中编写如下代码:

const square = n => n * n;

我们在命令行中执行:

babel example.js -o compolied-example.js

可以将example.js转换为ES5代码,使用--out-file 或着 -o 可以将结果写入到指定的文件。

如果我们想要把一个目录整个编译成一个新的目录,可以使用--out-dir或者-d

当我们打开生成的compolied-example.js时,你会惊奇地发现代码没有任何变化。这是因为默认情况下,我们没有告诉Babel如何编译我们的代码,所以Babel只是对代码进行了拷贝,没有对代码进行任何编译。

我们可以通过--plugins参数告诉Babel使用哪些插件对代码进行编译,比如我们想用箭头函数插件,那么我首先要在当前目录下通过npm安装该插件:

npm install babel-plugin-transform-es2015-arrow-functions

然后通过CLI执行:

babel example.js -o compolied-example.js --plugins transform-es2015-arrow-functions

编译后的代码为:

const square = function (n) {
    return n * n;
};

如果我们的源文件中使用了多个ES6语法特性,那么我们可以通过--plugins参数指定多个插件,插件名字之间用逗号分隔,比如以下命令可以同时执行箭头函数插件和解构插件:

babel example.js -o compolied-example.js --plugins transform-es2015-arrow-functions,transform-es2015-destructuring

当源码使用了更多ES6语法特性的时候,通过--plugins参数指定插件的方式显得捉襟见肘。为此,Babel还将多种插件组合成一组,形成一个预设(preset),所谓预设就是能满足一类功能的插件的集合。比如将ES2015相关的所有插件组合成了es2015这个预设,在使用的时候只需要引入es2015这个preset,就可以玩转ES2015所有的语法糖。

通过--presets参数可以指定要使用的预设,如果我们要使用es2015这个预设,那首先要在当前目录中通过npm安装它:

npm install babel-preset-es2015

然后在CLI中执行:

babel example.js -o compolied-example.js --presets es2015

也可以通过--presets参数指定多个预设,预设名字之间用逗号分隔。

还可以设置参数--ignore [regex]-i [regex],不会对指定正则表达式文件进行编译,默认值是node_modules,即默认情况下不会对node_modules下的文件进行编译。

还可以设置参数--extensions-x,指定只对特定后缀名的文件进行编译,默认值是".js",".jsx",".es6",".es",即默认会对这些后缀名的文件进行编译。

在项目中使用Babel

尽管你可以把 Babel CLI 全局安装在你的机器上,但是按项目逐个安装在本地会更好。

有两个主要的原因。

  1. 在同一台机器上的不同项目或许会依赖不同版本的 Babel 并允许你有选择的更新。

  2. 这意味着你对工作环境没有隐式依赖,这让你的项目有很好的可移植性并且易于安装。

因为全局运行 Babel 通常不是什么好习惯所以如果你想要卸载全局安装的 Babel 的话,可以运行:

npm uninstall --g babel-cli

在项目的src目录下存放着使用了ES6语法的多个源文件,目录结构如下所示:

src
  main.js
  Shape.js
  Circle.js
  Rectangle.js

Shape.js定义了Shape类,Circle.jsRectangle.js分别定义了CircleRectangele类,并且都继承自Shape类,main.js使用了这几个类。

要在项目本地安装 Babel CLI 可以运行:

npm install --save-dev babel-cli

然后运行命令安装es2015预设:

npm install --save-dev babel-preset-es2015

安装完成后,我们项目中的package.json如下所示:

{
    "name": "babel-basic-use",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {},
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "babel-cli": "^6.18.0",
        "babel-preset-es2015": "^6.18.0"
    }
}

在上文中我们通过--presets--plugins运行babel-cli,在项目中一般通过配置.babelrc文件的方式配置Babel。

我们在项目的根目录下放置了.babelrc文件,内容如下所示:

{
    "presets": ["es2015"],
    "plugins": []
}

可以通过presets数组和plugins数组配置要运行的预设和插件。

需要注意的是,Babel会首先运行plugins中的插件,然后再运行presets中的预设。plugins中插件的运行顺序是从头到尾,而presets中预设的运行顺序则是从尾到头。比如.babelrc存在配置如下:

{
    "presets": ["preset1", "preset2"],
    "plugins": ["plugin1", "plugin2"]
}

那么Babel运行的顺序是:

plugin1 -> plugin2 -> preset2 -> preset1

npm的package.json中有一个scripts对象字段,我们可以在其中定义通过npm可以运行的script,我们向其中添加build脚本,用Babel对src目录进行编译,如下所示:

"scripts": {
    "build": "babel src -d buildOutput"
}

这样,在项目的根目录下执行npm run build即可启动Babel对源码进行编译,输出到buildOutput目录下。

我们以src目录下的Circle.js对比编译前后的区别,src/Circle.js源码如下所示:

import Shape from './Shape';

class Circle extends Shape{
  constructor(radius){
    super();
    this.radius = radius;
  }

  getArea(){
    return Math.PI * this.radius * this.radius;
  }

  getLength(){
    return 2 * Math.PI * this.radius;
  }

  toString(){
    return `Circle radius:${this.radius}, Circle area:${this.getArea()}, Circle length:${this.getLength()}`;
  }
}

export default Circle;

在该文件中我们使用了ES6中的importclassextends等基于类定义的语法。

编译后的buildOutput/Circle.js的代码如下所示:

'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _Shape2 = require('./Shape');

var _Shape3 = _interopRequireDefault(_Shape2);

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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Circle = function (_Shape) {
  _inherits(Circle, _Shape);

  function Circle(radius) {
    _classCallCheck(this, Circle);

    var _this = _possibleConstructorReturn(this, (Circle.__proto__ || Object.getPrototypeOf(Circle)).call(this));

    _this.radius = radius;
    return _this;
  }

  _createClass(Circle, [{
    key: 'getArea',
    value: function getArea() {
      return Math.PI * this.radius * this.radius;
    }
  }, {
    key: 'getLength',
    value: function getLength() {
      return 2 * Math.PI * this.radius;
    }
  }, {
    key: 'toString',
    value: function toString() {
      return 'Circle radius:' + this.radius + ', Circle area:' + this.getArea() + ', Circle length:' + this.getLength();
    }
  }]);

  return Circle;
}(_Shape3.default);

exports.default = Circle;

可以看到,编译后的文件中importclassextends等关键词不见了,原有的ES6类文件被默认编译成了CommonJS模块。

CommonJS模块适合于Node.js环境中同步require使用,不适用于前端浏览器环境。我们想把ES6类文件编译成AMD模块,为此我们需要安装AMD相关的plugin:

npm install --save-dev babel-plugin-transform-es2015-modules-amd

然后将AMD插件添加到.babelrcplugins数组中,如下所示:

{
  "presets": ["es2015"],
  "plugins": ["transform-es2015-modules-amd"]
}

然后重新运行npm run build,新生成的buildOutput/Circle.js的代码如下所示:

define(['exports', './Shape'], function (exports, _Shape2) {
  'use strict';

  Object.defineProperty(exports, "__esModule", {
    value: true
  });

  var _Shape3 = _interopRequireDefault(_Shape2);

  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");
    }
  }

  var _createClass = 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);
      }
    }

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

  function _possibleConstructorReturn(self, call) {
    if (!self) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return call && (typeof call === "object" || typeof call === "function") ? call : self;
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  }

  var Circle = function (_Shape) {
    _inherits(Circle, _Shape);

    function Circle(radius) {
      _classCallCheck(this, Circle);

      var _this = _possibleConstructorReturn(this, (Circle.__proto__ || Object.getPrototypeOf(Circle)).call(this));

      _this.radius = radius;
      return _this;
    }

    _createClass(Circle, [{
      key: 'getArea',
      value: function getArea() {
        return Math.PI * this.radius * this.radius;
      }
    }, {
      key: 'getLength',
      value: function getLength() {
        return 2 * Math.PI * this.radius;
      }
    }, {
      key: 'toString',
      value: function toString() {
        return 'Circle radius:' + this.radius + ', Circle area:' + this.getArea() + ', Circle length:' + this.getLength();
      }
    }]);

    return Circle;
  }(_Shape3.default);

  exports.default = Circle;
});

可以看到编译后的Circle.js中在代码首行就通过define引入了依赖Shape,输出结果是一个AMD模块。

常见预设和插件

Babel官方提供了如下七种预设:

  • es2015 该预设用于编译ES2015新语法。

  • es2016 该预设用于编译ES2016新语法。

  • es2017 该预设用于编译ES2017新语法。

  • latest 该预设会包含从2015年之后每年ECMAScript版本的新语法,目前包含es2015、es2016、es2017,只需要引入latest预设,就无需再分别引入以上三个预设。

  • react 该预设用于编译react的JSX语法。

  • flow 该预设只是用来删除掉代码中的类型声明,在以后的文章中会进行介绍。

  • env 该预设根据项目需要支持的环境自动选择Babel插件。

我们此处只列举部分插件:

...

通过本文,我们应该掌握了使用Babel的基础知识。