babel源码解析之(@babel/plugin-transfo
前言
前面我们用了一篇很长的文章介绍了@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全部内容就已经解析完毕了。