Babel從入門到了解整個體系

Babel的定義

babel是一個工具集袱结,主要含@babel/cli亮隙、@babel/corebabel/preset-env這三個包垢夹。這個工具集是圍繞@babel/core這個核心npm包構(gòu)成的溢吻。每次@babel/core發(fā)布新版本的時候,整個工具集的其他npm包也都會跟著升級到與@babel/core相同的版本果元,即使它們的代碼可能一行都沒有改變促王。

  1. @babel/cli是Babel命令行轉(zhuǎn)碼工具,如果我們使用命令行進(jìn)行Babel轉(zhuǎn)碼就需要安裝它
  2. @babel/core是Babel的核心包而晒,包含了Babel的轉(zhuǎn)碼API
  3. @babel/preset-env這個npm包提供了ES6轉(zhuǎn)ES5的語法轉(zhuǎn)換規(guī)則蝇狼,我們需要再Babel配置文件里指定使用它。如果不使用的話倡怎,也可以完成轉(zhuǎn)碼迅耘,但轉(zhuǎn)碼后的代碼任然是ES6代碼,相當(dāng)于沒有轉(zhuǎn)碼。

Babel的主要工作

  1. 語法轉(zhuǎn)換
  2. 補(bǔ)齊API
    補(bǔ)齊API的意思是通過polyfill的方式在目標(biāo)環(huán)境中添加缺失的特性诈胜。

從廣義上講豹障,polyfill是為環(huán)境提供不支持的特性的一類文件或庫,從狹義上講,其是polyfill.js文件及@babel/polyfill這個npm包.

Babel的配置文件

文件babel.config.js是Babel 執(zhí)行時默認(rèn)在當(dāng)前目錄下搜尋的Babel配置文件焦匈。除了babel.config.js配置文件血公,也可以選擇.babelrc.babelrc.js者兩種配置文件,還可以直接將配置參數(shù)寫在package.json文件里缓熟。它們都是相同的,只需要選擇其中一種累魔。

//babel.config.js

var year = 2020;
var presets = [];

if (year > 2018) {
  presets = ["@babel/env"]
} else {
  presets = ['es2015','es2016','es2017'];
}

module.exports = {
  presets: presets,
  plugins: [],
  minified: false,
  ignore: [],
};

插件與預(yù)設(shè)

Babel的插件實(shí)在太多了摔笤,假如都在plugins里一一配置,那么配置文件會非常臃腫垦写。preset預(yù)設(shè)就是來解決這個問題的吕世。預(yù)設(shè)就是一組Babel插件的集合,通俗的說法是插件包梯投。另外命辖,預(yù)設(shè)也可以是插件和其他預(yù)設(shè)的集合。Babel官方已經(jīng)對常用的環(huán)境做了如下的preset包分蓖。

  1. @babel/preset-env
  2. @babel/preset-react
  3. @babel/preset-typescript
  4. @babel/preset-flow

plugins插件數(shù)組和presets預(yù)設(shè)數(shù)組是有順序要求的尔艇。如果兩個插件或預(yù)設(shè)都要處理同一個代碼片段,那么會根據(jù)插件和預(yù)設(shè)的順序來執(zhí)行么鹤,規(guī)則如下:

  1. 插件比預(yù)設(shè)先執(zhí)行
  2. 插件執(zhí)行順序是插件數(shù)組元素從前向后一次執(zhí)行
  3. 預(yù)設(shè)執(zhí)行順序是預(yù)設(shè)數(shù)組元素從后向前依次執(zhí)行

如果要給插件或者預(yù)設(shè)設(shè)置參數(shù)终娃,那么元素就不能寫成字符串了,而是改寫成一個數(shù)組蒸甜。

{
  "presets":[
    [
      "@babel/preset-env",
      {
        targets:{ // 如果設(shè)置了targets棠耕,那么就不會使用browserslist的配置
          "chrome":"58",
          "ie":"11"
        }
        "useBuiltIns":"entry",// 1.false(默認(rèn)值):polyfill引入全量代碼 2.entry:根據(jù)目標(biāo)環(huán)境缺失的api對polyfill進(jìn)行部分引入(需要在項(xiàng)目入口處手動引入polyfill) 3.usage 只有當(dāng)我們使用的ES6特性API在目標(biāo)環(huán)境下缺失的時候,Babel才會一入core.js針對性引入polyfill(不需要在項(xiàng)目入口處手動引入polyfill)
        "modules":auto,//auto(默認(rèn)值,轉(zhuǎn)換后的代碼里import轉(zhuǎn)換成require),可以是amd柠新、umd窍荧、systemjs、commonjs恨憎、cjs搅荞、false
      }
    ]
  ]
}

