记录学习babel——babel-polyfill的优化使用

982 阅读4分钟

babel在项目中的作用

在我的理解来看,babel就是用来转义我们项目中一些浏览器版本不兼容的高级语法(比如es6,es7...) babel的工作过程分为三个阶段:parsing(解析)、transforming(转化)、printing(生成)

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

2、transforming阶段内部的 babel-traverse 负责对抽象语法树进行变换操作

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

其中第二步的转化是重中之重,babel的插件机制也是在这一步发挥作用的,plugins在这里进行操作,转化成新的AST,再交给第三步的babel-generator。

polyfill、preset-env的区别

通过babel官网可以看到babel-polyfill里的代码是这样的

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

从 babel v7.4.0开始官方就不建议直接使用babel-polyfill了。所以来了解一下@babel/preset-env

@babel/preset-env

@babel/preset-env 会根据 browserlist 配置进行转换,如果需要兼容比较旧的浏览器,需要手动引入 @babel/polyfill @babel/preset-env的option如下

  • targets.esmodules:boolean = false;请注意:在指定 esmodules 目标时,将忽略 browserlists, 即 useBuiltIn 会失效,不转化 es6 语法也不 polyfill,如果 想用 esmodules 又需要 polyfill ,请组合使用 modules = false , useBuiltIn

  • useBuiltIns = false;根据 browserlist 是否转换新语法与 polyfill 新 API;

    • false : 不启用polyfill, 如果 import '@babel/polyfill', 会无视 browserlist 将所有的 polyfill 加载进来;
    • entry : 启用,需要手动 import '@babel/polyfill', 这样会根据 browserlist 过滤出 需要的 polyfill;
    • usage : 不需要手动import '@babel/polyfill'(加上也无妨,构造时会去掉), 且会根据 browserlist + 业务代码使用到的新 API 按需进行 polyfill
  • modules = 'commonjs'

    • "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | false, defaults to "commonjs".转换 es6 模块语法到其他 模块规范, false不会转换

    • include:Array<string|RegExp> = [] 如果你 使用了某个新特性(如es6.array.from),无论browserslist 如何你都想 转化它, 则 include: ['es6.array.from']

    • exclude:Array<string|RegExp> = [] 同理

  • loose = false(推荐)

    • 优势:代码更加简洁,更容易看懂,可能被老浏览器引擎执行得更快,兼容性更好。
    • 缺点:当从 编译后的 es6 代码转换成 原生 es6 代码,有可能出现问题。这不值得冒险启用 loose
    • 多数的 babel plugin 有两种模式,普通模式会将代码编译成尽可能接近 es6 语义,loose 模式则会将代码编译成 es5 风格 针对loose,看一个列子
// 源码
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return `(${this.x}, ${this.y})`;
    }
}
// 普通模式 更接近 es6
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: "toString",
    value: function toString() {
      return "(".concat(this.x, ", ").concat(this.y, ")");
    }
  }]);

  return Point;
}();
// loose = true编译模式 更接近 es5
var Point =
/*#__PURE__*/
function () {
  function Point(x, y) {
    this.x = x;
    this.y = y;
  }

  var _proto = Point.prototype;

  _proto.toString = function toString() {
    return "(" + this.x + ", " + this.y + ")";
  };

  return Point;
}();

虽然@babel/preset-env实现了对代码的按需进行 polyfill,但是我们可以看到babel 在每个需要的文件的顶部都会插入一些 helpers 代码,这可能会导致多个文件都会有重复的 helpers 代码。针对这个问题,接下来我们再看看@babel/plugin-transform-runtime、@babel/runtime

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime 的 helpers 选项就可以把这些模块抽离出来 具体配置如下

"@babel/plugin-transform-runtime",
{
"corejs": false, 
	// 默认值,可以不写,可选项23,
	// 配置 corejs 为 3,需要预先安装 @babel/runtime-corejs3
	// 配置 corejs 为 2,需要预先安装 @babel/runtime-corejs2,配置 corejs 为 false,需要预先安装 @babel/runtime
"helpers": true, // 默认,可以不写
"regenerator": false, // 通过 preset-env 已经使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime
"useESModules": true, // 使用 es modules helpers, 减少 commonJS 语法代码
}

添加新配置后打包出新的代码如下

// 添加新配置后编译出来的代码
import "core-js/modules/es6.promise";
import "regenerator-runtime/runtime";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";

var asyncFun =
/*#__PURE__*/
function () {
  var _ref = _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 new Promise(setTimeout, 2000);

          case 2:
            return _context.abrupt("return", '2s 延时后返回字符串');

          case 3:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));

  return function asyncFun() {
    return _ref.apply(this, arguments);
  };
}();

export default asyncFun;

可以看到,已经没有了内联的 helpers 代码,大功告成。

总结

首先安装依赖包:

npm i -S @babel/polyfill @babel/runtime @babel/preset-env @babel/plugin-transform-runtime

完整的babel配置如下

.babelrc.js配置

module.exports = {
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": false, // 默认值,可以不写
                "helpers": true, // 默认,可以不写
                "regenerator": false, // 通过 preset-env 已经使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime
                "useESModules": true, // 使用 es modules helpers, 减少 commonJS 语法代码
            }
        ]
    ],
    presets: [
    	['@babel/plugin-proposal-decorators', { legacy: true }], //项目中使用到装饰器的话需要
        ['@babel/plugin-proposal-class-properties'],
        [
            "@babel/preset-env",
            {
                "modules": false, // 模块使用 es modules ,不使用 commonJS 规范 
                "useBuiltIns": 'usage', // 默认 false, 可选 entry , usage
            }
        ]
    ]
}