Babel教程

一、Babel入门

1.1、Babel 简介

它是一个 JavaScript 编译器,它是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中下面举个简单例子

转换前,代码里使用ES6箭头函数以及使用const声明常量

  const fn = (num) => num + 2;

转换后,箭头函数变成ES5的普通函数,const也转换成var了,这样就可以在不支持箭头函数的浏览器里运行了

 var fn = function fn(num) {
    return num + 2;
  }

1.2、Babel快速入门

1.2.1、babel的安装、配置及转码

Babel依赖Node.js,没有安装的话,去官网下载安装最新稳定版本的Node.js。

在本地新建一个文件夹babel01,在该文件夹下新建一个js文件,文件命名为babel.config.js。该文件是 Babel配置文件 ,我们在该文件里输入如下内容:

  module.exports = {
    presets: ["@babel/env"],
    plugins: []
  }

然后在该文件夹下新建一个js文件main.js,该js里的代码是我们需要转译的,我们写入代码

  const helloFnc = ()=>{
    console.log("hello babel")
}

然后执行下面的命令安装三个npm包,这些npm包是Babel官方包

  // npm一次性安装多个包,包名之间用空格隔开
  npm install --save-dev @babel/cli @babel/core @babel/preset-env

安装完成后,执行下面的命令进行转码,该命令含义是把main.js转码生成compiled.js文件

  npx babel main.js -o dist.js   // npx是新版Node里附带的命令。它运行的时候默认会找到node_modules/.bin/下的路径执行。
  等同于下面
  node_modules/.bin/babel main.js -o dist.js

此时文件夹下会生成dist.js,该文件是转换后的代码:

"use strict";

var helloFnc = function helloFnc() {
  console.log("hello babel");
};

这就是一个最简单的Babel使用过程,我们把用ES6编写main.js转换成了ES5的dist.js。

1.2.2、Babel转码说明

babel.config.js是Babel执行时会默认在当前目录寻找的Babel配置文件。

除了babel.config.js,我们也可以选择用.babelrc或.babelrc.js这两种配置文件,还可以直接将配置参数写在package.json。它们的作用都是相同的,只需要选择其中一种。 我们将在另外一节详细介绍Babel的配置文件,接下来默认使用babel.config.js。

@babel/cli,@babel/core与@babel/preset-env是Babel官方的三个包,它们的作用如下:

  • @babel/cli是Babel命令行转码工具,如果我们使用命令行进行Babel转码就需要安装它
  • @babel/cli依赖@babel/core,因此也需要安装@babel/core这个Babel核心npm包
  • @babel/preset-env这个npm包提供了ES6转换ES5的语法转换规则,我们在Babel配置文件里指定使用它。如果不使用的话,也可以完成转码,但转码后的代码仍然是ES6的,相当于没有转码。

这些工具后续都会有单独的章节说明,现在先学会简单使用即可。

1.2.3、引入Polyfill

Babel的主要工作有两部分:语法转换和补充API

其实上一节中的就是语法转换(ES6=》ES5),那补充API是什么意思呢?

简单解释就是,通过 Polyfill 的方式在目标环境中添加缺失的特性 ,我们将刚才的例子中的main.js进行下修改(其他所有文件都不变):

  const helloFnc = ()=>{
    console.log("hello babel")
	}
  var promise = Promise.resolve('ok')  // 新增这一行promise的代码

我们再次执行npx babel main.js -o dist.js,我们再来看下此时生成的dist文件,发现新加的那句promise的语句并没有什么变化,

"use strict";

var helloFnc = function helloFnc() {
  console.log("hello babel");
};

var promise = Promise.resolve('ok');

这是为什么呢

因为babel 默认转换的是新的语法,而不是新的API,新的API分类两类,一类是Promise、Map、Symbol、Proxy、Iterator等全局对象及其对象自身的方法,例如Object.assign,Promise.resolve;另一类是新的实例方法,例如数组实例方法[1, 4, -5, 10].find((item) => item < 0)