使用ES6模塊化語法的好處:
在使用webpack一類的構(gòu)建工具時,可以更好地進(jìn)行靜態(tài)分析框咙,從而可以做Tree Shaking等優(yōu)化措施咕痛。

useBuiltIns進(jìn)行測試,使用的入口文件入下:

// main.js
import '@babel/polyfill';

var fn = (num) => num + 2;
var promise = Promise.resolve('ok');

babel配置文件如下:

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'entry',
        targets: {
          // 如果設(shè)置了targets喇嘱,那么就不會使用browserslist的配置
          chrome: '58',
        },
      },
    ],
  ],
  plugins: [],
  minified: false,
  ignore: [],
};

運(yùn)行指令npx babel main.js -o compiled.js

1.useBuiltIns設(shè)置為entry

轉(zhuǎn)換后的文件如

"use strict";

require("core-js/modules/es7.array.flat-map.js");
require("core-js/modules/es6.array.iterator.js");
require("core-js/modules/es6.array.sort.js");
require("core-js/modules/es7.object.define-getter.js");
require("core-js/modules/es7.object.define-setter.js");
require("core-js/modules/es7.object.lookup-getter.js");
require("core-js/modules/es7.object.lookup-setter.js");
require("core-js/modules/es7.promise.finally.js");
require("core-js/modules/es7.symbol.async-iterator.js");
require("core-js/modules/es7.string.trim-left.js");
require("core-js/modules/es7.string.trim-right.js");
require("core-js/modules/web.timers.js");
require("core-js/modules/web.immediate.js");
require("core-js/modules/web.dom.iterable.js");
var fn = num => num + 2;
var promise = Promise.resolve('ok');

引入了14個模塊文件茉贡。

2.useBuiltIns設(shè)置為usage

轉(zhuǎn)換后的文件如下:

"use strict";

var fn = num => num + 2;
var promise = Promise.resolve('ok');

沒有引入任何core-js的API補(bǔ)齊模塊,說明chorme v58都已經(jīng)支持了者铜。

3.useBuiltIns設(shè)置為false

轉(zhuǎn)換后的文件如下:

"use strict";

require("@babel/polyfill");
var fn = num => num + 2;
var promise = Promise.resolve('ok');

直接引入了整個@babel/polyfill包腔丧。

雖然Babel 7官方有九十多格插件,不過其中大多數(shù)都已經(jīng)整合在@babel/preset-env@babel/preset-react等預(yù)設(shè)里了作烟,我們在開發(fā)的時候直接使用預(yù)設(shè)就可以愉粤。

目前比較常用的插件只有@babel/plugin-transform-runtime

babel-polyfill

從Babel 7.4開始,官方就不推薦使用@babel/polyfill了拿撩,而是用core-jsregenerator-runtime,安裝方式入戲

npm install -D core-js regenerator-runtime

在入口文件中引入衣厘,配置如:

// main.js
import 'core-js/stable';
import "regenerator-runtime/runtime";

又或者在webpack中配置,皆可

// webpack.config.js

 const path = require('path');

 module.exports = {
  entry:['core-js/stable','regenerator-runtime/runtime','./a.js'],
  output:{
    filename:'main.js',
    path:path.resolve(__dirname,'dist')
  },
  mode:'development'
 }

如果我們的運(yùn)行環(huán)境已經(jīng)實(shí)現(xiàn)了部分ES6的功能,那么實(shí)在沒有必要引入整個polyfill影暴。我們可以將其部分引入错邦,這時需要使用Babel預(yù)設(shè)(useBuiltIns來設(shè)置)或者插件來進(jìn)行這部分引入的處理。

browserslist

browserslist叫作目標(biāo)環(huán)境配置表型宙,除了寫在package.json文件里撬呢,也可以單獨(dú)寫在工程目錄下的.browserslistrc里。browserslist是用來指定代碼最終要運(yùn)行在哪些瀏覽器或者Node.js環(huán)境里妆兑。PostCSS等可以根據(jù)我們設(shè)置的browserslist來自動判斷是否要加CSS前者,Babel也可以根據(jù)browserslist來細(xì)化判斷是否要轉(zhuǎn)成ES5魂拦。

