前言
前面我們用了一篇很長(zhǎng)的文章介紹了@babel/preset-env,感興趣的可以去看我之前的一篇文章babel源碼解析之(@babel/preset-env),今天我們要分析的是babel的一個(gè)插件瞧筛,叫@babel/plugin-transform-runtime.
簡(jiǎn)介
我們看一下官網(wǎng)對(duì)它的描述:
A plugin that enables the re-use of Babel's injected helper code to save on codesize.
很簡(jiǎn)短的一個(gè)描述信息,翻譯一下大概是:“抽離babel的一些公共工具類用來(lái)減少代碼的大小”赖舟,雖然描述很少齐饮,但是理解起來(lái)好像比較抽象宋欺,下面我們一起結(jié)合demo一步步分析一下。
開始
我們還是繼續(xù)使用我們前面的demo項(xiàng)目
我們先安裝一下@babel/plugin-transform-runtime插件迷殿,
npm install -D @babel/plugin-transform-runtime
然后我們?cè)趕rc目錄底下創(chuàng)建一個(gè)demo.runtime.js用來(lái)測(cè)試鹃彻,
src/demo.runtime.js:
const fn = () => {};
new Promise(() => {});
class Test {
say(){}
}
const c = [1, 2, 3].includes(1);
var a = 10;
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
可以看到郊闯,除了之前的一些代碼外,我們還加入了一個(gè)es6的generator函數(shù)蛛株,我們直接用一下@babel/plugin-transform-runtime插件团赁,然后用它的默認(rèn)設(shè)置,
babel.config.js:
module.exports = {
plugins: [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
};
我們運(yùn)行babel編譯看結(jié)果:
? babel-demo git:(v0.0.1) ? npx babel ./src/demo.runtime.js -o ./lib/demo.runtime.js
lib/demo.runtime.js:
const fn = () => {};
new Promise(() => {});
class Test {
say() {}
}
const c = [1, 2, 3].includes(1);
var a = 10;
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
可以看到谨履,經(jīng)過(guò)runtime插件處理后代碼并沒(méi)有改變欢摄,這是為什么呢?因?yàn)樵谖覀價(jià)untime插件的配置中我們默認(rèn)是關(guān)閉掉一些功能的笋粟,比如我們把runtime的corejs打開怀挠,
babel.config.js:
module.exports = {
plugins: [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
};
再次運(yùn)行看結(jié)果:
? babel-demo git:(v0.0.1) ? npx babel ./src/demo.runtime.js
import _Promise from "@babel/runtime-corejs2/core-js/promise";
const fn = () => {};
new _Promise(() => {});
class Test {
say() {}
}
const c = [1, 2, 3].includes(1);
var a = 10;
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
? babel-demo git:(v0.0.1) ?
可以看到析蝴,自動(dòng)幫我們引入了一個(gè)polyfill(_Promise),那小伙伴要疑問(wèn)了绿淋,es6的語(yǔ)法沒(méi)轉(zhuǎn)換闷畸?是的! 因?yàn)閞untime不做這些語(yǔ)法的轉(zhuǎn)換吞滞,它只能算是一個(gè)轉(zhuǎn)換幫助類佑菩、一個(gè)自動(dòng)添加polyfill的工具,es6語(yǔ)法轉(zhuǎn)換我們上一節(jié)用了preset-env裁赠,所以我們把preset-env加上殿漠,然后把polyfill去掉,最后runtime配置還原到默認(rèn)配置,
babel.config.js:
module.exports = {
presets:[
[
"@babel/preset-env"
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
};
再次運(yùn)行babel看效果:
? babel-demo git:(v0.0.1) ? npx babel ./src/demo.runtime.js -o ./lib/demo.runtime.js
lib/demo.runtime.js:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regeneratorRuntime2 = require("@babel/runtime/regenerator");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _marked = /*#__PURE__*/_regeneratorRuntime2.mark(helloWorldGenerator);
var fn = function fn() {};
new Promise(function () {});
var Test = /*#__PURE__*/function () {
function Test() {
(0, _classCallCheck2.default)(this, Test);
}
(0, _createClass2.default)(Test, [{
key: "say",
value: function say() {}
}]);
return Test;
}();
var c = [1, 2, 3].includes(1);
var a = 10;
function helloWorldGenerator() {
return _regenerator.default.wrap(function helloWorldGenerator$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'hello';
case 2:
_context.next = 4;
return 'world';
case 4:
return _context.abrupt("return", 'ending');
case 5:
case "end":
return _context.stop();
}
}
}, _marked);
}
看結(jié)果也看不出什么佩捞,那runtime到底為我們做了什么呢绞幌?我們?cè)囈幌氯绻覀儾皇褂胷untime插件,直接使用preset-env看結(jié)果:
babel.config.js
module.exports = {
presets:[
[
"@babel/preset-env"
]
],
plugins: [
// [
// "@babel/plugin-transform-runtime",
// {
// "absoluteRuntime": false,
// "corejs": false,
// "helpers": true,
// "regenerator": true,
// "useESModules": false,
// "version": "7.0.0-beta.0"
// }
// ]
]
};
運(yùn)行babel看結(jié)果:
"use strict";
var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);
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 fn = function fn() {};
new Promise(function () {});
var Test = /*#__PURE__*/function () {
function Test() {
_classCallCheck(this, Test);
}
_createClass(Test, [{
key: "say",
value: function say() {}
}]);
return Test;
}();
var c = [1, 2, 3].includes(1);
var a = 10;
function helloWorldGenerator() {
return regeneratorRuntime.wrap(function helloWorldGenerator$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'hello';
case 2:
_context.next = 4;
return 'world';
case 4:
return _context.abrupt("return", 'ending');
case 5:
case "end":
return _context.stop();
}
}
}, _marked);
}
ok一忱! 可以看到莲蜘,在沒(méi)有使用runtime的時(shí)候,我們的_classCallCheck帘营、_defineProperties菇夸、_createClass都是在當(dāng)前代碼中,如果使用了runtime后仪吧,這些方法都會(huì)直接從@babel/runtime/helpers中導(dǎo)入:
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regeneratorRuntime2 = require("@babel/runtime/regenerator");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _marked = /*#__PURE__*/_regeneratorRuntime2.mark(helloWorldGenerator);
所以,如果當(dāng)我們有很多需要編譯的文件的時(shí)候鞠眉,每個(gè)文件中都會(huì)有這些方法的定義薯鼠,這樣整個(gè)包就會(huì)很大,runtime把這些方法抽離到一個(gè)公共的地方械蹋,所以可以讓我們打包出來(lái)的源碼變小出皇。
配置
corejs
false
, 2
, 3
or { version: 2 | 3, proposals: boolean }
, defaults to false
.
比如:['@babel/plugin-transform-runtime', { corejs: 3 }]
corejs是可以讓當(dāng)前環(huán)境支持es的最新特性的api墊片(polyfill),在babel之前版本在用@babel/polyfill
哗戈,從7.4.0版本后就用core-js代替了polyfill郊艘,比如我們之前在代碼中加入全部的polyfill的是這樣的:
import "@babel/polyfill";
換成core-js后可以是這樣的:
import 'core-js/stable';
import 'regenerator-runtime/runtime';
所以core-js是包含了polyfill的特性,更多的core-js內(nèi)容大家可以看官網(wǎng)https://github.com/zloirock/core-js
這里的corejs配置的就是我們將要使用的runtime-corejs的版本唯咬,有2跟3的版本纱注,2版本是3之前的版本,所以3有一些es最新的一些特性胆胰,比如我們demo中的Array.prototy.includes方法狞贱,只有core-js3上才有:
var c = [1, 2, 3].includes(1);
選用corejs的版本 | Install command |
---|---|
false |
npm install --save @babel/runtime |
2 |
npm install --save @babel/runtime-corejs2 |
3 |
npm install --save @babel/runtime-corejs3 |
為了方便更好的分析,我們直接安裝一下runtime-core2跟runtime-core3:
npm install -D @babel/runtime-corejs2 && npm install -D @babel/runtime-corejs3
我們修改一下我們demo項(xiàng)目的配置文件蜀涨,然后先把corejs改成2瞎嬉,
babel.config.js:
module.exports = {
presets:[
[
"@babel/preset-env"
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2,
}
]
]
};
然后我運(yùn)行babel看效果:
? babel-demo git:(v0.0.1) ? npx babel ./src/demo.runtime.js -o ./lib/demo.runtime.js
lib/demo.runtime.js:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _regeneratorRuntime2 = require("@babel/runtime-corejs2/regenerator");
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var _marked = /*#__PURE__*/_regeneratorRuntime2.mark(helloWorldGenerator);
var fn = function fn() {};
new _promise.default(function () {});
var Test = /*#__PURE__*/function () {
function Test() {
(0, _classCallCheck2.default)(this, Test);
}
(0, _createClass2.default)(Test, [{
key: "say",
value: function say() {}
}]);
return Test;
}();
var c = [1, 2, 3].includes(1);
var a = 10;
function helloWorldGenerator() {
return _regenerator.default.wrap(function helloWorldGenerator$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'hello';
case 2:
_context.next = 4;
return 'world';
case 4:
return _context.abrupt("return", 'ending');
case 5:
case "end":
return _context.stop();
}
}
}, _marked);
}
可以看到蝎毡,只幫我們加了一個(gè)_promise(Promise的polyfill),我們并沒(méi)看到Array.prototype.includes的墊片氧枣。
我們修改一下配置文件沐兵,把corejs的版本改成3,
babel.config.js:
module.exports = {
presets:[
[
"@babel/preset-env"
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3,
}
]
]
};
再次運(yùn)行看結(jié)果便监,
lib/demo.runtime.js:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _context;
var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);
var fn = function fn() {};
new _promise.default(function () {});
var Test = /*#__PURE__*/function () {
function Test() {
(0, _classCallCheck2.default)(this, Test);
}
(0, _createClass2.default)(Test, [{
key: "say",
value: function say() {}
}]);
return Test;
}();
var c = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 1);
var a = 10;
function helloWorldGenerator() {
return _regenerator.default.wrap(function helloWorldGenerator$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return 'hello';
case 2:
_context2.next = 4;
return 'world';
case 4:
return _context2.abrupt("return", 'ending');
case 5:
case "end":
return _context2.stop();
}
}
}, _marked);
}
可以看到corejs3給我們添加了一個(gè)includes方法當(dāng)成了polyfill扎谎,如果看過(guò)之前preset-env那篇文章的同學(xué)可能會(huì)發(fā)現(xiàn)了,用transform-runtime插件添加的polyfill都是帶有 "_"符號(hào)的變量(可以看成局部變量)茬贵,是不會(huì)污染全局變量的簿透,我們?cè)賮?lái)回顧一下preset-env,我們修改一下配置文件解藻,把runtime插件去掉老充,然后開啟preset-env的polyfill,preset-env的內(nèi)容不懂的小伙伴可以看我之前的那篇文章哦螟左,
babel.config.js:
module.exports = {
presets:[
[
"@babel/preset-env",
{
corejs: 3,
useBuiltIns: "usage"
}
]
],
plugins: [
// [
// "@babel/plugin-transform-runtime",
// {
// "corejs": 3,
// }
// ]
]
};
運(yùn)行看效果啡浊,
lib/demo.runtime.js:
"use strict";
require("core-js/modules/es.array.includes");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
require("regenerator-runtime/runtime");
var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);
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 fn = function fn() {};
new Promise(function () {});
var Test = /*#__PURE__*/function () {
function Test() {
_classCallCheck(this, Test);
}
_createClass(Test, [{
key: "say",
value: function say() {}
}]);
return Test;
}();
var c = [1, 2, 3].includes(1);
var a = 10;
function helloWorldGenerator() {
return regeneratorRuntime.wrap(function helloWorldGenerator$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'hello';
case 2:
_context.next = 4;
return 'world';
case 4:
return _context.abrupt("return", 'ending');
case 5:
case "end":
return _context.stop();
}
}
}, _marked);
}
可以看到,首先的效果跟runtime插件是一樣的胶背,但是preset-env加的polyfill是直接導(dǎo)入corejs然后替換掉全局變量的居灯,這樣會(huì)造成全局變量的污染。
好啦翘鸭,我們順便把runtime插件跟preset-env的區(qū)別都給講了贴见,下面我們結(jié)合babel的源碼具體分析一下transfrom-runtime插件是怎樣結(jié)合@babel/runtime還有corejs對(duì)我們代碼進(jìn)行轉(zhuǎn)換的。
packages/babel-plugin-transform-runtime/src/index.js:
export default declare((api, options, dirname) => {
api.assertVersion(7);
const {
corejs,
helpers: useRuntimeHelpers = true,
regenerator: useRuntimeRegenerator = true,
useESModules = false,
version: runtimeVersion = "7.0.0-beta.0",
absoluteRuntime = false,
} = options;
let proposals = false;
let rawVersion;
//如果傳遞的是corejs: {version:3,proposals:true}對(duì)象類型的時(shí)候就拆分version跟proposals字段
if (typeof corejs === "object" && corejs !== null) {
rawVersion = corejs.version;
proposals = Boolean(corejs.proposals);
} else {
rawVersion = corejs;
}
//獲取corejs版本號(hào)
const corejsVersion = rawVersion ? Number(rawVersion) : false;
//校驗(yàn)版本號(hào)
if (![false, 2, 3].includes(corejsVersion)) {
throw new Error(
`The \`core-js\` version must be false, 2 or 3, but got ${JSON.stringify(
rawVersion,
)}.`,
);
}
//校驗(yàn)proposals參數(shù)只能出現(xiàn)在corejsVersion版本為3的情況
if (proposals && (!corejsVersion || corejsVersion < 3)) {
throw new Error(
"The 'proposals' option is only supported when using 'corejs: 3'",
);
}
...
/*
如果是core3版本的話就依賴“@babel/runtime-corejs3”
如果是core2版本的話就依賴“@babel/runtime-corejs2”
默認(rèn)是依賴“@babel/runtime”
*/
const moduleName = injectCoreJS3
? "@babel/runtime-corejs3"
: injectCoreJS2
? "@babel/runtime-corejs2"
: "@babel/runtime";
/*
如果是core3版本并且開啟提案選項(xiàng)的時(shí)候就會(huì)把corejs的根目錄設(shè)置為“core-js”(包含了最新提案的core-js)
反之會(huì)將corejs的根目錄設(shè)置為“core-js-stable”(穩(wěn)定版本的core-js)
*/
const corejsRoot = injectCoreJS3 && !proposals ? "core-js-stable" : "core-js";
...
}
ok红且,我們看到這里:
/*
如果是core3版本并且開啟提案選項(xiàng)的時(shí)候就會(huì)把corejs的根目錄設(shè)置為“core-js”(包含了最新提案的core-js)
反之會(huì)將corejs的根目錄設(shè)置為“core-js-stable”(穩(wěn)定版本的core-js)
*/
const corejsRoot = injectCoreJS3 && !proposals ? "core-js-stable" : "core-js";
我們沒(méi)有將proposals設(shè)置為true的時(shí)候我們看一下編譯結(jié)果坝茎,
babel.config.js:
module.exports = {
presets:[
[
"@babel/preset-env",
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3,
}
]
]
};
lib/demo.runtime.js:
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
...
可以看到,runtime插件幫我們安裝的polyfill都是依賴的core-js-stable版本的corejs,如果我們將proposals設(shè)置為true我們看一下效果暇番,
babel.config.js:
module.exports = {
presets: [
[
"@babel/preset-env",
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": {version: 3, proposals: true},
}
]
]
};
lib/demo.runtime.js:
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));
...
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/promise"));
可以看到嗤放,當(dāng)設(shè)置proposals為true的時(shí)候,runtime插件依賴的是core-js目錄的polyfill壁酬,我們分別點(diǎn)開“core-js-stable”跟“core-js”的promise目錄看一下有什么區(qū)別次酌,
首先是“core-js-stable”的“@babel/runtime-corejs3/core-js-stable/promise”,
xxxbabel-demo/node_modules/@babel/runtime-corejs3/core-js-stable/promise.js:
module.exports = require("core-js-pure/stable/promise");
然后是“core-js”的“@babel/runtime-corejs3/core-js/promise”舆乔,
xxx/babel-demo/node_modules/@babel/runtime-corejs3/core-js/promise.js:
module.exports = require("core-js-pure/features/promise");
可以看到岳服,都是引用了“core-js-pure”,那么“core-js-pure”又是啥呢希俩?其實(shí)是core-js的另外一個(gè)版本派阱,叫:“純凈的core-js”,也就是說(shuō)不會(huì)污染全局變量的意思斜纪,具體小伙伴可以看core-js的官網(wǎng)里面有詳細(xì)說(shuō)明的贫母。
都是依賴的“core-js-pure”但是下級(jí)目錄就不一樣了文兑,一個(gè)是“stable”一個(gè)是“features”,我們繼續(xù)往下看腺劣,找到這兩個(gè)文件绿贞,
node_modules/core-js-pure/features/promise/index.js:
var parent = require('../../es/promise');
require('../../modules/esnext.aggregate-error');
// TODO: Remove from `core-js@4`
require('../../modules/esnext.promise.all-settled');
require('../../modules/esnext.promise.try');
require('../../modules/esnext.promise.any');
module.exports = parent;
node_modules/core-js-pure/es/promise/index.js:
require('../../modules/es.object.to-string');
require('../../modules/es.string.iterator');
require('../../modules/web.dom-collections.iterator');
require('../../modules/es.promise');
require('../../modules/es.promise.all-settled');
require('../../modules/es.promise.finally');
var path = require('../../internals/path');
module.exports = path.Promise;
可以看到,feature的少了很多內(nèi)容橘原,然后還有依賴了一些“esnext”打頭的模塊籍铁,“esnext”打頭的也就是說(shuō)下一個(gè)es版本中可能會(huì)出現(xiàn)的一些內(nèi)容(處于stage階段,還不怎么穩(wěn)定)趾断。
ok拒名!我們了解corejs2跟3的區(qū)別,然后還分析了proposals參數(shù)芋酌,當(dāng)runtime插件拿到了我們的corejs之后又是怎樣動(dòng)態(tài)的注入到我們的代碼中的呢增显?
比如我們“src/demo.runtime.js”文件中有一個(gè)Promise,那么runtime是怎么注入的呢脐帝?看過(guò)前面preset-env文章的童鞋應(yīng)該是多多少少有點(diǎn)感覺了同云,其實(shí)就是遍歷ast的節(jié)點(diǎn),然后遍歷到Promise的時(shí)候動(dòng)態(tài)的添加上polyfill代碼堵腹,也就是一下代碼:
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
我們看一下源碼炸站,
Xxxx/babel-demo/node_modules/@babel/plugin-transform-runtime/lib/index.js:
...
visitor: {
ReferencedIdentifier(path) {
const {
node,
parent,
scope
} = path;
const {
name
} = node;
if (name === "regeneratorRuntime" && useRuntimeRegenerator) {
path.replaceWith(this.addDefaultImport(`${modulePath}/regenerator`, "regeneratorRuntime"));
return;
}
if (!injectCoreJS) return;
if (_core.types.isMemberExpression(parent)) return;
if (!hasMapping(BuiltIns, name)) return;
if (scope.getBindingIdentifier(name)) return;
path.replaceWith(this.addDefaultImport(`${modulePath}/${corejsRoot}/${BuiltIns[name].path}`, name));
...
之前寫過(guò)一篇文章介紹過(guò)babel的源碼,然后最后還自定義了一個(gè)插件疚顷,babel源碼解析一旱易,插件返回的就是一個(gè)ast節(jié)點(diǎn)遍歷的鉤子函數(shù),也就是說(shuō)babel在遍歷每一個(gè)節(jié)點(diǎn)的時(shí)候會(huì)觸發(fā)對(duì)應(yīng)插件的鉤子函數(shù)腿堤,也就是說(shuō)當(dāng)解析到"src/demo.runtime.js"中的這段代碼的時(shí)候:
new Promise(function () {});
會(huì)走上面runtime插件的ReferencedIdentifier方法咒唆,然后把當(dāng)前節(jié)點(diǎn)傳過(guò)來(lái),
...
visitor: {
ReferencedIdentifier(path) {
const {
node,
parent,
scope
} = path;
const {
name
} = node;
//如果有g(shù)enerator函數(shù)并且useRuntimeRegenerator設(shè)置為true的時(shí)候就添加generatorRuntime的polyfill释液,
if (name === "regeneratorRuntime" && useRuntimeRegenerator) {
path.replaceWith(this.addDefaultImport(`${modulePath}/regenerator`, "regeneratorRuntime"));
return;
}
//corejs為false就不添加polyfill直接返回
if (!injectCoreJS) return
if (_core.types.isMemberExpression(parent)) return;
//看當(dāng)前corejs中有沒(méi)有“Promise”的墊片polyfill
if (!hasMapping(BuiltIns, name)) return;
if (scope.getBindingIdentifier(name)) return;
//添加promise polyfill路徑為
//"@babel/runtime-corejs3/core-js-stable/promise"
path.replaceWith(this.addDefaultImport(`${modulePath}/${corejsRoot}/${BuiltIns[name].path}`, name));
...
可以看到,如果有g(shù)enerator函數(shù)并且“useRuntimeRegenerator”設(shè)置為“true”的時(shí)候就添加generatorRuntime的polyfill装处,“useRuntimeRegenerator”選項(xiàng)我們下面再說(shuō)误债,然后當(dāng)corejs選項(xiàng)不為false的時(shí)候就按照前面說(shuō)的路徑去添加“Promise”的polyfill代碼。
helpers
& useESModules
helpers: boolean
, defaults to true
.
是否運(yùn)行runtime插件添加babel的helpers函數(shù)妄迁,比如我們的classCallCheck寝蹈、extends方法等等,默認(rèn)是開啟的登淘。
useESModules: boolean, defaults to
true`.
是否在添加esm方式的helpers函數(shù)的時(shí)候箫老,默認(rèn)是根據(jù)babel的配置來(lái)選擇。
我們測(cè)試一下這兩個(gè)參數(shù)黔州,
src/demo.runtime.js:
const fn = () => {};
new Promise(() => {});
class Test {
say(){}
}
const c = [1, 2, 3].includes(1);
var a = 10;
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
babel.config.js:
module.exports = {
presets: [
[
"@babel/preset-env",
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: {version: 3, proposals: true},
helpers: true,
useESModules: false
}
]
]
};
運(yùn)行看結(jié)果耍鬓,
lib/demo.runtime.js:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
...
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
...
可以看到阔籽,當(dāng)helpers為true然后useESModules為false的時(shí)候會(huì)添加一些helper函數(shù),比如我們的createClass跟classCallCheck等等牲蜀,都是從corejs3的helpers目錄下直接取模塊笆制,如果我們把useESModules設(shè)置為true,我們看一下效果涣达,
babel.config.js:
module.exports = {
presets: [
[
"@babel/preset-env",
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: {version: 3, proposals: true},
helpers: true,
useESModules: true
}
]
]
};
運(yùn)行代碼看效果在辆,
lib/demo.runtime.js:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/createClass"));
...
可以看到,useESModules設(shè)置為true的時(shí)候會(huì)從helpers的esm目錄加載對(duì)應(yīng)的模塊度苔,這就是useESModules配置的作用匆篓。
下面我們分析一下源碼,
packages/babel-plugin-transform-runtime/src/index.js:
return {
name: "transform-runtime",
pre(file) {
//是否開啟了helpers選項(xiàng)
if (useRuntimeHelpers) {
file.set("helperGenerator", name => {
//看當(dāng)前helper是否在可用
if (
file.availableHelper &&
!file.availableHelper(name, runtimeVersion)
) {
return;
}
const isInteropHelper = HEADER_HELPERS.indexOf(name) !== -1;
const blockHoist =
isInteropHelper && !isModule(file.path) ? 4 : undefined;
//根據(jù)useESModules配置選擇加載helper的目錄
//useESModules: false(默認(rèn)為helpers)
//useESModules: true(helpers/esm)
const helpersDir =
esModules && file.path.node.sourceType === "module"
? "helpers/esm"
: "helpers";
return this.addDefaultImport(
`${modulePath}/${helpersDir}/${name}`,
name,
blockHoist,
);
});
}
const cache = new Map();
this.addDefaultImport = (source, nameHint, blockHoist) => {
// If something on the page adds a helper when the file is an ES6
// file, we can't reused the cached helper name after things have been
// transformed because it has almost certainly been renamed.
const cacheKey = isModule(file.path);
const key = `${source}:${nameHint}:${cacheKey || ""}`;
let cached = cache.get(key);
if (cached) {
cached = t.cloneNode(cached);
} else {
cached = addDefault(file.path, source, {
importedInterop: "uncompiled",
nameHint,
blockHoist,
});
cache.set(key, cached);
}
return cached;
};
},
OK寇窑!代碼中有注釋鸦概,我就不詳細(xì)說(shuō)明了。
regenerator
boolean
, defaults to true
.
是否開啟添加regenerator
函數(shù)的polyfill防止全局污染疗认。
描述不是很好理解哈完残,別怕,我們結(jié)合demo跟源碼來(lái)分析横漏,首先我們把regenerator
選項(xiàng)關(guān)閉(false)谨设,然后看一下我們demo中的編譯情況,
src/demo.runtime.js:
const fn = () => {};
new Promise(() => {});
class Test {
say(){}
}
const c = [1, 2, 3].includes(1);
var a = 10;
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
可以看到缎浇,我們?cè)创a中有一個(gè)generator函數(shù)叫“helloWorldGenerator”,然后我關(guān)閉regenerator
babel.config.js:
module.exports = {
presets: [
[
"@babel/preset-env",
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: {version: 3, proposals: true},
helpers: true,
useESModules: true,
regenerator: false
}
]
]
};
運(yùn)行看結(jié)果扎拣,
lib/demo.runtime.js:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/createClass"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/promise"));
var _context;
var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);
var fn = function fn() {};
new _promise.default(function () {});
var Test = /*#__PURE__*/function () {
function Test() {
(0, _classCallCheck2.default)(this, Test);
}
(0, _createClass2.default)(Test, [{
key: "say",
value: function say() {}
}]);
return Test;
}();
var c = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 1);
var a = 10;
function helloWorldGenerator() {
return regeneratorRuntime.wrap(function helloWorldGenerator$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return 'hello';
case 2:
_context2.next = 4;
return 'world';
case 4:
return _context2.abrupt("return", 'ending');
case 5:
case "end":
return _context2.stop();
}
}
}, _marked);
}
可以看到,我們的helloWorldGenerator函數(shù)被preset-env改造過(guò)后變成了:
var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);
function helloWorldGenerator() {
return regeneratorRuntime.wrap(function helloWorldGenerator$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return 'hello';
case 2:
_context2.next = 4;
return 'world';
case 4:
return _context2.abrupt("return", 'ending');
case 5:
case "end":
return _context2.stop();
}
}
}, _marked);
}
由于preset-env沒(méi)有開啟polyfill選項(xiàng)素跺,然后runtime插件又關(guān)閉了regenerator選項(xiàng)二蓝,所以我們的regeneratorRuntime對(duì)象并沒(méi)有被注入,所以我們打開我們的regenerator選項(xiàng)再試試指厌,
babel.config.js:
module.exports = {
presets: [
[
"@babel/preset-env",
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: {version: 3, proposals: true},
helpers: true,
useESModules: true,
regenerator: true
}
]
]
};
運(yùn)行看效果刊愚,
demo.runtime.js:
...
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);
function helloWorldGenerator() {
return _regenerator.default.wrap(function helloWorldGenerator$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return 'hello';
case 2:
_context2.next = 4;
return 'world';
case 4:
return _context2.abrupt("return", 'ending');
case 5:
case "end":
return _context2.stop();
}
}
}, _marked);
}
...
可以看到,當(dāng)開啟了regenerator選項(xiàng)的時(shí)候踩验,runtime會(huì)自動(dòng)的注入一個(gè)_regenerator對(duì)象鸥诽,用來(lái)替換我們之前的regeneratorRuntime對(duì)象,并且不會(huì)像preset-env一樣會(huì)污染全局箕憾,
以下是“preset-env”添加的regenerator polyfill
require("regenerator-runtime/runtime");
var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);
function helloWorldGenerator() {
return regeneratorRuntime.wrap(function helloWorldGenerator$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'hello';
case 2:
_context.next = 4;
return 'world';
case 4:
return _context.abrupt("return", 'ending');
case 5:
case "end":
return _context.stop();
}
}
}, _marked);
}
ok牡借!我們的regenerator參數(shù)就講到這里了,下面我們看一下源碼中的操作袭异,
packages/babel-plugin-transform-runtime/src/index.js:
visitor: {
ReferencedIdentifier(path) {
const { node, parent, scope } = path;
const { name } = node;
// transform `regeneratorRuntime`
if (name === "regeneratorRuntime" && useRuntimeRegenerator) {
path.replaceWith(
this.addDefaultImport(
`${modulePath}/regenerator`,
"regeneratorRuntime",
),
);
return;
}
可以看到钠龙,源碼中當(dāng)讀到"regeneratorRuntime"變量的時(shí)候,就替換掉"regeneratorRuntime"變量改為以下代碼:
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);
absoluteRuntime
boolean
or string
, defaults to false
.
設(shè)置runtime插件從哪個(gè)目錄導(dǎo)入helpers跟polyfill,默認(rèn)是:@babel/runtime-corejs3碴里、@babel/runtime-corejs2或者@babel/runtime沈矿,你也可以設(shè)置其它的路徑,我們用一下看效果:
babel.config.js:
module.exports = {
presets: [
[
"@babel/preset-env",
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: {version: 3, proposals: true},
helpers: true,
useESModules: true,
regenerator: true,
absoluteRuntime: "./node_modules"
}
]
]
};
運(yùn)行看效果:
lib/demo.runtime.js
"use strict";
var _interopRequireDefault = require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/regenerator"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));
var _classCallCheck2 = _interopRequireDefault(require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/helpers/esm/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/helpers/esm/createClass"));
var _promise = _interopRequireDefault(require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/core-js/promise"));
var _context;
var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);
var fn = function fn() {};
new _promise.default(function () {});
var Test = /*#__PURE__*/function () {
function Test() {
(0, _classCallCheck2.default)(this, Test);
}
(0, _createClass2.default)(Test, [{
key: "say",
value: function say() {}
}]);
return Test;
}();
var c = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 1);
var a = 10;
function helloWorldGenerator() {
return _regenerator.default.wrap(function helloWorldGenerator$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return 'hello';
case 2:
_context2.next = 4;
return 'world';
case 4:
return _context2.abrupt("return", 'ending');
case 5:
case "end":
return _context2.stop();
}
}
}, _marked);
}
可以看到并闲,大部分的polyfill跟helpers函數(shù)都變成“xxx/babel-demo/node_modules/@babel/runtime-corejs3/xxx”细睡,也就是說(shuō)runtime插件可以讓用戶指定truntime依賴的位置,轉(zhuǎn)換過(guò)后就變成一個(gè)絕對(duì)路徑了帝火。
version
runtime中corejs的版本溜徙,比如現(xiàn)在我們的@babel/runtime-corejs2的7.0.1之前是沒(méi)有Math的一些方法的,那么如果你的version值設(shè)置的是<=7.0.0的時(shí)候runtime插件就不會(huì)Math的一些方法給加進(jìn)來(lái)的犀填。
packages/babel-plugin-transform-runtime/src/index.js:
const { BuiltIns, StaticProperties, InstanceProperties } = (injectCoreJS2
? getCoreJS2Definitions
: getCoreJS3Definitions)(runtimeVersion);
packages/babel-plugin-transform-runtime/src/runtime-corejs2-definitions.js:
export default runtimeVersion => {
// Conditionally include 'Math' because it was not included in the 7.0.0
// release of '@babel/runtime'. See issue https://github.com/babel/babel/pull/8616.
...
const includeMathModule = hasMinVersion("7.0.1", runtimeVersion);
...(includeMathModule
? {
Math: {
acosh: { stable: true, path: "math/acosh" },
asinh: { stable: true, path: "math/asinh" },
atanh: { stable: true, path: "math/atanh" },
cbrt: { stable: true, path: "math/cbrt" },
clz32: { stable: true, path: "math/clz32" },
cosh: { stable: true, path: "math/cosh" },
expm1: { stable: true, path: "math/expm1" },
fround: { stable: true, path: "math/fround" },
hypot: { stable: true, path: "math/hypot" },
imul: { stable: true, path: "math/imul" },
log10: { stable: true, path: "math/log10" },
log1p: { stable: true, path: "math/log1p" },
log2: { stable: true, path: "math/log2" },
sign: { stable: true, path: "math/sign" },
sinh: { stable: true, path: "math/sinh" },
tanh: { stable: true, path: "math/tanh" },
trunc: { stable: true, path: "math/trunc" },
},
}
: {}),
...
}
OK蠢壹,我們的@babel/plugin-transform-runtime全部?jī)?nèi)容就已經(jīng)解析完畢了。