有什么办法可以使新的API也一同转换呢

在前端web工程里,最常规的做法是使用polyfill,为当前环境提供一个垫片。所谓垫片,是指垫平不同浏览器之间差异的东西。polyfill提供了全局的ES6对象以及通过修改原型链Array.prototype等实现对实例的实现。

polyfill广义上讲是为环境提供不支持的特性的一类文件或库,狭义上讲是polyfill.js文件以及@babel/polyfill这个npm包

补齐API的方式有哪几种

  • 通过引入 polyfill.js 文件(本文介绍这种,但这种方式慢慢被淘汰了)
  • 在构建工具入口文件(例如webapck),babel配置文件等方式进行

1.3、Babel深入

1.3.1、关于babel版本

目前,前端开发领域使用的Babel版本主要的Babel6和Babel7这两个版本。

Babel是一个工具集,而这个工具集是围绕@babel/core这个核心npm包构成的。每次@babel/core发布新版本的时候,整个工具集的其它npm包也都会跟着升级到与@babel/core相同的版本号,即使它们的代码可能一行都没有改变;

因此,我们提到Babel版本的时候,通常是指@babel/core这个Babel核心包的版本

Babel7的npm包都是放在babel域下的,即在安装npm包的时候,我们是安装@babel/这种方式,例如@babel/cli、@babel/core等。而在Babel6,我们安装的包名是babel-cli,babel-core等。其实它们本质是一样的,都是Babel官方的cli命令行工具和core核心包,而且功能是一样的,只是名称版本变化了一下而已。在平时开发和学习的过程中,碰到’@babel/'和’babel-'应该下意识认识到他俩原本是一个包,只是版本不一样而已。

1.3.2、babel配置文件

配置文件:

Babel的配置文件是Babel执行时默认会在当前目录寻找的文件,主要有.babelrc,.babelrc.js,babel.config.js和package.json。它们的配置项都是相同,作用也是一样的,只需要选择其中一种。

对于.babelrc,它的配置是这样子

  {
    "presets": ["es2015", "react"],
    "plugins": ["transform-decorators-legacy", "transform-class-properties"]
  }

对于babel.config.js和.babelrc.js,它的配置是一样的,通过module.exports输出配置项

module.exports = {
    "presets": ["es2015", "react"],
    "plugins": ["transform-decorators-legacy", "transform-class-properties"]
  }

对于package.json,就是在package.json中增加一个babel属性和值,它的配置是这样子

{
    "name": "demo",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "",
    "babel": {
      "presets": ["es2015", "react"],
      "plugins": ["transform-decorators-legacy", "transform-class-properties"]
    }
  }

仔细观察上述几种配置文件,会发现它们的配置项其实都是pluginspresets,当然除了plugins和presets这两个配置项,还有minified、ignore等,但我们平时都用不到,大家还是把精力放在plugins和presets上。

插件和预设:

plugin代表插件,preset代表预设,它们分别放在plugins和presets,每个插件或预设都是一个npm包。

Babel通过Babel配置文件来指定编译的规则,所谓编译的规则,就是在配置文件里列出的编译过程中会用到的Babel插件或预设。这些插件和预设会在编译过程中把我们的ES6代码转换成ES5。

Babel插件的数量非常多,处理ES2015的有

@babel/plugin-transform-arrow-functions
@babel/plugin-transform-block-scoped-functions
@babel/plugin-transform-block-scoping
……
处理ES2018的有

@babel/plugin-proposal-async-generator-functions
@babel/plugin-transform-dotall-regex
……

Babel插件实在太多,如果我们把所有插件都写到配置项里,我们的Babel配置文件会非常臃肿,所以预设就解决了我们这个问题

预设是一组Babel插件的集合,用大白话说就是插件包,例如babel-preset-es2015就是所有处理es2015的二十多个Babel插件的集合。这样我们就不用写一大堆插件配置项了,只需要用一个预设代替就可以了。另外,预设也可以是插件和其它预设的集合。Babel官方已经对常用的环境做了一些preset包

  • @babel/preset-env
  • @babel/preset-react
  • @babel/preset-stage-0
  • @babel/preset-typescript
  • @babel/preset-stage-1 …