比如chrome60瀏覽器支持箭頭函數(shù),就不需要轉(zhuǎn)成ES5的function形式搁嗓。但如果是chrome38不支持箭頭函數(shù)晨另,就需要轉(zhuǎn)換。

Babel使用的browserslist的配置功能依賴于@babel/preset-env,如果Babel沒有配置任何預(yù)設(shè)或插件谱姓,那么Babel就不會對要轉(zhuǎn)換的代碼做任何處理,會原封不動地生成與轉(zhuǎn)換前一樣的代碼刨晴。

"browserslist":[
  ">1%",
  "not ie <= 8"
]

上面配置的含義是屉来,該項(xiàng)目工程的目標(biāo)環(huán)境是市場份額大于1%的瀏覽器且不考慮IE8及以下的IE瀏覽器。

轉(zhuǎn)換過程的輔助函數(shù)

在使用Babel做語法轉(zhuǎn)換的時候狈癞,需要Babel在轉(zhuǎn)換后的代碼里注入一些函數(shù)茄靠,然后才能正常工作。
入口文件:

class Person{
  sayName () {
    return 'name'
  }
}

let john = new Person();
console.log(john);

babel配置:

module.exports = {
  presets: [
    [
      '@babel/preset-env',
    ],
  ],
  plugins: [],
  minified: false,
  ignore: [],
};

轉(zhuǎn)化后的文件(為方便閱讀蝶桶,格式化處理了慨绳,默認(rèn)輸出的文件是壓縮的):

'use strict';

function _typeof(o) {
  '@babel/helpers - typeof';
  return (
    (_typeof =
      'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
        ? function (o) {
            return typeof o;
          }
        : function (o) {
            return o &&
              'function' == typeof Symbol &&
              o.constructor === Symbol &&
              o !== Symbol.prototype
              ? 'symbol'
              : typeof o;
          }),
    _typeof(o)
  );
}
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, _toPropertyKey(descriptor.key), descriptor);
  }
}
function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, 'prototype', { writable: false });
  return Constructor;
}
function _toPropertyKey(t) {
  var i = _toPrimitive(t, 'string');
  return 'symbol' == _typeof(i) ? i : i + '';
}
function _toPrimitive(t, r) {
  if ('object' != _typeof(t) || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (void 0 !== e) {
    var i = e.call(t, r || 'default');
    if ('object' != _typeof(i)) return i;
    throw new TypeError('@@toPrimitive must return a primitive value.');
  }
  return ('string' === r ? String : Number)(t);
}
var Person = /*#__PURE__*/ (function () {
  function Person() {
    _classCallCheck(this, Person);
  }
  return _createClass(Person, [
    {
      key: 'sayName',
      value: function sayName() {
        return 'name';
      },
    },
  ]);
})();
var john = new Person();
console.log(john);


可以看到轉(zhuǎn)換后的代碼上部增加了好幾個函數(shù)聲明,這些函數(shù)是Babel轉(zhuǎn)碼時注入的真竖,我們稱之為輔助函數(shù)脐雪。在實(shí)際開發(fā)中,我們少個幾十個JS文件恢共,多則上千個JS文件战秋,如果每個轉(zhuǎn)換后的JS文件里都注入這些輔助函數(shù),會造成代碼體積的增加讨韭。一個解決方案就是把需要的輔助函數(shù)放入一個npm包中脂信,這樣只需要引入一個包,減少了冗余的代碼體積透硝。

@babel/runtime會把所有語法轉(zhuǎn)換會用到的輔助函數(shù)都集中在一起狰闪,這就是上面說的npm包。

需要安裝這一個npm包濒生,

npm install --save @babel/runtime 

以需要的_classCallCheck方法為例埋泵,我們可以在node_modules中找到

runTimeFunction.png

接下來就可以手動替換了。

but罪治,手動替換是一件多么麻煩的事情秋泄,這時Babel插件@babel/plugin-transform-runtime就派上用場了琐馆。
它可以自動移除語法轉(zhuǎn)換后內(nèi)聯(lián)的輔助函數(shù),而是使用@babel/runtime/helpers里的輔助函數(shù)來替代恒序,就減少了手動引入的麻煩瘦麸。

插件需要安裝

npm install --save-dev @babel/plugin-transform-runtime

babel.config.js中配置使用

module.exports = {
  presets: [['@babel/preset-env']],
  plugins: ['@babel/plugin-transform-runtime'],
  minified: false,
  ignore: [],
};

我們重新運(yùn)行指令

npx babel testHelper.js -o helper.js

得到的文件如下:

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var Person = /*#__PURE__*/function () {
  function Person() {
    (0, _classCallCheck2["default"])(this, Person);
  }
  return (0, _createClass2["default"])(Person, [{
    key: "sayName",
    value: function sayName() {
      return 'name';
    }
  }]);
}();
var john = new Person();
console.log(john);

可以看到,生成的代碼里自動引入了輔助函數(shù)歧胁,并且比手動引入要更有優(yōu)雅滋饲。實(shí)際在進(jìn)行前端開發(fā)的時候,除了安裝@babel/runtime包喊巍,基本也安裝@babel/plugin-transform-runtime這個Babel插件屠缭。

QA:既然每個轉(zhuǎn)換后的代碼上部都會注入一些相同的函數(shù)聲明,那么為何不用webpack一類的構(gòu)建工具去掉重復(fù)的函數(shù)聲明崭参,而是單獨(dú)引入一個輔助函數(shù)包呵曹?

這是因?yàn)閣ebpack在構(gòu)建的時候,是基于模塊來做去重工作的何暮。每一個函數(shù)聲明都是引用類型奄喂。在堆內(nèi)存不同的空間存放,缺少唯一的地址來找到它們海洼。 所以webpack本身做不到把每個文件中的相同函數(shù)聲明去重跨新。因此,我們需要單獨(dú)的輔助函數(shù)包坏逢,這樣webpack打包的時候會基于模塊來做去重工作域帐。

