前面我们用了一篇很长的文章介绍了@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.jslib/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.jslib/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把这些方法抽离到一个公共的地方,所以可以让我们打包出来的源码变小。
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 commandfalsenpm install --save @babel/runtime2npm install --save @babel/runtime-corejs23npm 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.jslib/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: boolean, defaults to true.
是否运行runtime插件添加babel的helpers函数,比如我们的classCallCheck、extends方法等等,默认是开启的。
useESModules: boolean, defaults totrue`.
是否在添加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!代码中有注释,我就不详细说明了。
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);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依赖的位置,转换过后就变成一个绝对路径了。
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全部内容就已经解析完毕了。
demo项目
vv_小虫 认证博客专家 JavaScript CSS 前端框架 6 年开发经验,前端架构师,目前主要负责企业级应用前端技术平台建设工作,在前端工程化实现、Node 应用开发、Android 技术、Vue 技术、React 技术、移动开发等方向有丰富实践。