babel

218 阅读4分钟

Babel

Babel 是一个 JavaScript 编译器

Babel 能做什么

  • 语法转换
  • 通过 Polyfill 兼容
  • 源码转换(codemods)
const obj = {
  a: {
    b: { c: { d: { e: 1 }, f: 2 } },
  },
};

能力范围

  • Babel 只是转译新标准引入的语法

    • 箭头函数
    • let / const
    • 解构
  • 新标准引入的全局变量、部分原生对象新增的原型链上的方法,Babel 表示无能为力

    • 全局变量
      • Promise
      • Symbol
      • WeakMap
      • Set
    • includes
    • generator

对于这些,需要借助 polyfill 来处理

基本原理

Babel 的编译过程和大多数其他语言的编译器相似,可以分为三个阶段:

  • 解析(Parsing):将代码字符串解析成抽象语法树。
  • 转换(Transformation):对抽象语法树进行转换操作。
  • 生成(Code Generation): 根据变换后的抽象语法树再生成代码字符串。

核心库 @babel/core

Babel 的核心功能包含在 @babel/core 模块中,没有它,在 babel 的世界里注定寸步难行.不安装 @babel/core,无法使用 babel 进行编译

d1

CLI 命令行工具 @babel/cli

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

npm i @babel/cli

那我们在项目里面可以使用babel命令,比如 babel index.js 会对 index.js 进行编译

插件

Babel 构建在插件之上,可以使用插件组成一个转换通道,插件分为语法插件和转换插件

语法插件 比如我们需要解析 react 那就需要 preset-react 来解析 jsx 语法来生成 AST

转换插件 比如我们需要把箭头函数转换成 ES5 支持 需要配置 plugin-transform-arrow-functions

Babel 配置

官方文档

{
  "presets": [],
  "plugins": []
}

presets

presets 即是多个插件的集合,比如我们常用的preset-env 他是智能的 parse,通过配置,可以按需加载多个 plugin

如何智能的工作

通过browserslist,compat-tableelectron-to-chromium 利用这些数据源来维护哪个版本的受支持目标环境获得了 JavaScript 语法或浏览器功能的支持,以及这些语法和功能到 Babel transform 插件和 core-js polyfills 的映射

eg


index.js

const [a, b] = [1, 2];

.browserslistrc

last 2 Chrome versions

.babelrc

{
  "presets": ["@babel/preset-env"]
}

输出

const [a, b] = [1, 2];

那么我们修改.browserslistrc

ie >10

输出

var a = 1,
  b = 2;

index.js

async function f1() {
  await f2();
}

new Promise((resolve, rejec) => {
  resolve();
});

输出

"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function () {
    var self = this,
      args = arguments;
    return new Promise(function (resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}

function f1() {
  return _f.apply(this, arguments);
}

function _f() {
  _f = _asyncToGenerator(
    /*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
      return regeneratorRuntime.wrap(function _callee$(_context) {
        while (1) {
          switch ((_context.prev = _context.next)) {
            case 0:
              _context.next = 2;
              return f2();

            case 2:
            case "end":
              return _context.stop();
          }
        }
      }, _callee);
    })
  );
  return _f.apply(this, arguments);
}

new Promise(function (resolve, rejec) {
  resolve();
});

为什么异步函数解析成功,但是 Promise 没有解析成功呢

我们需要引入Polyfill

安装

npm install --save @babel/polyfill

引入 polyfill 就能完美兼容

import "@babel/polyfill";

new Promise((resolve, rejec) => {
  resolve();
});

缺陷

V7.4.0 开始@babel/polyfill 被弃用 需单独安装 core-js 和 regenerator-runtime 模块

修改配置

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

输出

"use strict";
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
require("regenerator-runtime/runtime");
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function () {
    var self = this,
      args = arguments;
    return new Promise(function (resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}
function f1() {
  return _f.apply(this, arguments);
}
function _f() {
  _f = _asyncToGenerator(
    /*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
      return regeneratorRuntime.wrap(function _callee$(_context) {
        while (1) {
          switch ((_context.prev = _context.next)) {
            case 0:
              _context.next = 2;
              return f2();

            case 2:
            case "end":
              return _context.stop();
          }
        }
      }, _callee);
    })
  );
  return _f.apply(this, arguments);
}
new Promise(function (resolve, rejec) {
  resolve();
});

新问题

有没有发现asyncGeneratorStep_asyncToGenerator每次都是重新引入,如果有很多个 js 调用 anync 的话,那么这两个就需要多次注入,引起打包体积过大

这时候我们需要引入 plugin-transform-runtime,使用 @babel/plugin-transform-runtime 插件,所有帮助程序都将引用模块 @babel/runtime,这样就可以避免编译后的代码中出现重复的帮助程序,有效减少包体积

修改配置

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

输出结果

"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
var _regenerator = _interopRequireDefault(
  require("@babel/runtime/regenerator")
);
require("regenerator-runtime/runtime");
var _asyncToGenerator2 = _interopRequireDefault(
  require("@babel/runtime/helpers/asyncToGenerator")
);
function f1() {
  return _f.apply(this, arguments);
}
function _f() {
  _f = (0, _asyncToGenerator2.default)(
    /*#__PURE__*/ _regenerator.default.mark(function _callee() {
      return _regenerator.default.wrap(function _callee$(_context) {
        while (1) {
          switch ((_context.prev = _context.next)) {
            case 0:
              _context.next = 2;
              return f2();

            case 2:
            case "end":
              return _context.stop();
          }
        }
      }, _callee);
    })
  );
  return _f.apply(this, arguments);
}
new Promise(function (resolve, rejec) {
  resolve();
});

新问题

有没有发现新增全局的 Promise 方法,污染了全局环境,那么在兼容 Promise 的浏览器中,性能可能会缩减,babel 官网也考虑到了,提供了@babel/runtime-corejs3

安装

npm install @babel/runtime-corejs3 --save

修改配置

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

输出

"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(
  require("@babel/runtime-corejs3/regenerator")
);
var _promise = _interopRequireDefault(
  require("@babel/runtime-corejs3/core-js-stable/promise")
);
var _asyncToGenerator2 = _interopRequireDefault(
  require("@babel/runtime-corejs3/helpers/asyncToGenerator")
);
function f1() {
  return _f.apply(this, arguments);
}
function _f() {
  _f = (0, _asyncToGenerator2.default)(
    /*#__PURE__*/ _regenerator.default.mark(function _callee() {
      return _regenerator.default.wrap(function _callee$(_context) {
        while (1) {
          switch ((_context.prev = _context.next)) {
            case 0:
              _context.next = 2;
              return f2();

            case 2:
            case "end":
              return _context.stop();
          }
        }
      }, _callee);
    })
  );
  return _f.apply(this, arguments);
}
new _promise.default(function (resolve, rejec) {
  resolve();
});