@babel/plugin-transform-runtime有如下三大作用:

  1. 自動移除語法轉(zhuǎn)換后內(nèi)聯(lián)的輔助函數(shù)(inline Babel helpers),而是使用@babel/runtime/helpers里的輔助函數(shù)來替代是整,就減少了手動引入的麻煩肖揣。

  2. 當(dāng)代碼里使用了core-js的api時,自動引入@babel/runtime-corejs3/core-js-stabel/,以此來替換全局引入的core-js/stable

  3. 當(dāng)代碼里使用了Generator時浮入,自動引入@babel/runtime/regenerator,以此來替換全局引入的regenerator-runtime/runtime
    作用二和作用三其實(shí)都是在作API轉(zhuǎn)換许饿,目的是對內(nèi)置對象進(jìn)行重命名,以防止污染全局環(huán)境舵盈。

在我們引入babel-polyfill的時候陋率,或者引入了core-js/stableregenerator-runtime/runtime兩個包,來做全局的API補(bǔ)齊,但這同時可能產(chǎn)生一個問題秽晚,就是對運(yùn)行環(huán)境造成污染瓦糟。例如polyfill重寫了window.Promise及其原型鏈。

在我們不希望重新window.Promise的時候赴蝇,我們就可以使用@babel/plugin-transform-runtime進(jìn)行轉(zhuǎn)換菩浙。

對轉(zhuǎn)化前的代碼如:

async function test() {
  await 1;
}

若使用babel-polyfill來轉(zhuǎn)換,轉(zhuǎn)換后的代碼如下:

"use strict";

function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
require("core-js/modules/es6.object.define-property.js");
require("core-js/modules/es6.symbol.js");
require("core-js/modules/es6.string.iterator.js");
require("core-js/modules/es6.array.iterator.js");
require("core-js/modules/web.dom.iterable.js");
require("core-js/modules/es7.symbol.async-iterator.js");
require("core-js/modules/es6.object.create.js");
require("core-js/modules/es6.object.get-prototype-of.js");
require("core-js/modules/es6.array.for-each.js");
require("core-js/modules/es6.function.name.js");
require("core-js/modules/es6.object.set-prototype-of.js");
require("core-js/modules/es6.array.slice.js");
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.promise.js");
function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
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 test() {
  return _test.apply(this, arguments);
}
function _test() {
  _test = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
    var obj;
    return _regeneratorRuntime().wrap(function _callee$(_context) {
      while (1) switch (_context.prev = _context.next) {
        case 0:
          obj = Promise.resolve('ok');
          return _context.abrupt("return", obj);
        case 2:
        case "end":
          return _context.stop();
      }
    }, _callee);
  }));
  return _test.apply(this, arguments);
}

