前言
前面我們用了一篇很長的文章介紹了@babel/preset-env,感興趣的可以去看我之前的一篇文章babel源碼解析之(@babel/preset-env),今天我們要分析的是babel的一個插件,叫@babel/plugin-transform-runtime.
簡介
我們看一下官網對它的描述:
A plugin that enables the re-use of Babel's injected helper code to save on codesize.
很簡短的一個描述信息,翻譯一下大概是:“抽離babel的一些公共工具類用來減少代碼的大小”,雖然描述很少,但是理解起來好像比較抽象,下面我們一起結合demo一步步分析一下。
開始
我們還是繼續使用我們前面的demo項目
我們先安裝一下@babel/plugin-transform-runtime插件,
npm install -D @babel/plugin-transform-runtime
然后我們在src目錄底下創建一個demo.runtime.js用來測試,
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';
}
可以看到,除了之前的一些代碼外,我們還加入了一個es6的generator函數,我們直接用一下@babel/plugin-transform-runtime插件,然后用它的默認設置,
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"
}
]
]
};
我們運行babel編譯看結果:
? 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';
}
可以看到,經過runtime插件處理后代碼并沒有改變,這是為什么呢?因為在我們runtime插件的配置中我們默認是關閉掉一些功能的,比如我們把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"
}
]
]
};
再次運行看結果:
? 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) ?
可以看到,自動幫我們引入了一個polyfill(_Promise),那小伙伴要疑問了,es6的語法沒轉換?是的! 因為runtime不做這些語法的轉換,它只能算是一個轉換幫助類、一個自動添加polyfill的工具,es6語法轉換我們上一節用了preset-env,所以我們把preset-env加上,然后把polyfill去掉,最后runtime配置還原到默認配置,
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"
}
]
]
};
再次運行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);
}
看結果也看不出什么,那runtime到底為我們做了什么呢?我們試一下如果我們不使用runtime插件,直接使用preset-env看結果:
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"
// }
// ]
]
};
運行babel看結果:
"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! 可以看到,在沒有使用runtime的時候,我們的_classCallCheck、_defineProperties、_createClass都是在當前代碼中,如果使用了runtime后,這些方法都會直接從@babel/runtime/helpers中導入:
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);
所以,如果當我們有很多需要編譯的文件的時候,每個文件中都會有這些方法的定義,這樣整個包就會很大,runtime把這些方法抽離到一個公共的地方,所以可以讓我們打包出來的源碼變小。
配置
corejs
false
, 2
, 3
or { version: 2 | 3, proposals: boolean }
, defaults to false
.
比如:['@babel/plugin-transform-runtime', { corejs: 3 }]
corejs是可以讓當前環境支持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內容大家可以看官網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項目的配置文件,然后先把corejs改成2,
babel.config.js:
module.exports = {
presets:[
[
"@babel/preset-env"
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2,
}
]
]
};
然后我運行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);
}
可以看到,只幫我們加了一個_promise(Promise的polyfill),我們并沒看到Array.prototype.includes的墊片。
我們修改一下配置文件,把corejs的版本改成3,
babel.config.js:
module.exports = {
presets:[
[
"@babel/preset-env"
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3,
}
]
]
};
再次運行看結果,
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給我們添加了一個includes方法當成了polyfill,如果看過之前preset-env那篇文章的同學可能會發現了,用transform-runtime插件添加的polyfill都是帶有 "_"符號的變量(可以看成局部變量),是不會污染全局變量的,我們再來回顧一下preset-env,我們修改一下配置文件,把runtime插件去掉,然后開啟preset-env的polyfill,preset-env的內容不懂的小伙伴可以看我之前的那篇文章哦,
babel.config.js:
module.exports = {
presets:[
[
"@babel/preset-env",
{
corejs: 3,
useBuiltIns: "usage"
}
]
],
plugins: [
// [
// "@babel/plugin-transform-runtime",
// {
// "corejs": 3,
// }
// ]
]
};
運行看效果,
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是直接導入corejs然后替換掉全局變量的,這樣會造成全局變量的污染。
好啦,我們順便把runtime插件跟preset-env的區別都給講了,下面我們結合babel的源碼具體分析一下transfrom-runtime插件是怎樣結合@babel/runtime還有corejs對我們代碼進行轉換的。
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}對象類型的時候就拆分version跟proposals字段
if (typeof corejs === "object" && corejs !== null) {
rawVersion = corejs.version;
proposals = Boolean(corejs.proposals);
} else {
rawVersion = corejs;
}
//獲取corejs版本號
const corejsVersion = rawVersion ? Number(rawVersion) : false;
//校驗版本號
if (![false, 2, 3].includes(corejsVersion)) {
throw new Error(
`The \`core-js\` version must be false, 2 or 3, but got ${JSON.stringify(
rawVersion,
)}.`,
);
}
//校驗proposals參數只能出現在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”
默認是依賴“@babel/runtime”
*/
const moduleName = injectCoreJS3
? "@babel/runtime-corejs3"
: injectCoreJS2
? "@babel/runtime-corejs2"
: "@babel/runtime";
/*
如果是core3版本并且開啟提案選項的時候就會把corejs的根目錄設置為“core-js”(包含了最新提案的core-js)
反之會將corejs的根目錄設置為“core-js-stable”(穩定版本的core-js)
*/
const corejsRoot = injectCoreJS3 && !proposals ? "core-js-stable" : "core-js";
...
}
ok,我們看到這里:
/*
如果是core3版本并且開啟提案選項的時候就會把corejs的根目錄設置為“core-js”(包含了最新提案的core-js)
反之會將corejs的根目錄設置為“core-js-stable”(穩定版本的core-js)
*/
const corejsRoot = injectCoreJS3 && !proposals ? "core-js-stable" : "core-js";
我們沒有將proposals設置為true的時候我們看一下編譯結果,
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設置為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"));
可以看到,當設置proposals為true的時候,runtime插件依賴的是core-js目錄的polyfill,我們分別點開“core-js-stable”跟“core-js”的promise目錄看一下有什么區別,
首先是“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”又是啥呢?其實是core-js的另外一個版本,叫:“純凈的core-js”,也就是說不會污染全局變量的意思,具體小伙伴可以看core-js的官網里面有詳細說明的。
都是依賴的“core-js-pure”但是下級目錄就不一樣了,一個是“stable”一個是“features”,我們繼續往下看,找到這兩個文件,
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的少了很多內容,然后還有依賴了一些“esnext”打頭的模塊,“esnext”打頭的也就是說下一個es版本中可能會出現的一些內容(處于stage階段,還不怎么穩定)。
ok!我們了解corejs2跟3的區別,然后還分析了proposals參數,當runtime插件拿到了我們的corejs之后又是怎樣動態的注入到我們的代碼中的呢?
比如我們“src/demo.runtime.js”文件中有一個Promise,那么runtime是怎么注入的呢?看過前面preset-env文章的童鞋應該是多多少少有點感覺了,其實就是遍歷ast的節點,然后遍歷到Promise的時候動態的添加上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));
...
之前寫過一篇文章介紹過babel的源碼,然后最后還自定義了一個插件,babel源碼解析一,插件返回的就是一個ast節點遍歷的鉤子函數,也就是說babel在遍歷每一個節點的時候會觸發對應插件的鉤子函數,也就是說當解析到"src/demo.runtime.js"中的這段代碼的時候:
new Promise(function () {});
會走上面runtime插件的ReferencedIdentifier方法,然后把當前節點傳過來,
...
visitor: {
ReferencedIdentifier(path) {
const {
node,
parent,
scope
} = path;
const {
name
} = node;
//如果有generator函數并且useRuntimeRegenerator設置為true的時候就添加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;
//看當前corejs中有沒有“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));
...
可以看到,如果有generator函數并且“useRuntimeRegenerator”設置為“true”的時候就添加generatorRuntime的polyfill,“useRuntimeRegenerator”選項我們下面再說,然后當corejs選項不為false的時候就按照前面說的路徑去添加“Promise”的polyfill代碼。
helpers
& useESModules
helpers: boolean
, defaults to true
.
是否運行runtime插件添加babel的helpers函數,比如我們的classCallCheck、extends方法等等,默認是開啟的。
useESModules: boolean, defaults to
true`.
是否在添加esm方式的helpers函數的時候,默認是根據babel的配置來選擇。
我們測試一下這兩個參數,
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
}
]
]
};
運行看結果,
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"));
...
可以看到,當helpers為true然后useESModules為false的時候會添加一些helper函數,比如我們的createClass跟classCallCheck等等,都是從corejs3的helpers目錄下直接取模塊,如果我們把useESModules設置為true,我們看一下效果,
babel.config.js:
module.exports = {
presets: [
[
"@babel/preset-env",
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: {version: 3, proposals: true},
helpers: true,
useESModules: true
}
]
]
};
運行代碼看效果,
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設置為true的時候會從helpers的esm目錄加載對應的模塊,這就是useESModules配置的作用。
下面我們分析一下源碼,
packages/babel-plugin-transform-runtime/src/index.js:
return {
name: "transform-runtime",
pre(file) {
//是否開啟了helpers選項
if (useRuntimeHelpers) {
file.set("helperGenerator", name => {
//看當前helper是否在可用
if (
file.availableHelper &&
!file.availableHelper(name, runtimeVersion)
) {
return;
}
const isInteropHelper = HEADER_HELPERS.indexOf(name) !== -1;
const blockHoist =
isInteropHelper && !isModule(file.path) ? 4 : undefined;
//根據useESModules配置選擇加載helper的目錄
//useESModules: false(默認為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!代碼中有注釋,我就不詳細說明了。
regenerator
boolean
, defaults to true
.
是否開啟添加regenerator
函數的polyfill防止全局污染。
描述不是很好理解哈,別怕,我們結合demo跟源碼來分析,首先我們把regenerator
選項關閉(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';
}
可以看到,我們源代碼中有一個generator函數叫“helloWorldGenerator”,然后我關閉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
}
]
]
};
運行看結果,
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函數被preset-env改造過后變成了:
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沒有開啟polyfill選項,然后runtime插件又關閉了regenerator選項,所以我們的regeneratorRuntime對象并沒有被注入,所以我們打開我們的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: true
}
]
]
};
運行看效果,
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);
}
...
可以看到,當開啟了regenerator選項的時候,runtime會自動的注入一個_regenerator對象,用來替換我們之前的regeneratorRuntime對象,并且不會像preset-env一樣會污染全局,
以下是“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參數就講到這里了,下面我們看一下源碼中的操作,
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;
}
可以看到,源碼中當讀到"regeneratorRuntime"變量的時候,就替換掉"regeneratorRuntime"變量改為以下代碼:
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);
absoluteRuntime
boolean
or string
, defaults to false
.
設置runtime插件從哪個目錄導入helpers跟polyfill,默認是:@babel/runtime-corejs3、@babel/runtime-corejs2或者@babel/runtime,你也可以設置其它的路徑,我們用一下看效果:
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"
}
]
]
};
運行看效果:
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函數都變成“xxx/babel-demo/node_modules/@babel/runtime-corejs3/xxx”,也就是說runtime插件可以讓用戶指定truntime依賴的位置,轉換過后就變成一個絕對路徑了。
version
runtime中corejs的版本,比如現在我們的@babel/runtime-corejs2的7.0.1之前是沒有Math的一些方法的,那么如果你的version值設置的是<=7.0.0的時候runtime插件就不會Math的一些方法給加進來的。
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全部內容就已經解析完畢了。