平时在开发的过程中,我们可能并不太需要十分了解babel的内容,仅仅知道它能够将新特性的代码转换成能够在旧版本浏览器中运行的代码。但是这一次想要趁着自己搭建脚手架的机会去进一步的了解babel的知识,所以写了这篇文章。以下内容是babel 7.4之后的版本,也就是@babel/polyfill被废弃需要独立安装core-jsregenerator-runtime 模块的版本。

babel命令行工具 @babel/cli

@babel/cli是babel的命令行工具,主要提供babel命令。另外还需要安装@babel/core才能使用babel去编译。

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

将命令配置在 package.json 文件的 scripts 字段中:

// package.json
"scripts": {
    "compiler": "babel src --out-dir lib --watch"
}

这样就能够通过npm run compiler来执行编译,但是babel本身什么都不做,需要添加插件来帮助babel完成工作。

plugin

babel所有功能都建立在各种的plugin上,使用方式是安装相应的plugin再去配置文件中去使用。例如箭头函数转换插件,
安装@babel/plugin-transform-arrow-functions,然后在.babelrc配置文件中去指定对应的插件

//.babelrc
{
  plugins: ["@babel/plugin-transform-arrow-functions"],
};

然后执行npm run compiler,可以看到箭头函数已经被编译完成

但是如果我们每个功能都去一个个添加对应的plugin会很麻烦,多以我们就需要preset预设去直接添加一组插件。

preset

preset就是一组插件的集合,最常用的preset就是@babel/preset-env

@babel/preset-env

它的作用是根据目标环境去进行语法转换和导入对应的polyfill

需要注意的是,@babel/preset-env会根据你配置的目标环境,生成插件列表来编译。默认情况下,如果你没有在 Babel 配置文件中(如 .babelrc)设置 targets 或 ignoreBrowserslistConfig,@babel/preset-env 会使用 package.jsonbrowserslist 配置源。

我们可以模拟生产环境和开发环境的浏览器版本

const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];

通过设置不同浏览器环境使用@babel/preset-env去编译相同代码,可以看到最终的结果也会不同。

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        // targets: product,
        targets: development,
      },
    ],
  ],
};

babel 只负责对语法进行编译,比如当我们写箭头函数,babel 会帮你把它编译成普通函数。但是对一些新的扩展方法,新的类来说babel就不能转换了。这时就需要去引入polyfillpolyfill的中文意思是垫片,所谓垫片就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。

polyfill

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

首先,我们需要安装他们到生产环境中,因为需要在生产环境中运行其中的polyfill

npm install --save core-js regenerator-runtime

@babel/preset-env的配置项中把useBuiltIns设置成usage,这样会根据目标浏览器环境去引入所需要的polyfill。需要注意点是,设置useBuiltIns还需要同时设置corejs

//.babelrc
const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: development,
        useBuiltIns: "usage",
        corejs: 3,
      },
    ],
  ],
};
//index.js
const isHas = [1, 2, 3].includes(2);

const getData = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(100);
    }, 1000);
  });

const main = async () => {
  const res = await getData();
  console.log(res);
};

main();

编译后的文件:

可以看到,编译后的文件中只引入了所用到的polyfill。

useBuiltIns还可以设置成其他值,比如entry,这需要在项目入口文件手动引入polyfills,例如@babel/polyfill或者core-js

//.babelrc
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: product,
        useBuiltIns: "entry",
        corejs: 3,
      },
    ],
  ],
};



//index.js
// 入口文件引入core-js
require("core-js");

但是这种方式会引入全量的polyfill。

useBuiltIns默认值为false,代表每个文件里不自动添加polyfill,或不将import "@babel/polyfill"转换为单独的polyfill。

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime可以重复使用 Babel 注入的帮助程序

在使用@babel/preset-env配合useBuiltIns: usage时,文件中会引入一些辅助方法例如_classCallCheck,当多处文件都使用到class时同样也会在每个文件中去引入这些辅助方法,这样会增大打包体积并且完全没有必要多次去引入同样的辅助方法。

