关于Babel你需要知道的那些事

2,794 阅读8分钟

本文介绍了babel的配置和用法,对presets、plugins等概念及其用法进行了介绍;阐述了polyfill 和 transform-runtime 的作用和用法,对两者进行了对比;通过实验验证各自的特点和优势。 希望能对你有所帮助,欢迎交流~

一、什么是babel

将es6+进行转化,向后兼容 转化为es5使得他的绝大部分浏览器可以使用
下面地址可查看es6的支持情况: kangax.github.io/compat-tabl…

二、用法

安装babel相关的依赖

npm install --save-dev @babel/core @babel/cli @babel/preset-env    //@babel/cli实现命令行的能力
npm install --save @babel/polyfill

配置

以前配置使用.babelrc文件,现在可以使用bable.config.js 放在项目根路径下;进行babel转义时,如果没有指定对应的配置,就会走这套配置。

命令行编译src下文件:

./node_modules/.bin/babel src --out-dir dist
或
npx babel src --out-dir dist

babel也是可以进行文件配置的,而不是仅仅只可以像这里一样使用命令行,接下来看一下文件配置。 在跟目录下新建babel.config.js文件

const presets = [
  [
    "@babel/env",
    {
      "targets": {
        "edge": "17",
        "firefox": "60",
        "chrome": "67",
        "safari": "11.1",
        "ie": "10"
      },
    }
  ]
]

module.exports={
  presets
}

如果不指定特定的转义插件,就会走到配置文件

"scripts": {
    "build": "babel src -d dist --presets=@babel/env",
    "build:auto": "babel src -d dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

三、Plugins & Presets

Presets是Plugins的集合 Plugins用来为不同特定特性转化为es6,不同的特性转化需要引入不同的Plugins,这样引入就很繁杂和不好维护,特此才有了集合Presets 如箭头函数的转化:

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

./node_modules/.bin/babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions

一般复杂的命令行可以通过脚本的方式方便维护:

"scripts": {
    "build": "babel src -d dist",
    "build:env": "babel src -d dist --presets=@babel/env",
    "build:arrow": "babel src -d dist --plugins=@babel/plugin-transform-arrow-functions",
    "test": "echo \"Error: no test specified\" && exit 1"
 },

一般多数es6语法转化使用的是 preset-env

npm install --save-dev @babel/preset-env

./node_modules/.bin/babel src --out-dir lib --presets=@babel/env

四、polyfill

可以用polyfill来实现所有新的js功能,env只实现我们使用的功能的转换,实现目标浏览器中缺少的功能

安装

npm install --save @babel/polyfill

使用

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1",
        },
        "useBuiltIns": "usage",
      }
    ]
  ]
}

转义效果

转义前

const sayHi = () => {
  console.log(77777777);

}
Promise.resolve().finally();
sayHi()

转义后

"use strict";

require("core-js/modules/es6.promise");

require("core-js/modules/es6.object.to-string");

require("core-js/modules/es7.promise.finally");

var sayHi = function sayHi() {
  console.log(77777777);
};

Promise.resolve().finally();
sayHi();

五、plugin详解

插件有很多可以安装指定插件后,在命令行、脚本、bablerc、babael.config.js 进行配置,在这以@babel/plugin-transform-member-expression-literals举例。

插件用法举例

@babel/plugin-transform-member-expression-literals 将和关键字同名的属性,修改为.['xxx']的形式 输入:

obj.foo = "isValid";

obj.const = "isKeyword";
obj["var"] = "isKeyword";

输出:

obj.foo = "isValid";

obj["const"] = "isKeyword";
obj["var"] = "isKeyword";

使用:

//命令行
npx babel --plugins @babel/plugin-transform-member-expression-literals src -d dist

//通过 Node API

const code = `obj.foo = "isValid";

obj.const = "isKeyword";
obj.var = "isKeyword";`

const result = require("@babel/core").transform(code, {
  plugins: ["@babel/plugin-transform-member-expression-literals"]
});
console.log(result);

效果:

其他新es版本的新特性的语法特性转化在此不再赘述。

模块化转化插件

babel提供多种形式的模块化转化支持:
·modules-amd
·modules-commonjs
·modules-systemjs
·modules-umd

