20211220


Babel

一、babel的理解

官方文档对babel的描述是这样的Babel 是一个 JavaScript 编译器,相对于编译器compiler,叫转译器transpiler更准确,他是把比较高规则的语法转化为低规则的语法,做到向后兼容,而不像编译器那样,输出的是另一种更低级的语言代码,但是和编译器类似,babel的转译过程也分为三个阶段:parsing(解析)、transforming(转化)、generating(生成)

  • parsing阶段babel内部的 babylon 负责将es6代码进行语法分析和词法分析后转换成抽象语法树(AST)

  • transforming阶段内部的 babel-traverse 负责对抽象语法树进行变换操作得到新的AST树

  • printing阶段内部的 babel-generator 负责生成对应的代码

babel其实只是提供了一个“平台”,让更多有能力的plugins入驻我的平台,是这些plugins提供了将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法的能力,因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的

二、分类处理

babel对于ECMAScript 2015+ 版本的代码分为了两种情况处理:

  • 语法层: let、const、class、箭头函数等,这些需要在构建时进行转译,是指在语法层面上的转译

  • api方法层:Promise、includes、map等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义

三、preset-env

preset-env用于处理语法层

//compiled.js
const fn = () => {
  console.log("wens");
};

const p = new Promise((resolve, reject) => {
  resolve("wens");
});
const list = [1, 2, 3, 4].map(item => item * 2);

加入preset-env

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

编译之后

//compiled.js
"use strict";
var fn = function fn() {
  console.log("wens");
};

var p = new Promise(function (resolve, reject) {
  resolve("wens");
});
var list = [1, 2, 3, 4].map(function (item) {
  return item * 2;
});

语法层面都降级了。那么api层面要如何处理? 需要我们加入 @bable/polyfill

四、polyfill

用于处理api层,polyfill的中文意思是垫片,顾名思义就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。@babel/polyfill是一个运行时包,主要是通过核心依赖core-js@2来完成对应浏览器不支持的新的全局和实例api的添加。在升级到core-js@3后,如果还要保留@babel/polyfill的使用,就要在@babel/polyfill中添加core-js@2core-js@3切换的选项,这样@babel/polyfill中将包含core-js@2core-js@3两个包,出于这个原因官方决定弃用@babel/polyfill

// Promise是api层面的,是一个es6中的全局对象
const p = new Promise((resolve, reject) => {    
  resolve(100);
});

编译之后

"use strict";

var p = new Promise(function (resolve, reject) {
  resolve(100);
});

但是下载@bable/polyfill是全部加载并没有按需导入,需要配置useBuiltIns

五、useBuiltIns

上面我们通过 import "@bable/polyfill" 的方式来实现针对api层面的“抹平”。然而从 babel v7.4.0开始官方就不建议采取这样的方式了。 因为引入 @bable/polyfill 就相当于在代码中引入下面两个库

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

useBuiltIns这一配置项,它的值有三种:

  • false: 不对polyfills做任何操作
  • entry: 根据target中浏览器版本的支持,将polyfills拆分引入,仅引入有浏览器不支持的polyfill
  • usage(新):检测代码中ES6/7/8等的使用情况,仅仅加载代码中用到的polyfills

然后我们修改配置如下:

//.babelrc
{
   "presets": [
       ["@babel/preset-env",{
           "useBuiltIns": "usage",
        	 "corejs":3
       }]
   ]
}

六、@babel/plugin-transform-runtime

解决代码冗余

代码冗余是出现在转译语法层时出现的问题。
首先依然修改下我们的index.js,这次我们再写一个语法层面的es6–>class(let,const这些对比不出问题,所以用class),看看babel会把class转化成什么

//index.js
class Student {
    constructor(name,age){
        this.name = name;
        this.age = age
    }
}

编译之后

"use strict";require("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 Student = function Student(name, age) {  _classCallCheck(this, Student);  this.name = name;  this.age = age;};

上面出现的_classCallCheck函数叫做辅助函数,是在编译阶段辅助 Babel 的函数。在每个文件中都会出现,会造成冗余,plugin-transform-runtime 闪亮登场。

当使用了plugin-transform-runtime插件后,就可以将babel转译时添加到文件中的内联辅助函数统一隔离到babel-runtime提供的helper模块中,编译时,直接从helper模块加载,不在每个文件中重复的定义辅助函数,从而减少包的尺寸

可以看出preset-env在处理例如Promise这种的api时,只是引入了core-js中的相关的js库,这些库重新定义了Promise,然后将其挂载到了全局。
这里的问题就是:必然会造成全局变量污染,同理其他的例如Array.from等会修改这些全局对象的原型prototype,这也会造成全局对象的污染。
解决方式就是:将core-js交给transform-runtime处理。添加一个配置即可,将core-js这个属性添加到@babel/plugin-transform-runtime这个插件的配置下,让这个插件处理,同时也不需要配置useBuiltIns了,因为在babel7中已经将其设置为默认值 (transform-runtime是利用plugin自动识别并替换代码中的新特性,检测到需要哪个就用哪个

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

plugin-transform-runtime 插件借助babel-runtime实现了下面两个重要的功能

  • 对辅助函数的复用,解决转译语法层时出现的代码冗余

  • 解决转译api层出现的全局变量污染

    更多关于这个插件的配置可参考官方文档

七、参考资料:

  1. https://juejin.cn/post/6844904199554072583
  2. https://juejin.cn/post/6976501655302832159#heading-4

Author: wxy
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source wxy !
  TOC