Babel7 支持

export type ParserPlugin =
  | "asyncGenerators"
  | "bigInt"
  | "classPrivateMethods"
  | "classPrivateProperties"
  | "classProperties"
  | "decimal"
  | "decorators"
  | "decorators-legacy"
  | "doExpressions"
  | "dynamicImport"
  | "estree"
  | "exportDefaultFrom"
  | "exportNamespaceFrom"
  | "flow"
  | "flowComments"
  | "functionBind"
  | "functionSent"
  | "importMeta"
  | "jsx"
  | "logicalAssignment"
  | "moduleAttributes"
  | "nullishCoalescingOperator"
  | "numericSeparator"
  | "objectRestSpread"
  | "optionalCatchBinding"
  | "optionalChaining"
  | "partialApplication"
  | "pipelineOperator"
  | "placeholders"
  | "privateIn"
  | "throwExpressions"
  | "topLevelAwait"
  | "typescript"
  | "v8intrinsic";

词法分析

[
  {
    "type": "Keyword",
    "value": "const"
  },
  {
    "type": "Identifier",
    "value": "a"
  },
  {
    "type": "Punctuator",
    "value": "="
  },
  {
    "type": "Numeric",
    "value": "1"
  },
  {
    "type": "Punctuator",
    "value": ";"
  }
]

ast(抽象语法树)

{
  "type": "File",
  "start": 0,
  "end": 10,
  "loc": {
    "start": {
      "line": 1,
      "column": 0
    },
    "end": {
      "line": 1,
      "column": 10
    }
  },
  "errors": [],
  "program": {
    "type": "Program",
    "start": 0,
    "end": 10,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 1,
        "column": 10
      }
    },
    "sourceType": "script",
    "interpreter": null,
    "body": [
      {
        "type": "VariableDeclaration",
        "start": 0,
        "end": 10,
        "loc": {
          "start": {
            "line": 1,
            "column": 0
          },
          "end": {
            "line": 1,
            "column": 10
          }
        },
        "declarations": [
          {
            "type": "VariableDeclarator",
            "start": 4,
            "end": 9,
            "loc": {
              "start": {
                "line": 1,
                "column": 4
              },
              "end": {
                "line": 1,
                "column": 9
              }
            },
            "id": {
              "type": "Identifier",
              "start": 4,
              "end": 5,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 4
                },
                "end": {
                  "line": 1,
                  "column": 5
                },
                "identifierName": "a"
              },
              "name": "a"
            },
            "init": {
              "type": "NumericLiteral",
              "start": 8,
              "end": 9,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 8
                },
                "end": {
                  "line": 1,
                  "column": 9
                }
              },
              "extra": {
                "rawValue": 1,
                "raw": "1"
              },
              "value": 1
            }
          }
        ],
        "kind": "let"
      }
    ],
    "directives": []
  },
  "comments": []
}