所有的预设也都需要先安装npm包到node_modules。

plugin与preset的短名称

如果插件的npm包名称的前缀为 babel-plugin-,可以省略前缀。例如

module.exports = {
    "presets": [],
    "plugins": ["babel-plugin-transform-decorators-legacy"]  //可以简写为 "plugins": ["transform-decorators-legacy"]
  }

目前Babel7的官方npm包里绝大部分插件已经升级为@babel/plugin-前缀的,这种情况的短名称比较特殊了,绝大部分可以像babel-plugin-那样省略@babel/plugin-。但babel官方并没有给出明确的说明,所以还是推荐用全称。

如果npm包名称的前缀带有npm作用域@,例如@org/babel-plugin-xxx,短名称可以写成@org/xxx。

预设npm包名称的前缀为babel-preset-或作用域@xxx/babel-preset-xxx的可以省略掉babel-preset-。

对于Babel7的官方npm包里绝大部分预设已经升级为@babel/preset-前缀的,这种情况的短名称比较特殊了,绝大部分可以像babel-preset-那样省略@babel/preset-。但babel官方并没有给出明确的说明,例如,@babel/preset-env的短名称就是@babel/env,所以还是推荐用全称。

plugins插件数组presets预设数组有顺序要求的。如果两个插件或预设都要处理同一个代码片段,那么会根据插件和预设的顺序来执行。规则如下:

  • 插件比预设先执行
  • 插件执行顺序是插件数组从前向后执行
  • 预设执行顺序是预设数组从后向前执行

Babel插件和预设的参数

每个插件是插件数组的一成员项,每个预设是预设数组的一成员项,默认情况下,成员项都是用字符串来表示的,例如"@babel/preset-env"。

如果要给插件或预设设置参数,那么成员项就不能写成字符串了,而要改写成一个数组。数组的第一项是插件或预设的名称字符串,第二项是个对象,该对象用来设置第一项代表的插件或预设的参数。例如给@babel/preset-env设置参数:

 {
    "presets": [
      [
        "@babel/preset-env",
        {
          "useBuiltIns": "entry"
        }
      ]
    ]
  }
1.3.3、babel预设和插件的选择

总结起来,Babel官方的preset,我们实际可能会用到的其实就只有4个(其他基本不用或者已经被废弃):

  • @babel/preset-env:对标准的ES6语法进行转换
  • @babel/preset-flow
  • @babel/preset-react
  • @babel/preset-typescript
1.3.4、babel-polyfill

babel-polyfill这是在Babel6中的名字,在Babel7以后名字是@babel/polyfill

前面大致讲了下polyfill的概念和作用,现在我们具体来讲下它

polyfill传统上分两类一类是已构建成JS文件的polyfill.js另一类是未构建的需要安装npm包@babel/polyfill。因为@babel/polyfill本质是由两个npm包core-jsregenerator-runtime组合而成的,所以在使用层面上还可以再细分为是引入@babel/polyfill本身还是其组合子包

总体来说,Babel官方的polyfill使用方法主要有如下几种:

  • 直接在html文件引入Babel官方的polyfill.js脚本文件;
  • 在前端工程的入口文件里引入polyfill.js;
  • 在前端工程的入口文件里引入@babel/polyfill;
  • 在前端工程的入口文件里引入core-js/stable与regenerator-runtime
  • 在前端工程构建工具的配置文件入口项引入polyfill.js;
  • 在前端工程构建工具的配置文件入口项引入@babel/polyfill;
  • 在前端工程构建工具的配置文件入口项引入core-js/stable与regenerator-runtime/runtime;

1、直接在html文件引入Babel官方的polyfill.js脚本文件

