babel简介

babel主要用于编译JavaScript代码。一般来说,我们使用JavaScript的最新语法特性来编写程序,但为了能够兼容更多的浏览器,会使用babel将兼容性不好的语法特性编译成被各个浏览器广泛支持的形式。

哪些语法特性需要被编译?

默认情况下,babel不会编译任何语法特性,需要通过配置文件依次添加。

babel的配置方法,参考这里。

配置需要编译的语法特性其实就是在babel的配置中添加plugin。每一个plugin负责编译一个语法特性,多个plugin间互不干扰。比如添加@babel/plugin-transform-arrow-functions,可以把箭头函数编译成普通函数。添加@babel/plugin-transform-classes,可以把class声明的类编译成普通函数的形式。

更方便的管理方式

一个项目中一般会用到很多的plugin。如果一个个的去添加,会特别耗费时间。而preset解决了这个问题。一个preset是一组plugin的集合。根据功能的不同,plugin被分类在不同的preset中。一般来说,项目中只需要配置特定的preset就可以满足编译需求。

Babel官方提供了一些preset, 常用的有:

  • @babel/preset-env 把一些已经发布的特性编译成被各版本浏览器广泛支持的形式。
  • @babel/preset-react 用于编译jsx,使用react时需要使用。

查看更多preset。

更多优化

复用helpers

babel在编译后会在代码加入一些额外的代码,叫做helpers,用于替换被编译的语法特性。比如:

// 输入
export default class A extends B{
    
}
// 输出
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = void 0;

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

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

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

var A =
/*#__PURE__*/
function (_B) {
  _inherits(A, _B);

  function A() {
    _classCallCheck(this, A);

    return _possibleConstructorReturn(this, _getPrototypeOf(A).apply(this, arguments));
  }

  return A;
}(B);

exports["default"] = A;

一般项目中都有很多个需要被编译的文件,编译后添加的helpers会被添加在每一个文件中。helpers的代码其实是固定的,这样相同的代码充斥在每一个文件中,无疑是一种浪费,大大增加了最终的代码体积。

这个问题可以通过@babel/plugin-transform-runtime解决。它使编译后的文件从公用包中引用helpers,而不是直接添加在文件中。这样所有的文件都会公用一份helpers,减少了文件体积。

// 使用 @babel/plugin-transform-runtime 编译后的文件
"use strict";

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

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = void 0;

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

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

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

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

var A =
/*#__PURE__*/
function (_B) {
  (0, _inherits2["default"])(A, _B);

  function A() {
    (0, _classCallCheck2["default"])(this, A);
    return (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(A).apply(this, arguments));
  }

  return A;
}(B);

exports["default"] = A;

按需使用plugin

上文中提到的@babel/preset-env可以把一些语法特性编译成被各版本浏览器广泛支持的形式。同时编译后的文件也会因此多了许多helpers,极大的增加了文件的体积。

其实,对于很多项目来说,增加的helpers里有很多是没有必要的。因为这些项目只需要兼容更少的浏览器,使用更少的plugin,编译更少的语法特性。

针对这一问题,@babel/preset-env支持配置browserslist。根据需要兼容的浏览器版本号,来确定需要使用哪些plugin

查看配置方法。

polyfill的添加

要使项目能够兼容更多浏览器,只是编译语法特性是远远不够的。因为老浏览器所不支持的,不只是一些语法特性,还有一些全局对象和某些成员方法,比如SetMap[].findIndex等。为了解决这一兼容问题,一般会在项目中引入polyfill,比如在入口文件中引入import '@babel/polyfill'

这样的确可以解决兼容问题,但却极大地增加了代码体积,需要继续优化。@babel/preset-env为这一问题提供了两种解决方案。

方案一

根据browserslist,在入口处为指定版本的浏览器添加polyfill。

注意: 入口文件一定要添加代码——import '@babel/polyfill'。babel无法判断当前处理的文件是否是入口文件,它的处理方式是把import '@babel/polyfill'替换为特定的polyfill。

配置方法: useBuiltIns: entry,见配置方法。

// 输入
console.log('llala')
import '@babel/polyfill'
// 输出
"use strict";

require("core-js/modules/web.timers");

require("core-js/modules/web.immediate");

// ... 此处省略很多polyfill

console.log('llala');

方案二

根据browserslist,在当前文件中为已使用的对象或方法提供polyfill。

配置方法: useBuiltIns: usage,见配置方法。

// 输入
[].findIndex(item => item)
// 输出
"use strict";

require("core-js/modules/es6.array.find-index");

[].findIndex(function (item) {
  return item;
});

使用这一方案会有一个小问题,babel可能无法正确识别代码中是否使用了某个需要polyfill的对象或方法。我做了一些测试,如下:

// 可以识别
const key = 'findIndex';
a[key](item => item)
// 不能识别
const key = ['findIndex'].join('')
a[key](item => item)
// 可以识别
new Map()
// 不能识别
new window.Map()

如果你在使用这一方案,需要注意避开一些让babel无法正确识别的写法。

进一步优化

无论你是在使用上述的方案一,或是方案二,都可以通过配置exclude来避免添加某些polyfill。
例如:

{
    "exclude": ["web.dom.iterable"]
}

更多信息,见[文档]。

最后

笔者在写这篇文章时,babel的版本号为7.6.0

你可能感兴趣的:(babel简介)