若我們開啟@babel/plugin-transform-runtime的API轉(zhuǎn)換,得到代碼如下:

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.promise.js");
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
function test() {
  return _test.apply(this, arguments);
}
function _test() {
  _test = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
    var obj;
    return _regenerator["default"].wrap(function _callee$(_context) {
      while (1) switch (_context.prev = _context.next) {
        case 0:
          obj = Promise.resolve('ok');
          return _context.abrupt("return", obj);
        case 2:
        case "end":
          return _context.stop();
      }
    }, _callee);
  }));
  return _test.apply(this, arguments);
}


使用的babel.config.js的配置如下:

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
      }
    ],
  ],
  plugins: [['@babel/plugin-transform-runtime', {
    'helper': true,// 是否自動引入輔助函數(shù),默認(rèn)true
    'core.js': false,// 默認(rèn)false,如果需要使用core-js2或者3做轉(zhuǎn)換劲蜻,這里配置成2或者3
    'regenerator': true,// 默認(rèn)值是true
    'useESModules': true,// 使用使用ES6的模塊化語法陆淀,默認(rèn)是false,在用webpack一類的構(gòu)建工具時,可以設(shè)置為true,方便做靜態(tài)分析
    'absoluteRuntime': false,//用來自定義@babel/plugin-transform-runtime引入@babel/runtime/模塊的路徑規(guī)則先嬉,取值是布爾值或者字符串轧苫。一般沒有特殊要求,保持默認(rèn)值false即可
    'version': '7',// 和@babel/runtime及其進(jìn)化版@babel/runtime-corejs2疫蔓、@babel/runtime-corejs3的版本對應(yīng)含懊,填寫任意一個包的版本即可(依據(jù)package.json文件),填寫版本號主要是可以減少打包體積
  }]],
};

對比可以看出

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

@babel/plugin-transform-runtime替換掉了一些core.js包的引入
其實(shí)衅胀,api轉(zhuǎn)換主要是給開發(fā)JS庫或者npm包的人使用的岔乔,前端工程例一般仍然使用polyfill來補(bǔ)齊API。

對于@babel/runtime及其進(jìn)化版@babel/runtime-corejs2@babel/runtime-corejs3,依據(jù)需要安裝一個即可滚躯。如果不需要對core-js做API轉(zhuǎn)換雏门,那么安裝@babel/runtime并把corejs配置項(xiàng)設(shè)置成false。

核心包@babel/core

無論是用命令行掸掏,還是通過webpack進(jìn)行轉(zhuǎn)碼,底層都是通過Node.js來調(diào)用@babel/core相關(guān)功能的API來實(shí)現(xiàn)的茁影。

使用如下代碼做測試:

let babelCore = require('@babel/core');
let es6core = `var fn = (num)=> num + 2`;

let options = {
  presets:["@babel/env"]
}

let result = babelCore.transform(es6core, options);

console.log(result);
console.log('-------------');
console.log('-------------');
console.log(result.code);

運(yùn)行可以看到


transform.png

調(diào)用transform方法后生成的結(jié)果是一個對象,該對象的code屬性就是轉(zhuǎn)碼后的結(jié)果阅束。

babel原理

babel轉(zhuǎn)碼過程

babel的轉(zhuǎn)碼過程主要由三個階段組成:解析(parse)、轉(zhuǎn)換(transform)茄唐、生成(generator),這三個階段分別由@babel/parser,@babel/core,@babel/generator來完成息裸。

舉個例子
轉(zhuǎn)換前的代碼

let name = 'Jack';

解析階段,會轉(zhuǎn)換成如下結(jié)構(gòu)

{
  "標(biāo)識符":"var",
  "變量名":"name",
  "變量值":"Jack"
}

轉(zhuǎn)換階段,將let是ES6中語法沪编,把它轉(zhuǎn)換成ES5的var呼盆,而其他部分保持不變,轉(zhuǎn)換后如下

{
  "標(biāo)識符":"let",
  "變量名":"name",
  "變量值":"Jack"
}

到了生成階段蚁廓,我們把轉(zhuǎn)換后的結(jié)構(gòu)還原成JS代碼

var name = 'Jack'

