Babel的定義
babel是一個工具集袱结,主要含@babel/cli
亮隙、@babel/core
、babel/preset-env
這三個包垢夹。這個工具集是圍繞@babel/core
這個核心npm包構(gòu)成的溢吻。每次@babel/core
發(fā)布新版本的時候,整個工具集的其他npm包也都會跟著升級到與@babel/core
相同的版本果元,即使它們的代碼可能一行都沒有改變促王。
-
@babel/cli
是Babel命令行轉(zhuǎn)碼工具,如果我們使用命令行進(jìn)行Babel轉(zhuǎn)碼就需要安裝它 -
@babel/core
是Babel的核心包而晒,包含了Babel的轉(zhuǎn)碼API -
@babel/preset-env
這個npm包提供了ES6轉(zhuǎn)ES5的語法轉(zhuǎn)換規(guī)則蝇狼,我們需要再Babel配置文件里指定使用它。如果不使用的話倡怎,也可以完成轉(zhuǎn)碼迅耘,但轉(zhuǎn)碼后的代碼任然是ES6代碼,相當(dāng)于沒有轉(zhuǎn)碼。
Babel的主要工作
- 語法轉(zhuǎn)換
- 補(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包分蓖。
- @babel/preset-env
- @babel/preset-react
- @babel/preset-typescript
- @babel/preset-flow
plugins插件數(shù)組和presets預(yù)設(shè)數(shù)組是有順序要求的尔艇。如果兩個插件或預(yù)設(shè)都要處理同一個代碼片段,那么會根據(jù)插件和預(yù)設(shè)的順序來執(zhí)行么鹤,規(guī)則如下:
- 插件比預(yù)設(shè)先執(zhí)行
- 插件執(zhí)行順序是插件數(shù)組元素從前向后一次執(zhí)行
- 預(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-js
和regenerator-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中找到
接下來就可以手動替換了。
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
有如下三大作用:
自動移除語法轉(zhuǎn)換后內(nèi)聯(lián)的輔助函數(shù)(inline Babel helpers),而是使用
@babel/runtime/helpers
里的輔助函數(shù)來替代是整,就減少了手動引入的麻煩肖揣。當(dāng)代碼里使用了core-js的api時,自動引入
@babel/runtime-corejs3/core-js-stabel/
,以此來替換全局引入的core-js/stable
當(dāng)代碼里使用了
Generator
時浮入,自動引入@babel/runtime/regenerator
,以此來替換全局引入的regenerator-runtime/runtime
作用二和作用三其實(shí)都是在作API轉(zhuǎn)換许饿,目的是對內(nèi)置對象進(jìn)行重命名,以防止污染全局環(huán)境舵盈。
在我們引入babel-polyfill的時候陋率,或者引入了core-js/stable
和regenerator-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)行可以看到
調(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)碼分析
解析階段
該階段由Babel讀取源碼并生成抽象語法書(AST),該階段由兩部分組成:詞法分析與語法分析访圃。詞法分析會將字符串形式的代碼轉(zhuǎn)換成token流,語法分析會將tokens流轉(zhuǎn)換成AST相嵌。轉(zhuǎn)換階段
上一個階段完成了解析工作腿时,生成了AST,AST是一個樹狀的JSON結(jié)構(gòu)饭宾。接下來就可以通過Babel插件度該樹狀結(jié)構(gòu)執(zhí)行修改操作批糟,修改完成后就得到了新的AST。生成階段
通過轉(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.js
和babel.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
.