该方法在分类上属于使用已构建成JS文件polyfill.js的一类,该方法在引入polyfill一节已经讲过,本节不再重复讲解。

2、在前端工程的入口文件里引入polyfill.js

假设a.js是入口文件,在webpack配置文件中配置为entry,该方法就是在a.js文件顶层引入 import './polyfill.js';

3、在前端工程的入口文件里引入@babel/polyfill
直接将方法2中的 import './polyfill.js';改为import '@babel/polyfill';

4、在前端工程的入口文件里引入core-js/stable与regenerator-runtime/runtime

首先新安装以上两个包core-js和regenerator-runtime,然后将方法3中的import '@babel/polyfill';改为上述两个包的分开引入

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

5、在前端工程构建工具的配置文件入口项引入polyfill.js

将webpack配置入口处改为数组

module.exports = {
    entry: ['./polyfill.js', './a.js'],
    output: {
      filename: 'b.js',
      path: path.resolve(__dirname, '')
    },
    mode: 'development'
  };

6、在前端工程构建工具的配置文件入口里引入@babel/polyfill;

module.exports = {
    entry: ['@babel/polyfill', './a.js'],
    output: {
      filename: 'b.js',
      path: path.resolve(__dirname, '')
    },
    mode: 'development'
  };

7、在前端工程构建工具的配置文件入口里引入core-js/stable与regenerator-runtime/runtime

module.exports = {
    entry: ['core-js/stable', 'regenerator-runtime/runtime', './a.js'],
    output: {
      filename: 'b.js',
      path: path.resolve(__dirname, '')
    },
    mode: 'development'
  };

备注:从babel7.4开始,官方不推荐再使用@babel/polyfill了,因为@babel/polyfill本身其实就是两个npm包的集合:core-js与regenerator-runtime。
官方推荐直接使用这两个npm包。虽然@babel/polyfill还在进行版本升级,但其使用的core-js包为2.x.x版本,而core-js这个包本身已经发布到了3.x.x版本了,@babel/polyfill以后也不会使用3.x.x版本的包了。新版本的core-js实现了许多新的功能,例如数组的includes方法。

1.3.5、@babel/preset-env

在Babel快速入门一节,我们简单使用过@babel/preset-env的语法转换功能。除了进行语法转换,该预设还可以通过设置参数项进行针对性语法转换以及polyfill的部分引入。

我们重点要学习的参数项有targets、useBuiltIns、modules和corejs这四个

对于preset,在不需要设置参数时,我们直接把preset的名字放在对应preset的数组中即可

module.exports = {
    presets: ["@babel/env"],  //@babel/preset-env的简写
    plugins: []
  }

如果要给preset添加参数,则需要再包裹一层数组,数组第一项是该preset字符串,数组第二项是该preset的参数对象。如果该preset没有参数需要设置,则数组第二项可以是空对象或者不写第二项

module.exports = {
    presets: [["@babel/env", {}]],
    plugins: []
  }

如果你使用过vue或react的官方脚手架cli工具,你一定会在其package.json里看到browserslist项,下面该项配置的一个例子:

 "browserslist": [
    "> 1%",
    "not ie <= 8"
  ]

Browserslist叫做目标环境配置表,除了写在package.json里,也可以单独写在工程目录下.browserslistrc文件里

我们的Babel也可以使用browserslist,如果你使用了@babel/preset-env这个预设,此时Babel就会读取browserslist的配置

如果我们的@babel/preset-env不设置任何参数,Babel就会完全根据browserslist的配置来做语法转换。如果没有browserslist,那么Babel就会把所有ES6的语法转换成ES5版本

注意:Babel使用browserslist的配置功能依赖于@babel/preset-env,如果Babel没有配置任何预设或插件,那么Babel对转换的代码会不做任何处理,原封不动生成和转换前一样代码。

既然@babel/preset-env可以通过browserslist针对目标环境不支持的语法进行语法转换,那么是否也可以对目标环境不支持的特性API进行部分引用呢?这样我们就不用把完整的polyfill全部引入到最终的文件里,可以大大减少体积

