webpackH5

babel 7 的使用的个人理解

2018-11-08  本文已影响1509人  zshawk1982

babel 7 的使用的个人理解

最近看了很多关于babel的使用方法,大部分在一些点上都没有说明白,同时给出的代码也很难再现,为了能够理解和防止以后自己遗忘,特写此文。
首先我们要明白babel存在的意义,babel的目的就是为了解决浏览器的自身对于es语言的差异性而带来的一款工具,有了babel就首先不用担心浏览器不支持es语言这件事(当然现在的浏览器尤其是chrome对es6的支持越来越好),其实最重要的不是支持,而是解决差异性,这种差异性不仅介于浏览器之间,对于node这样的环境也会存在这样的问题,各个node版本对于es的支持,或者对于es的一些尚未提交的草案的支持都是不同的,所以不论是浏览器下还是node下都需要到使用babel的场景。

我这里就以node下的使用为例,来介绍babel的具体使用
首先在node项目里创建的index.js文件:

console.log([1,2,3].findIndex(x => x == 4))

console.log('abc'.padStart(10)) 

const alertMe = (msg) => {
    console.log(msg)
}
class Robot {
    constructor (msg) {
        this.message = msg
    }
    say () {
        alertMe(this.message)
    }
}
const marvin = new Robot('hello babel')
marvin.say()

这段代码很简单,没有特别含义,主要是测试babel对es6下的转义能力
在具体使用babel之前,必须介绍一下babel的各个组成部分,注意这里我们使用babel7

@babel/cli

@babel/cli是babel提供的内建的命令行工具,主要是提供babel这个命令来对js文件进行编译,这里要注意它与另一个命令行工具@babel/node的区别,首先要知道他们二者都是命令行工具,但是官方文档明确对他们定义了他们各自的使用范围:
@babel/cli 是一个适合安装在本地项目里,而不是全局安装

While you can install Babel CLI globally on your machine, it's much better to install it locally project by project.

@babel/node 跟node cli类似,不适用在产品中,意味着适合全局安装

babel-node is a CLI that works exactly the same as the Node.js CLI
You should not be using babel-node in production

除了这个不同以外,他们还有一个共同点就是,直接使用它们编译,上来就会报个错(找不到@babel/core),原因是@babel/cli是依赖一个叫@babel/core的包文件的,没有这个包文件是绝对不能执行任何编译操作的(因为执行编译的transform方法在这个包里),好吧,那我们就赶紧npm install --save @babel/core,接着再来执行babel编译,结果!结果是编译了之后都不会产生任何的编译效果,例如下面这个箭头函数所在的文件:test.js

let fun = () => console.log('hello babel')

我们在安装了@babel/cli或者@babel/node之后
使用@babel/cli编译
$ babel test.js
使用@babel/node编译
$ babel-node test.js
两个的编译结果都是该文件无任何变化
这个问题的发生来自 babel 6 。Babel 6 做了大量模块化的工作,将原来集成一体的各种编译功能分离出去,独立成插件。这意味着,默认情况下,当下版本的 babel 不会编译代码。

这里就扯淡了。。。你不能将箭头函数编译成es5,那搞个毛呀。。。

好吧,既然安装了@babel/core,安装了@babelb/cli这两个还是不行,那就说明它还需要别人配合,这也就是所谓的光有刀(@babel/core,@babelb/cli)不行,还得有料(@babel/plugin-transform-xxx)才行,一堆配合前两者,真正发挥作用的插件(@babel/plugin-transform-xxx)就登场了。
例如我们要将上面的箭头函数编译,我们需要这个插件
@babel/plugin-transform-arrow-functions
npm install --save-dev @babel/plugin-transform-arrow-functions
在安装这个插件之后,再次使用babel命令行工具@babel/cli
babel test.js --plugins @babel/plugin-transform-arrow-functions
结果是箭头函数成功的被成功编译成了:

let fun = function () {
     return console.log('hello babel');
};

这说明要使用各种真正的编译功能是需要配合各种插件的,要将箭头函数编译成普通函数需要使用@babel/plugin-transform-arrow-functions,要将const或者let变量编译成var变量需要@babel/plugin-transform-block-scoping,要将class关键字转化成传统基于原型的类需要@babel/plugin-transform-classes,所以为了真正的编译我们是可能需要大量各种的插件的,具体插件有哪些请点这里

使用插件是可以编译了,但是大量的使用插件会产生两个问题:
{
  "plugins": [
    "@babel/plugin-transform-arrow-functions"
    "@babel/plugin-transform-block-scoping" 
  ]
}

这样我们就可以将代码编译成下面的样子:

var fun = function () {
  return console.log('hello babel');
};

这样第一个问题就解决了,好了第二个问题是什么了?

注意呀,除了这些插件包外,还有很多别的哦,这些只是常用的而已

