babel及其使用


theme: smartblue

什么是Babel?

Babel 是一个工具链,由大量的工具包组成,接下来我们逐步了解。主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

核心库 @babel/core

Babel 的核心功能包含在 @babel/core 模块中。看到 core 这个词了吧,意味着核心,没有它,在 babel 的世界里注定寸步难行。不安装 @babel/core,无法使用 babel 进行编译。

babel-core 的作用是把 js 代码分析成 ast(抽象语法树) ,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js。

CLI命令行工具 @babel/cli

babel 提供的命令行工具,主要是提供 babel 这个命令
image.png

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

实例代码:

const fn = () => {
    console.log('a');
s};

package.json中配置:

"scripts": {
    "build": "babel src --out-dir lib --watch"
  },

执行babel命令后发现输出还是一模一样,并没有转换为es5语法。因为babel想要做一些实际的事情就需要配置plugin。此实例中需要箭头函数的转换插件:
babel.config.json中配置

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

最终输出了:

const fn = function () {
  console.log('a');
};

现在,我们仅支持转换箭头函数,而现如今js语法已经发展到了es13,如果想将其它的新的JS特性转换成低版本,需要使用其它对应的 plugin 。如果我们一个个配置的话,会非常繁琐,这时就需要简化配置

预设

通过预设可以让你轻松使用es新语法!!!
官方针对常用的环境编写了一些 preset,如下所示:

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-react
  • @babel/preset-typescript

这几个 preset 是社区维护的,可以通过 npm 命令下载。我们可以根据项目需要来下载使用,例如一个普通的 vue 项目,Babel 的官方预设中,只需要配一个 @babel/preset-env 就可以啦。

@babel/preset-env 会根据你配置的目标环境,生成插件列表来编译。对于基于浏览器的项目,官方推荐使用 .browserslistrc 文件来指定目标环境

browserslist

根据提供的目标浏览器的环境来,智能添加css前缀,js的polyfill垫片,来兼容旧版本浏览器,而不是一股脑的添加。避免不必要的兼容代码,以提高代码的编译质量。

设置语法: 通过浏览器过滤的思路实现
默认是兼容所有最新版本

例子 说明
> 1% 全球超过1%人使用的浏览器
> 5% in US 指定国家使用率覆盖
last 2 versions 所有浏览器兼容到最后两个版本根据CanIUse.com追踪的版本
Firefox ESR 火狐最新版本
Firefox > 20 指定浏览器的版本范围
not ie <=8 方向排除部分版本
Firefox 12.1 指定浏览器的兼容到指定版本
unreleased versions 所有浏览器的beta测试版本
unreleased Chrome versions 指定浏览器的测试版本
since 2013 2013年之后发布的所有版本

“browserslist”: [ “>0.2%”, “not dead”, “last 2 versions” ]

这个组合查询的意思是,筛选 最新两个版本、还存活着的、且用户比例大于0.2%  的浏览器

  • 之所以要组合查询,而不单独使用>0.2%,是为了避免流行的浏览器越来越流行,避免出现当初IE6一样的垄断情况。 (有竞争才有进步)
  • 只有当你专门针对某个浏览器(如Chrome)时 ,才直接使用last 2 Chrome versions。 如果不是针对某个浏览器,不要使用这种写法。我们应该尊重所有为用户体验努力的浏览器厂商。
  • 不要因为你不认识某个浏览器,而把它从browerslist配置中去除。

@babel/preset-env

@babel/preset-env 主要作用是对我们所使用的并且目标浏览器中缺失的功能进行代码转换和加载 polyfill,在不进行任何配置的情况下,@babel/preset-env 所包含的插件将支持所有最新的JS特性(ES2015,ES2016等,不包含 stage 阶段),将其转换成ES5代码。如果你不是要兼容所有的浏览器和环境,推荐你指定目标环境,这样你的编译代码能够保持最小。

例如,仅包括浏览器市场份额超过0.25%的用户所需的 polyfill 和代码转换

//.browserslistrc
> 0.25%
not dead

我们修改下 src/index.js

const isHas = [1,2,3].includes(2);

const p = new Promise((resolve, reject) => {
    resolve(100);
});

编译出来的结果为:

"use strict";

var isHas = [1, 2, 3].includes(2);
var p = new Promise(function (resolve, reject) {
  resolve(100);
});

这个编译出来的代码在低版本浏览器中使用的话,显然是有问题的,因为低版本浏览器中数组实例上没有 includes 方法,也没有 Promise 构造函数。

这是为什么呢?因为语法转换只是将高版本的语法转换成低版本的,但是新的内置函数、实例方法无法转换。这时,就需要使用 polyfill 上场了,顾名思义,polyfill的中文意思是垫片,所谓垫片就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。

Polyfill

Babel对于语法的转换,有两种情况需要处理:

  • 语法层: let、const、箭头函数、class等,这些需要在构建时进行转译,是指在语法层面上的转译
  • api方法层:Promise、includes、map等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义。

对于api方法层,需要用到Polyfill。在Babel 7.4.0版本之前,使用的方案是在项目入口处直接引入

@babel/polyfill 模块包括 core-js 和一个自定义的 regenerator runtime 模块,可以模拟完整的 ES2015+ 环境

这意味着可以使用诸如 PromiseArray.fromObject.assign 之类的静态方法、Array.prototype.includes 之类的实例方法。为了添加这些功能,polyfill 将添加到全局范围中(会对全局环境造成污染)。