答案是可以的,但需要对@babel/preset-env的参数项进行设置才可以,这个我们接下来讲

targets参数项

该参数项可以取值为字符串、字符串数组或对象,不设置的时候取默认值空对象{}。

  module.exports = {
    presets: [["@babel/env", {
      targets: {
        "chrome": "58",
        "ie": "11"
      }
    }]],
    plugins: []
  }

如果我们对@babel/preset-env的targets参数项进行了设置,那么就不使用browserslist的配置,而是使用targets的配置。如不设置targets,那么就使用browserslist的配置。如果targets不配置,browserslist也没有配置,那么@babel/preset-env就对所有ES6语法转换成ES5的。

正常情况下,我们推荐使用browserslist的配置而很少单独配置@babel/preset-env的targets。

useBuiltIns参数项:

useBuiltIns项取值可以是"usage" 、 “entry” 或 false。如果该项不进行设置,则取默认值false。

useBuiltIns这个参数项主要和polyfill的行为有关。在我们没有配置该参数项或是取值为false的时候,polyfill就是我们上节课讲的那样,会全部引入到最终的代码里。

useBuiltIns取值为"entry"或"usage"的时候,会根据配置的目标环境找出需要的polyfill进行部分引入。让我们看看这两个参数值使用上的不同。

我们指定目标环境是火狐58,同时设置useBuiltIns为”entry“,package.json里的browserslist设置如下:

 "browserslist": [
      "firefox 58"
    ]

然后要转换的代码还是这个a.js(入口文件)

 import '@babel/polyfill';
 var promise = Promise.resolve('ok');
 console.log(promise);

使用npx babel a.js -o b.js命令进行转码。

"use strict";
  require("core-js/modules/es7.array.flat-map");
  require("core-js/modules/es7.string.trim-left");
  require("core-js/modules/es7.string.trim-right");
  require("core-js/modules/web.timers");
  require("core-js/modules/web.immediate");
  require("core-js/modules/web.dom.iterable");
  var promise = Promise.resolve('ok');
  console.log(promise);

我们可以看到Babel针对火狐58不支持的API特性进行引用,一共引入了6个core-js的API补齐模块。同时也可以看到,因为火狐58已经支持Promise特性,所以没有引入promise相关的API补齐模块。你可以试着修改browserslist里火狐的版本,修改成版本26后,会引入API模块大大增多,有几十个。

我们再来看下useBuiltIns:"usage"的情况

这种方式不需要我们在入口文件(以及webpack的entry入口项)引入polyfill,Babel发现useBuiltIns的值是"usage"后,会自动进行polyfill的引入。

我们指定目标环境是火狐27,同时设置useBuiltIns:“usage”,package.json里的browserslist设置如下:

 "browserslist": [
      "firefox 27"
    ]

使用npx babel a.js -o b.js命令进行转码,转码后如下:

"use strict";
  require("core-js/modules/es6.promise");
  require("core-js/modules/es6.object.to-string");
  var promise = Promise.resolve('ok');
  console.log(promise);

观察转换的代码,我们发现引入的core-js的API补齐模块非常少,只有2个。为什么呢?

因为我们的代码里只使用了Promise这一火狐27不支持特性API,使用useBuiltIns:"usage"后,Babel除了会考虑目标环境缺失的API模块,同时考虑我们项目代码里使用到的ES6特性。只有我们使用到的ES6特性API在目标环境缺失的时候,Babel才会引入core-js的API补齐模块。

小结:'entry’这种方式不会根据我们实际用到的API进行针对性引入polyfill,而’usage’可以做到。另外,在使用的时候,'entry’需要我们在项目入口处手动引入polyfill,而’usage’不需要。

corejs参数项:

这个参数项只有useBuiltIns设置为’usage’或’entry’时,才会生效。