测试的时候同一份代码可以通过不同的插件转化,效果不一样
转化配置

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build:amd": "babel src/test.js --out-file dist/test-amd.js --plugins @babel/plugin-transform-modules-amd",
    "build:common": "babel src/test.js --out-file dist/test-common.js --plugins @babel/plugin-transform-modules-commonjs",
    "build:system": "babel src/test.js --out-file dist/test-system.js --plugins @babel/plugin-transform-modules-systemjs",
    "build:umd": "babel src/test.js --out-file dist/test-umd.js --plugins @babel/plugin-transform-modules-umd",
    "build": "npm run build:amd && npm run build:common && npm run build:system && npm run build:umd"
  },

注:命令行可以合并在一起 如果命令行不指定插件就会去找.babelrc等配置 如果没有找到 就会不进行任何处理

原代码 test.js

export default 42;

转化后的

amd

define(["exports"], function (_exports) {
  "use strict";

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.default = void 0;
  var _default = 42;
  _exports.default = _default;
});

common

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _default = 42;
exports.default = _default;

system

System.register([], function (_export, _context) {
  "use strict";

  return {
    setters: [],
    execute: function () {
      _export("default", 42);
    }
  };
});

umd

(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define(["exports"], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports);
  } else {
    var mod = {
      exports: {}
    };
    factory(mod.exports);
    global.test = mod.exports;
  }
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) {
  "use strict";

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.default = void 0;
  var _default = 42;
  _exports.default = _default;
});

在实验插件

www.babeljs.cn/docs/plugin…
一些有提案但还没有纳入正式标准的用法也有对应的插件的开发,可以使用对应插件就可以体验最新的提案。提案一般都是优势胜过劣势的,可以大胆的使用,一定可以提升研发的效能。

代码瘦身插件 Minification

www.babeljs.cn/docs/plugin…
使用他们可以去除无用代码或用更简洁的方式替换原来的代码,减少代码的体积。 去除console debugger的可以使用

六、polyfill vs runtime

polyfill

垫片,内容多,体积大 运行原代码前 先将垫片垫进去,

runtime

运行时 环境隔离,体积小,不做侵入

preset-env

plugins 的集成 可以按需引入使用useBuiltIns

七、总结与验证

转化的对象有两种:语法和API;

语法转化:

关于语法的转化是固定的,而且是开发依赖就已经转化好的,不涉及对新代码的引入,所以一般没有问题。
在配置文件中引入对应的语法插件或preset就可以 如:

const presets = [
  [
    "@babel/preset-env",
    {
      "targets": {
        "edge": "17",
        "firefox": "60",
        "chrome": "67",
        "safari": "11.1",
        "ie": "10"
      },
    },

  ]
]

module.exports = {
  presets
}

API支持

支持api都是需要在代码中先引入新api的定义代码的, 有两种方式:
1.使用polyfill
2.使用@babel/plugin-transform-runtime

1.使用polyfill

使用polyfill的劣势:

1)自身总体很大: 可以考虑按需加载 2)会污染内置函数 静态方法 实例方法等,如果在项目的末端(开发项目本身或命令行终端)一般是没有影响的,可是如果在组件中要提供给别人用就会干扰其他人了:解决方案是使用@babel/plugin-transform-runtime

使用polyfill的方法

1.可以直接在代码开头引入polyfill,这样很大一般不推荐
2.使用@babel/env这个preset 里面有项配置useBuiltIns可以指定按需加载。 babel.config.js

const presets = [
  [
    "@babel/preset-env",
    {
      "targets": {
        "edge": "17",
        "firefox": "60",
        "chrome": "67",
        "safari": "11.1",
        "ie": "10"
      },
      "useBuiltIns": "usage"  //usage代表按需加载  此外还可以配置false:不按需加载  entry:从入口加载
    },

  ]
]

module.exports = {
  presets
}


转化前的源码

const sayHi = () => {
  console.log(77777777);

}
Promise.resolve().finally();
sayHi()

转化后的代码

"use strict";

require("core-js/modules/es6.promise");

require("core-js/modules/es6.object.to-string");

require("core-js/modules/es7.promise.finally");

// import '@babel/polyfill'
var sayHi = function sayHi() {
  console.log(77777777);
};

