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, 常用的有:

查看更多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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容