它的取目前可以是2或者3,没有设置时默认取2,这个取值其实代表着你使用什么版本的core-js,默认值或2的时候,Babel转码的时候使用的是core-js@2版本(即core-js2.x.x)。因为某些新API只有core-js@3里才有,例如数组的flat方法,我们需要使用core-js@3的API模块进行补齐,这个时候我们就把该项设置为3。

需要注意的是,corejs取值为2的时候,需要安装并引入core-js@2版本,或者直接安装并引入polyfill也可以(因为polyfill使用的core-js已经锁死为2.x.x版本了)。如果corejs取值为3,必须安装并引入core-js@3版本才可以,否则Babel会转换失败并提示:

  `@babel/polyfill` is deprecated. Please, use required parts of `core-js` and `regenerator-runtime/runtime` separately

modules参数项:

这个参数项的取值可以是"amd"、“umd” 、 “systemjs” 、 “commonjs” 、“cjs” 、“auto” 、false。在不设置的时候,取默认值"auto"。

该项用来设置是否把ES6的模块化语法改成其它模块化语法。

我们常见的模块化语法有两种:(1)ES6的模块法语法用的是import与export;(2)commonjs模块化语法是require与module.exports。

在该参数项值是’auto’或不设置的时候,会发现我们转码前的代码里import都被转码成require了。

如果我们将参数项改成false,那么就不会对ES6模块化进行更改,还是使用import引入模块。

使用ES6模块化语法有什么好处呢。在使用Webpack一类的打包工具,可以进行静态分析,从而可以做tree shaking 等优化措施。

1.3.6、@babel/plugin-transform-runtime以及@babel/runtime。

有时我们用@babel/preset-env预设做语法转换时,会引入很多个注入函数
,看个例子,下面这个是转换前的代码

 class Person {
    sayname() {
      return 'name'
    }
  }
  var john = new Person()
  console.log(john)

Babel转码后生成的代码如下:

  "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; }
  var Person = /*#__PURE__*/function () {
    function Person() {
      _classCallCheck(this, Person);
    }
    _createClass(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);
    return Person;
  }();
  var john = new Person();
  console.log(john);

可以看到转换后的代码上面增加了好几个函数声明,这就是注入的函数,我们称之为辅助函数。@babel/preset-env在做语法转换的时候,注入了这些函数声明,以便语法转换后使用。

但这么做有个问题就是在我们正常的前端工程开发的时候,少则几十个js文件,多则上千个。如果每个文件里都使用了class类语法,那会导致每个转换后的文件上部都会注入这些相同的函数声明。这会导致我们用构建工具打包出来的包非常大。

那么怎么办?一个思路就是,我们把这些函数声明都放在一个npm包里,需要使用的时候直接从这个包里引入到我们的文件里。这样即使上千个文件,也会从相同的包里引用这些函数。通过webpack这一类的构建工具打包的时候,我们只会把使用到的npm包里的函数引入一次,这样就做到了复用,减少了体积。

@babel/runtime就是上面说的这个npm包@babel/runtime把所有语法转换会用到的辅助函数都集成在了一起。

使用前,先安装下载这个包

npm install --save @babel/runtime

然后打开这个包的结构,可以看出里面含有好多个函数
在这里插入图片描述

_classCallCheck, _defineProperties与 _createClass这个三个辅助函数就在图片所示的位置,我们直接引入即可。

我们手动把辅助函数替换掉函数声明,之前文件的代码就变成如下所示:

  "use strict";
  var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
  var _defineProperties = require("@babel/runtime/helpers/defineProperties");
  var _createClass = require("@babel/runtime/helpers/createClass");
  var Person = /*#__PURE__*/function () {
    function Person() {
      _classCallCheck(this, Person);
    }
    _createClass(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);
    return Person;
  }();
  var john = new Person();
  console.log(john);

这样就解决了代码复用和最终文件体积大的问题。不过,这么多辅助函数要一个个记住并手动引入,平常人是做不到的,我也做不到。这个时候,Babel插件@babel/plugin-transform-runtime就来帮我们解决这个问题。