//index.js
class A {}


//.babelrc.js
const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: product,
        useBuiltIns: "usage",
        corejs: 3,
      },
    ],
  ],
};

编译结果:

"use strict";

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

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

为了解决这个问题就需要使用@babel/plugin-transform-runtime,使用该插件,所有辅助方法都将引用模块 @babel/runtime,这样就可以避免编译后的代码中出现重复的辅助方法,有效减少包体积。

@babel/plugin-transform-runtime需要配合@babel/runtime来使用,@babel/plugin-transform-runtime在开发时使用,最终代码需要依赖@babel/runtime

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
//index.js
class A {}

//.babelrc.js
const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: product,
        useBuiltIns: "usage",
        corejs: 3,
      },
    ],
  ],
  //使用@babel/plugin-transform-runtime
  plugins: [["@babel/plugin-transform-runtime"]],
};

编译结果:

"use strict";

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

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

var A = function A() {
  (0, _classCallCheck2.default)(this, A);
};

可以看到这些辅助方法都是从@babel/runtime中引入。

@babel/plugin-transform-runtime可以创建一个沙盒环境来避免对全局环境的污染

之前在使用@babel/preset-env编译promise和includes时会引入core-js中的全局变量或者在对应的原型链中添加相应的方法,这样都造成了全局环境的污染。虽然这对于应用程序或命令行工具是可以的,但是如果你的代码是要发布供他人使用的库,或者无法完全控制代码运行的环境,则将成为一个问题。

首先,单独使用@babel/plugin-transform-runtime只能够处理辅助方法,如果想要去引入polyfill就需要配合@babel/runtime-corejs3使用。

同样还是在生产环境安装@babel/runtime-corejs3

npm install @babel/runtime-corejs3 --save

这里需要在.babelrc中去除@babel/preset-env配置中关于polyfill的部分以免与@babel/runtime-corejs3重复。

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

const getData = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(100);
    }, 1000);
  });

getData();

//.babelrc.js
module.exports = {
  presets: [["@babel/preset-env"]],
  plugins: [
    [
      "@babel/plugin-transform-runtime",
      {
        corejs: 3,
      },
    ],
  ],
};

编译结果:

"use strict";

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

var _setTimeout2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set-timeout"));

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

var getData = function getData() {
  return new _promise["default"](function (resolve, reject) {
    (0, _setTimeout2["default"])(function () {
      resolve(100);
    }, 1000);
  });
};

getData();

可以看到,使用@babel/plugin-transform-runtime会用一个临时变量去保存polyfill中的一些值,并不是直接去修改原型链或者新增Promise方法。

在一般开发中使用@babel/preset-env配合useBuiltIns: usage,在开发第三方库时使用@babel/plugin-transform-runtime

在上面介绍@babel/plugin-transform-runtime的一些使用时可以看到,它不仅能够处理引入多次helper辅助方法的问题,而且在只引入所需polyfill时还不会污染全局环境,那还有必要使用@babel/preset-envuseBuiltIns吗?

其实@babel/plugin-transform-runtime配合@babel/runtime-corejs3引入polyfill有一个很大的不足就是不能够通过设置目标环境去引入所需要的polyfil。,我们在普通开发时只需要在package.json中的browserslist去设置开发环境和生产环境的浏览器版本,然后通过使用@babel/preset-envuseBuiltIns就能够根据不同的运行环境去引入适当的polyfill。

但是在开发第三方库时,不能确定代码的运行环境,所以就需要利用@babel/plugin-transform-runtime来保证引入的polyfill不去污染全局环境。

最后总结

一般开发: 通过useBuiltIns: usage去保证引入恰当的polyfill,通过@babel/plugin-transform-runtime保证辅助函数都是引用@babel/runtime`。

const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        //代替browserslist设置浏览器版本
        targets: product,
        useBuiltIns: "usage",
        corejs: 3,
      },
    ],
  ],
  plugins: [["@babel/plugin-transform-runtime"]],
};

参考文章


RThong
280 声望7 粉丝

引用和评论

0 条评论