Promise.resolve().finally();
sayHi();

3.自己手动按需引入,这样一般效率会低很多 需要对各个core里面的内容都非常清楚,其实要求非常高。

2. plugin-transform-runtime 和 runtime

@babel/plugin-transform-runtime && @babel/runtime两个一般搭配起来一起用

来源背景

Babel 会使用很小的辅助函数来实现类似 _createClass 等公共方法。默认情况下,它将被添加(inject)到需要它的每个文件中。这样就会有一个问题:如果一个项目的多个不同的文件用到了同一个api,此时如果使用polyfill的方式,每个文件都会单独在头部引入,这样重复的代码就太多了,为了解决这个问题可以使用@babel/plugin-transform-runtime

这套方案的核心优势是:

1.同一个新api在这个项目中不同地方支持它时,引入的支持代码都从库@babel/runtime中引入,这样就避免重复了。
2.可以避免全局污染

plugin-transform-runtime避免重复的效果演示

转换前的代码

class Point { constructor(x, y) { this.x = x; this.y = y; }; getX() { return this.x; } } let cp = new ColorPoint(25, 8);

如果用folyfill按需转化 配置如下:

const presets = [
 [
   "@babel/preset-env",
   {
     "targets": {
       "edge": "17",
       "firefox": "60",
       "chrome": "67",
       "safari": "11.1",
       "ie": "10"
     },
     "useBuiltIns": "usage"
   },

 ]
]
module.exports = {
 presets,
}

转化后的效果为:

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

可以看到有很多inject插入 如果使用plugin-transform-runtime插件

const presets = [
 [
   "@babel/preset-env",
   {
     "targets": {
       "edge": "17",
       "firefox": "60",
       "chrome": "67",
       "safari": "11.1",
       "ie": "10"
     },
     "useBuiltIns": "usage"
   },

 ]
]

const plugins = [["@babel/plugin-transform-runtime"]]

module.exports = {
 presets, plugins
}

转换后的代码

"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 Point = /*#__PURE__*/function () {
 function Point(x, y) {
   (0, _classCallCheck2.default)(this, Point);
   this.x = x;
   this.y = y;
 }

 (0, _createClass2.default)(Point, [{
   key: "getX",
   value: function getX() {
     return this.x;
   }
 }]);
 return Point;
}();

var cp = new ColorPoint(25, 8);

可以看到没有直接插入 而是通过@babel/runtime的方式引入的

避免全局污染效果演示

源码如下:

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

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

如果没有使用plugin-transform-runtime时,使用polyfill按需加载,配置文件如下

const presets = [
 [
   "@babel/preset-env",
   {
     "targets": {
       "edge": "17",
       "firefox": "60",
       "chrome": "67",
       "safari": "11.1",
       "ie": "10"
     },
     "useBuiltIns": "usage"
   },

 ]
]

module.exports = {
 presets,
}

转化后的效果

"use strict";

require("core-js/modules/es6.promise");

require("core-js/modules/es6.object.to-string");

require("core-js/modules/es7.array.includes");

require("core-js/modules/es6.string.includes");

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

可以发现Array.prototype 上新增了 includes 方法,并且新增了全局的 Promise 方法,污染了全局环境。

如果使用plugin-transform-runtime,配置如下:

const presets = [
  [
    "@babel/preset-env",
    {
      "targets": {
        "edge": "17",
        "firefox": "60",
        "chrome": "67",
        "safari": "11.1",
        "ie": "10"
      },
      // "useBuiltIns": "usage"
    },

  ]
]

const plugins = [["@babel/plugin-transform-runtime", { "corejs": 3 }]]

module.exports = {
  presets, plugins
}

plugin-transform-runtime配置选项中要配指定要引入的corejs,不再使用"useBuiltIns": "usage" 因为他会和plugin-transform-runtime引入重复,这是转化后的效果:

"use strict";

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

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

var _context;

var isHas = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 2);
new _promise.default(function (resolve, reject) {
  resolve(100);
});

没有直接去修改 Array.prototype,或者是新增 Promise 方法,避免了全局污染。 plugin-transform-runtime的优势可以总结为: 减少体积; 避免全局污染; 按需加载