@babel/plugin-transform-runtime有三大作用,其中之一就是自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代。这样就减少了我们手动引入的麻烦。

先安装下载@babel/plugin-transform-runtime插件

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

然后将其配置到Babel配置文件中

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

然后再来看下刚才的例子,转换前a.js代码:

  class Person {
    sayname() {
      return 'name'
    }
  }
  var john = new Person()
  console.log(john)

执行"npx babel a.js -o b.js"命令后,转换生成的b.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"));
  var Person = /*#__PURE__*/function () {
    function Person() {
      (0, _classCallCheck2["default"])(this, Person);
    }
    (0, _createClass2["default"])(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);
    return Person;
  }();
  var john = new Person();
  console.log(john);

可以看到,它生成的代码比我们完全手动引入@babel/runtime里的辅助函数更加优雅。实际前端开发的时候,我们除了安装@babel/runtime这个包外,一定会安装@babel/plugin-transform-runtime这个Babel插件包的。

刚才讲到@babel/plugin-transform-runtime这个插件有三大作用:

1.自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代;

2.当代码里使用了core-js的API,自动引入@babel/runtime-corejs3/core-js-stable/,以此来替代全局引入的core-js/stable;

3.当代码里使用了Generator/async函数,自动引入@babel/runtime/regenerator,以此来替代全局引入的regenerator-runtime/runtime;

现在我们来学习下其他两个作用。。。。(作用2和3其实是在做API转换,对内置对象进行重命名,以防止污染全局环境。)

在前面学习了引入’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’来做全局的API补齐。但这样可能有一个问题,那就是对运行环境产生了污染。例如Promise,我们的polyfill是对浏览器的全局对象进行了重新赋值,我们重写了Promise及其原型链。

有时候我们不想改变或补齐浏览器的window.Promise,那么我们就不能使用’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’,因为其会对浏览器环境产生污染(即修改了浏览器的window.Promise)。

这个时候我们就可以使用@babel/plugin-transform-runtime,它可以对我们代码里ES6的API进行转换。还是以Promise举例子。

Babel转换前的代码

  var obj = Promise.resolve();

若使用了’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’来做全局的API补齐,那么Babel转换后的代码仍然是上面的代码,polyfill只是补齐了浏览器的window.Promise对象

但若我们不使用polyfill,而开启@babel/plugin-transform-runtime的API转换功能。那么Babel转换后的代码将会是下面这样

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
  var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
  var obj = _promise["default"].resolve();

@babel/plugin-transform-runtime把我们代码里的Promise变成了_promise[“default”],而_promise[“default”]拥有ES标准里Promise所有的功能。现在,即使浏览器没有Promise,我们的代码也能正常运行

1.4、常用的Babel工具

1.4.1、@babel/core

@babel/core是我们使用Bable进行转码的核心npm包,我们使用的babel-cli、babel-node都依赖这个包,因此我们在前端开发的时候,都需要安装这个包

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

@babel/cli是一个npm包,安装了它之后,我们就可以在命令行里使用命令进行转码了。

它有两种安装方式:全局安装和本地安装(推荐)

全局安装可以直接使用babel XXXXX进行语法转换

  # @babel/cli如果是全局安装的
  babel a.js -o b.js

本地安装要使用npx babel XXXX

  # @babel/cli如果是本地安装的
  npx babel a.js -o b.js
1.4.3、babel-loader

babel-loader是用于webpack的一个loader,以便webpack在构建的时候用Babel对JS代码进行转译,这样我们就不用再通过命令行手动转译了。我们在配置该loader的时候需要先安装它:

npm install babel-loader

在webpack配置文件中,我们把babel-loader添加到module的loaders列表中:

 module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }

在这里,我们通过options属性给babel-loader传递预设和插件等Babel配置项。我们也可以省略这个options,这个时候babel-loader会去读取默认的Babel配置文件,也就是.babelrc,.babelrc.js,babel.config.js等。在现在的前端开发中,建议通过配置文件来传递这些配置项。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值