我们这里主要介绍这个叫@babel/preset-env的插件包
我们还是通过示例来研究研究:
代码是什么了,是我们在文章之初就创建的那个index.js文件,里面可有大量es6的语法代码,那么我们在安装了@babel/preset-env,并且在.babelrc中配置了@babel/preset-env之后

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "4"
        }
      }
    ]
  ]
}

这时候运行babel index.js之后,我们就可以带到index.js被编译之后的结果:

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

console.log([1, 2, 3].findIndex(function (x) {
  return x == 4;
}));
console.log('abc'.padStart(10));

var alertMe = function alertMe(msg) {
  console.log(msg);
};

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    _classCallCheck(this, Robot);

    this.message = msg;
  }

  _createClass(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);

  return Robot;
}();

var marvin = new Robot('hello babel');
marvin.say();

妈呀,吓死哥哥了,居然产生了这么多鬼东西,我不要看这些,我要看es6

这说明在node下我们成功将es6的代码转化成了编译后的js代码。
这时候我们再回过头来看@babel/preset-env这个插件包

Without any configuration options, babel-preset-env behaves exactly the same as babel-preset-latest (or babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017 together).

默认情况下,babel-preset-env 等效于三个插件包,而不巧我们前面提到的单个的插件已经囊括在 babel-preset-es2015 中。
当然现在编译结果已经出来了,但是马上就有眼尖的同学发现新的问题,

{
 "targets": {
      "node": "4"
  }
}

这个是什么鬼?这个targets实际上是针对上面的@babel/preset-env这个插件包的一个配置参数,它所代表的是你编译代码所针对的目标平台,我们这里的目标是版本号为4的node(友情提示:node -v 命令可以检查node的版本),也就是我编译之后的代码能够在node版本号为4的环境下运行,同样大家可以做个试验,如果将node这个4改为6,再次编译,你会发现编译之后的代码和编译之前的代码没有任何变化,这表明原始的代码实际上已经可以直接在版本为6的node上直接运行,不需要babel的编译了。
当然这里的targets参数配置除了可以设置node环境外,还可以设置针对各个浏览器环境的配置,例如:

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

当然针对浏览器差异的设置比node更多更灵活和复杂,感兴趣的同学可以点这里看所有的配置参数信息。
好了,第一个问题解决了,我们可以引出第二个问题。

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

我对polyfill的理解:polyfill我们又称垫片,见名知意,所谓垫片也就是垫平不同浏览器或者不同环境下的差异,因为有的环境支持这个函数,有的环境不支持这种函数,解决的是有与没有的问题,这个是靠单纯的@babel/preset-env不能解决的,因为@babel/preset-env解决的是将高版本写法转化成低版本写法的问题,因为不同环境下低版本的写法有可能不同而已。

所以我们要使用node命令正常运行编译后的index.js,那么要在编译后的代码里加上require('@babel/polyfill')这句

"use strict";
require('@babel/polyfill')
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); return Constructor; }

console.log([1, 2, 3].findIndex(function (x) {
  return x == 4;
}));
console.log('abc'.padStart(10)); 

var alertMe = function alertMe(msg) {
  console.log(msg);
};

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    _classCallCheck(this, Robot);

    this.message = msg;
  }

  _createClass(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);

  return Robot;
}();

var marvin = new Robot('hello babel');
marvin.say();

再次执行node命令,就可以看到结果了


QQ截图20181108223713.png

再介绍完以上的babel包之后我们还剩最后两个包给大家介绍一下,分别是:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "4"
        }
      }
    ]
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}

这次再次使用babel命令编译index.js就可以得到以下结果:

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

console.log([1, 2, 3].findIndex(function (x) {
  return x == 4;
}));
console.log('abc'.padStart(10)); 

var alertMe = function alertMe(msg) {
  console.log(msg);
};

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

  (0, _createClass2.default)(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);
  return Robot;
}();

var marvin = new Robot('hello babel');
marvin.say();

注意:我们这里要在编译结果后人为添加require('@babel/polyfill')之后才可以用node命令去正常执行index.js

重点在:

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

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

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

这一类helper已经是被从@babel/runtime包require进来了,这都是@babel/runtime的功劳,但是事情还没完,我们还有个包@babel/plugin-transform-runtime没提到就用了,这个包的作用其实就是辅助@babel/runtime的,因为有了@babel/plugin-transform-runtime它会帮我自动动态require @babel/runtime中的内容,如果没有这个@babel/plugin-transform-runtime,那么我们要使用@babel/runtime中的内容,就只有像require('@babel/polyfill')那样人工去手动添加了,所以@babel/plugin-transform-runtime非常方便,由于@babel/plugin-transform-runtime是一个插件,所以它是需要配置到.babelrc中的,这一点要记住。

好了自此关于babel的知识就暂时告一段落,关于babel的内容肯定还有很多很多,但常用的这些相信也够理解它的主要意义和基本使用了。

最后放上本人在gitee上的代码例子:点这里
友情提示:使用npm run build来编译哦,使用node命令执行lib/index.js就ok了

上一篇下一篇

猜你喜欢

热点阅读