babel轉(zhuǎn)碼分析

  1. 解析階段
    該階段由Babel讀取源碼并生成抽象語法書(AST),該階段由兩部分組成:詞法分析與語法分析访圃。詞法分析會將字符串形式的代碼轉(zhuǎn)換成token流,語法分析會將tokens流轉(zhuǎn)換成AST相嵌。

  2. 轉(zhuǎn)換階段
    上一個階段完成了解析工作腿时,生成了AST,AST是一個樹狀的JSON結(jié)構(gòu)饭宾。接下來就可以通過Babel插件度該樹狀結(jié)構(gòu)執(zhí)行修改操作批糟,修改完成后就得到了新的AST。

  3. 生成階段
    通過轉(zhuǎn)換階段的工作看铆,我們得到了新的AST徽鼎。在生成階段,對AST的樹狀JSON結(jié)構(gòu)進(jìn)行還原,生成新的JS代碼否淤。

以上三個階段重點(diǎn)是第二個階段(轉(zhuǎn)換階段)悄但,該階段使用不同的Babel插件會得到不同的AST,也就意味著最終會生成不同的JS代碼石抡。在開發(fā)工作中檐嚣,主要工作也是選擇合適的Babel插件或者預(yù)設(shè)。

自定義babel插件

創(chuàng)建一個babelPlugins文件夾汁雷,里面會含demo.js,animalToDog.jsbabel.config.js文件净嘀,
demo文件內(nèi)容如下

let animal = 'hello world';

animalToDog.js是的插件,babel插件本身是第一個JS函數(shù)侠讯,文件內(nèi)容如下

// babel插件本質(zhì)上就是一個函數(shù)
module.exports = function({ types: t }) {
  return {
    name:'animalToDog',// 插件的名稱
    visitor: {// visitor對象是遍歷AST各個節(jié)點(diǎn)的方法
      Identifier(path,state) {
        if (path.node.name === 'animal') {
          path.node.name = 'dog';
        }
      },
      VariableDeclaration (path, state) {
        // state.opts是插件的傳參
        if (path.node.kind === 'let' && state.opts.ES5 == true) {
          path.node.kind = 'var';
        }
      }
    }
  }
}

可以看到其對外導(dǎo)出了一個函數(shù)

babel.config.js文件內(nèi)容如下

// babel.config.js
module.exports = {
  plugins: [['./animalToDog.js', {
    ES5:false,
  }]],
}

運(yùn)行指令npx babel demo.js -o after.js(需要先安裝@babel/cli挖藏、@babel/core)

得到的after.js文件內(nèi)容如下

// after.js
var dog = 'hello world';

可以看到我們成功把animal改成了dog,把let改成了var.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市厢漩,隨后出現(xiàn)的幾起案子膜眠,更是在濱河造成了極大的恐慌,老刑警劉巖溜嗜,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宵膨,死亡現(xiàn)場離奇詭異,居然都是意外死亡炸宵,警方通過查閱死者的電腦和手機(jī)辟躏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來土全,“玉大人捎琐,你說我怎么就攤上這事」祝” “怎么了瑞凑?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長概页。 經(jīng)常有香客問我籽御,道長,這世上最難降的妖魔是什么惰匙? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任技掏,我火速辦了婚禮,結(jié)果婚禮上项鬼,老公的妹妹穿的比我還像新娘零截。我一直安慰自己迎捺,他們只是感情好哈蝇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布般贼。 她就那樣靜靜地躺著,像睡著了一般自娩。 火紅的嫁衣襯著肌膚如雪绑榴。 梳的紋絲不亂的頭發(fā)上朵逝,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天淳蔼,我揣著相機(jī)與錄音,去河邊找鬼撤嫩。 笑死偎捎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的序攘。 我是一名探鬼主播茴她,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼程奠!你這毒婦竟也來了丈牢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤瞄沙,失蹤者是張志新(化名)和其女友劉穎己沛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體距境,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡申尼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了垫桂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片师幕。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诬滩,靈堂內(nèi)的尸體忽然破棺而出霹粥,到底是詐尸還是另有隱情,我是刑警寧澤碱呼,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布蒙挑,位于F島的核電站宗侦,受9級特大地震影響愚臀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜矾利,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一姑裂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧男旗,春花似錦舶斧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泽台。三九已至,卻和暖如春矾缓,著一層夾襖步出監(jiān)牢的瞬間怀酷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工嗜闻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜕依,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓琉雳,卻偏偏與公主長得像样眠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子翠肘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容