babel v7.4版之后,@babel/polyfill已经被废弃,需要直接安装core-js 和 regenerator-runtime去替代之前的@babel/polyfillcroe-js 提供了 ES5、ES6 规范中新定义的各种对象、方法的polyfill,regenerator-runtime 用来实现 ES6/ES7 中 generators、yield、async 及 await 等相关的 polyfill。

在项目入口处引入:

import "@bable/polyfill";
// 引用"@bable/polyfill"相当于引用了下面这两个库,是等价的
import "core-js/stable"; 
import "regenerator-runtime/runtime";

// ...项目代码

很多时候,我们未必需要完整的 @babel/polyfill,这会导致我们最终构建出的包的体积增大,并且会造成全局环境污染。

我们更期望的是,如果我使用了某个新特性,再引入对应的 polyfill,避免引入无用的代码。

值得庆幸的是, Babel 已经考虑到了这一点。

@babel/preset-env参数

@babel/preset-env 中与 @babel/polyfill 的相关参数有 targets 和 useBuiltIns 两个

targets:  支持的目标浏览器的列表

useBuiltIns:  参数有 “entry”、”usage”、false 三个值。默认值是false,此参数决定了babel打包时如何处理@babel/polyfilll 语句。

“entry”: 会将文件中 import‘@babel/polyfilll’语句 结合 targets ,转换为一系列引入语句,去掉目标浏览器已支持的polyfilll 模块,不管代码里有没有用到,只要目标浏览器不支持都会引入对应的polyfilll 模块。

“usage”: 不需要手动在代码里写import‘@babel/polyfilll’,打包时会自动根据实际代码的使用情况,结合 targets 引入代码里实际用到 部分 polyfilll 模块

false: 对 import‘@babel/polyfilll’不作任何处理,也不会自动引入

实例源代码:

const isHas = [1,2,3].includes(2);

const p = new Promise((resolve, reject) => {
    resolve(100);
});

构建后代码:

"use strict";

require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
var isHas = [1, 2, 3].includes(2);
var p = new Promise(function (resolve, reject) {
  resolve(100);
});

总结: 在业务项目中需要用到polyfill时, 可以使用和 @babel/preset-env 的 targets 和 useBuiltIns: usage来根据目标浏览器的支持情况,按需引入用到的 polyfill 文件。

有一点需要注意:配置此参数的值为 usage ,必须要同时设置 corejs (如果不设置,会给出警告,默认使用的是”corejs”: 2) 。

corejs@2已停止维护,所有新的功能添加到corejs@3中(例如:Array.prototype.flat、Array.prototype.flatMap、Symbol.prototype.description等)

core-js (点击了解更多) : JavaScript 的模块化标准库,包含 PromiseSymbolIterator和许多其他的特性,它可以让你仅加载必需的功能。

@babel/plugin-transform-runtime

一个插件,可以重用 Babel 注入的帮助程序代码以节省代码大小。以模块化方式包含功能实现的包。

假如,我们的 src/index.js 是这样的:

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    };
    getX() {
        return this.x;
    }
}

let cp = new ColorPoint(25, 8);
复制代码

编译出来的 lib/index.js,如下所示:

"use strict";

require("core-js/modules/es.object.define-property.js");
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); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
var Point = /*#__PURE__*/function () {
  function Point(x, y) {
    _classCallCheck(this, Point);
    this.x = x;
    this.y = y;
  }
  _createClass(Point, [{
    key: "getX",
    value: function getX() {
      return this.x;
    }
  }]);
  return Point;
}();
var cp = new ColorPoint(25, 8);

我们联想到如果多个文件中引用了该class,是不是每次都要注入这些方法,显而易见包的体积会变大

这个时候,就是 @babel/plugin-transform-runtime 插件大显身手的时候了,使用 @babel/plugin-transform-runtime 插件,所有帮助程序都将引用模块 @babel/runtime,这样就可以避免编译后的代码中出现重复的帮助程序,有效减少包体积。

@babel/plugin-transform-runtime 是一个可以重复使用 Babel 注入的帮助程序,以节省代码大小的插件。
另外,@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

除了前文所说的,@babel/plugin-transform-runtime 可以减少编译后代码的体积外,我们使用它还有一个好处,它可以为代码创建一个沙盒环境,如果使用 @babel/polyfill 及其提供的内置程序(例如 PromiseSetMap ),则它们将污染全局范围。虽然这对于应用程序或命令行工具可能是可以的,但是如果你的代码是要发布供他人使用的库,或者无法完全控制代码运行的环境,则将成为一个问题。

@babel/plugin-transform-runtime 会将这些内置别名作为 core-js 的别名,因此您可以无缝使用它们,而无需 polyfill

@babel/plugin-transform-runtime作用:

  • babel-runtime编译过程中会重复生成冗余代码。
  • babel-runtime无法做到智能化分析,需要我们手动引入。

@babel/plugin-transform-runtime插件会智能化的分析我们的项目中所使用到需要转译的js代码,从而实现模块化从babel-runtime中引入所需的polyfill实现。

  • babel-runtime编译过程中会重复生成冗余代码。

@babel/plugin-transform-runtime插件提供了一个helpers参数。
最终配置:

regenerator-runtime 包其实可以不用手动安装,因为在安装 @babel/preset-env 的时候会自动安装 @babel/runtime,而安装 @babel/runtime 的时候会自动安装regenerator-runtime 包。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容