2

一、 Babel的介绍

Babel是一个工具链,主要用于将ECMAScript 2015+版本的代码转化为向后兼容的javaScript语法,以便能运行在当前和旧版本的浏览器或其他环境中。
babel7的重要组成部分

  • @babel/cli
  • @babel/core
  • @babel/preset-env
  • @babel/polyfill
  • @babel/runtime
  • @babel/plugin-transform-runtime
  • @babel/plugin-transform-xxx

以上这些就是我们以后常常会使用的babel的各个重要部分了
这里要注意一下这个@这个符号,这个是只有babel7才特有的,这是 babel7 的一大调整,原来的 babel-xx 包统一迁移到babel域下,域由 @符号来标识。
下面我们来随便建一个项目,例如index.js里面写一行代码:

let fn = () => console.log('Hello World')

然后我们可以想将其搁置一边,一步步学习!

二、@babel/cli和@babel/core

@babel/cli是babel提供的内建的命令行工具,主要是提供babel这个命令来对js文件进行编译,适合安装在项目里。但是仅仅npm install @babel/cli再执行babel index.js 并不会执行成功。因为Babel 的核心功能包含在 @babel/core 模块中。所以需要安装 @babel/core
即执行:

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

这时候我们用babel来执行下我们上面的index.js

babel index.js

发现生成的代码并没有任何改变!看来还需要其他的配合,于是我们继续配置其他的选项!

三、@babel/preset-env

真正使Babel做实际工作的其实是插件。以上面的代码为例,我们想转化下箭头函数,我们需要插件 @babel/plugin-transform-arrow-functions。
于是输入命令行:

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

创建一个名为 .babelrc的配置文件,@babel/cli在调用之时都会去调用.babelrc文件
文件内容如下

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

再编译,会发现箭头函数已经变成非箭头函数了
但是我们还需要其他ES6的新特性也进行转化,比如要将const或者let转化成var,需要插件 @babel/plugin-transform-block-scoping,将class关键字转化成传统基于原型的,需要插件@babel/plugin-transform-classes。所以我们在使用ES6时需要配置大量的插件,这很显然不合理,于是babel提出了预设的概念----preset,其实就是预先为我们做好了一系列的插件包。

我们在.babelrc的配置文件里作如下配置:

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

注:targets是指定目标环境,该参数配置除了可以设置node环境外,还可以设置针对各个浏览器环境的配置。
按照上述配置,运行后发现就目前的代码是没有问题了!

四、@babel/polyfill

现在我们把index.js的代码改为如下:

const m = [1,2,3].findIndex(x => x == 1)

发现编译后的代码如下:

"use strict";

var m = [1, 2, 3].findIndex(function (x) {
  return x == 1;
});

此代码如果运行在低版本浏览器,一定会报错,因为低版本浏览器中数组实例上没有 includes 方法。这种情况的发生是因为语法转换只是将高版本的语法转换成低版本的,但是新的内置函数、实例方法无法转换。
这时 polyfill 登场了。

顾名思义,polyfill的中文意思是垫片,所谓垫片就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。

首先安装 @babel/polyfill 依赖:

npm install --save @babel/polyfill

我们需要在我们的代码里对 polyfill引入:

import '@babel/polyfill';
const m = [1,2,3].findIndex(x => x == 1)

这样代码可以在低版本运行了,但是我们未必需要加载所有的 polyfill,这会导致我们最终构建出的包的体积很大。为了解决这一问题,@babel/preset-env 提供了一个 useBuiltIns 参数,设置值为 usage 时,就只会包含代码需要的 polyfill 。
配置此参数的值为 usage ,必须要同时设置 corejs (如果不设置,会给出警告,默认使用的是"corejs": 2), 为了可以使用更多的新特性,建议大家使用 core-js@3。

于是我们在.babelrc的配置文件里作如下配置:

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

这样编译后的代码为:

import "core-js/modules/es.array.find-index";
var m = [1, 2, 3].findIndex(function (x) {
  return x == 1;
});

这样我们需要findIndex 函数就只需要打包polyfill里的findIndex函数

五、@babel/plugin-transform-runtime

现在我们将index.js的代码修改为:

class A {
    constructor (name) {
        this.name = name
    }
}
const a = new A()

被编译后的代码如下:

import "core-js/modules/es.function.name";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var A = function A(name) {
  _classCallCheck(this, A);

  this.name = name;
};

var a = new A();

现在试想如果多个js文件都有class,那么是不是每个被编译后的文件都会有_classCallCheck函数。为了解决这个问题,babel拥有了@babel/plugin-transform-runtime插件。使用 @babel/plugin-transform-runtime 插件,所有帮助程序都将引用模块 @babel/runtime,这样就可以避免编译后的代码中出现重复的帮助程序,有效减少包体积。

@babel/plugin-transform-runtime需要和@babel/runtime配合使用。

首先安装依赖,@babel/plugin-transform-runtime通常仅在开发时使用,但是运行时最终代码需要依赖@babel/runtime,所以@babel/runtime必须要作为生产依赖被安装,如下 :

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

修改 .babelrc 的配置,如下:

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

编译后的代码为:


"use strict";

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

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

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

var A = function A(name) {
  (0, _classCallCheck2["default"])(this, A);
  this.name = name;
};

var a = new A();

这时发现_classCallCheck2函数是通过引入的方式,而不是直接将函数插入到代码中。

现在我们说回findIndex函数,编译后会require("core-js/modules/es.array.find-index"),这种方式会修改Array.prototype,然后全局污染。为了防止这一问题,该插件还有一个作用,可以避免全局污染。当然我们需要给 @babel/plugin-transform-runtime 增加配置信息。
首先新增依赖 @babel/runtime-corejs3:

npm install @babel/runtime-corejs3 --save

修改配置文件

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

编译后生成的代码为


"use strict";

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

var _findIndex = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find-index"));

var _context;

var m = (0, _findIndex["default"])(_context = [1, 2, 3]).call(_context, function (x) {
  return x == 1;
});

如上面所示,这种就不会直接修改Array.prototype。避免全局污染。

结语

本人经常看到关于Babel配置的文章,但是没有特别清晰有体系的讲解,直到我看到了《不容错过的Babel7知识》。本文基本是按照文章作者的思路,自己捋了一遍。如果大家看到这篇文章,并且想有更深的理解,还请参考原作者的文章!
参考链接 不容错过的 Babel7知识


小葱
95 声望3 粉丝