du blog
Hello, welcome to my blog
transform-runtime使用
created: Jan 11 21updated: Jan 22 21

为什么要用transform-runtime

这个插件解决的问题:

抽离重用babel转换代码中的辅助代码,以减少构建代码的尺寸.

example

index.js / index2.js:

1 class Person2{ 2 constructor(){ 3 this.name=13 4 } 5 } 6

babel.config.json:

1 { 2 "presets": [ 3 [ 4 "@babel/preset-env", 5 { 6 "targets": { 7 "edge": "17", 8 "firefox": "60", 9 "chrome": "50", 10 "safari": "11.1", 11 "ie":"11" 12 }, 13 "useBuiltIns": "usage", 14 "corejs":{ 15 "version":3 16 } 17 } 18 ] 19 ] 20 } 21

transform result:

1 "use strict"; 2 require("core-js/modules/es.function.name.js"); 3 4 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 5 6 var Person = function Person() { 7 _classCallCheck(this, Person); 8 this.name = 123; 9 }; 10

注意:两个文件都是这样的.

加入transform-runtime:

confog:

1 + "plugins":[ 2 + "@babel/plugin-transform-runtime", 3 + ] 4

transform result:

1 "use strict"; 2 var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 3 4 require("core-js/modules/es.function.name.js"); 5 6 var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); 7 8 var Person = function Person() { 9 (0, _classCallCheck2.default)(this, Person); 10 this.name = 123; 11 }; 12

可以看到, _classCallCheck 这个 function 已经改为从 @babel/runtime 这个包导入了.

install

安装为开发依赖

1 npm install --save-dev @babel/plugin-transform-runtime 2

还需要安装运行程序的包为生产依赖

1 npm install --save @babel/runtime 2

@babel/plugin-transform-runtime只是在转换的时候使用,但是转换之后的代码是依赖于@babel/runtime的

支持的重要功能

1.抽离运行环境的辅助代码

参数:helpers, 默认为true classCallCheck, extends, etc.

babel默认会将辅助代码放在每个文件顶部,避免在文件中的重复使用,但是这会在每个文件中添加重复的代码,transform-runtime 会将每个文件的 helpers 改为 @babel/runtime/helpers 的引用

example: 上边的第一个例子就是抽离运行环境的赋值代码

2.自动切换regenerator全局导入为@babel/runtime/regenerator

参数:regenerator, 默认为true

这个我自己使用的时候会有些问题,到最后会总结一下,这里先给出官方的例子(参考babel的测试用例)

example1:

index.js

1 function* foo() {} 2

config:

1 { "plugins": ["@babel/plugin-transform-regenerator"]} 2

transform result:

1 var _marked = /*#__PURE__*/regeneratorRuntime.mark(foo); 2 function foo() { 3 return regeneratorRuntime.wrap(function foo$(_context) { 4 while (1) switch (_context.prev = _context.next) { 5 case 0: 6 case "end": 7 return _context.stop(); 8 } 9 }, _marked); 10 } 11

增加transform-runtime:

config:

1 { 2 "plugins": [ 3 "@babel/plugin-transform-runtime", 4 "@babel/plugin-transform-regenerator" 5 ] 6 } 7

transform result:

1 import _regeneratorRuntime from "@babel/runtime/regenerator"; 2 3 var _marked = /*#__PURE__*/_regeneratorRuntime.mark(foo); 4 5 function foo() { 6 return _regeneratorRuntime.wrap(function foo$(_context) { 7 while (1) switch (_context.prev = _context.next) { 8 case 0: 9 case "end": 10 return _context.stop(); 11 } 12 }, _marked); 13 } 14

区别在于使用了之后增加了对于regenerator的单独导出, 但是单独使用 @babel/plugin-transform-regenerator 的时候没有导入全局的generator环境

问题是 我们一般在项目中都不会一个一个配置plugin,而是直接使用preset. 下边就preset-evn配置的例子.

example2:

config:

1 { 2 "presets": [ 3 [ 4 "@babel/preset-env", 5 { 6 "targets": { 7 "edge": "17", 8 "firefox": "60", 9 "chrome": "50", 10 "safari": "11.1", 11 "ie":"10" 12 }, 13 "useBuiltIns": "usage", 14 "corejs":{ 15 "version":3 16 } 17 } 18 ] 19 ] 20 } 21

transform result:

1 "use strict"; 2 require("regenerator-runtime/runtime.js"); 3 4 var _marked = /*#__PURE__*/regeneratorRuntime.mark(foo); 5 6 function foo() { 7 return regeneratorRuntime.wrap(function foo$(_context) { 8 while (1) { 9 switch (_context.prev = _context.next) { 10 case 0: 11 case "end": 12 return _context.stop(); 13 } 14 } 15 }, _marked); 16 } 17

这里导入了全局的generator运行环境的代码 应该是根据目标浏览器是否支持而决定是否转换和导入的.

加入transform-runtime:

1 + "plugins": [ 2 + "@babel/plugin-transform-runtime" 3 + ] 4

transform result:

1 "use strict"; 2 3 var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 5 var _regeneratorRuntime2 = require("@babel/runtime/regenerator"); 6 7 var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); 8 9 require("regenerator-runtime/runtime.js"); 10 11 var _marked = /*#__PURE__*/_regeneratorRuntime2.mark(foo); 12 13 function foo() { 14 return _regenerator.default.wrap(function foo$(_context) { 15 while (1) { 16 switch (_context.prev = _context.next) { 17 case 0: 18 case "end": 19 return _context.stop(); 20 } 21 } 22 }, _marked); 23 } 24

可以看到已经使用了从具名的导入,但是全局的generator导入并没有删除掉

3.沙盒环境的croe-js

参数: corejs, 默认为false

如果直接使用croe-js,从中导出,会污染全局变量,如果代码是需要发布给别人使用的库,则会产生不可预知的问题.

example:

index.js

1 Promise.resolve() 2

config

1 { 2 "plugins": [ 3 [ 4 "@babel/plugin-transform-runtime", 5 { 6 "corejs":3 7 } 8 ] 9 ] 10 } 11

transform result

1 import _Promise from "@babel/runtime-corejs3/core-js-stable/promise"; 2 _Promise.resolve(); 3

感觉这个API与preset-env是有冲突的,平时大家都在使用preset-env做这些事情,如果我们也使用preset-env的时候

config:

1 { 2 "presets": [ 3 [ 4 "@babel/preset-env", 5 { 6 "targets": { 7 "edge": "17", 8 "firefox": "60", 9 "chrome": "50", 10 "safari": "11.1", 11 "ie":"10" 12 }, 13 "useBuiltIns": "usage", 14 "corejs":{ 15 "version":3 16 } 17 } 18 ] 19 ], 20 "plugins": [ 21 [ 22 "@babel/plugin-transform-runtime", 23 { 24 "corejs":3 25 } 26 ] 27 ] 28 } 29

transform result

1 "use strict"; 2 3 var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); 4 5 require("core-js/modules/es.object.to-string.js"); 6 7 require("core-js/modules/es.promise.js"); 8 9 var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); 10 11 _promise.default.resolve(); 12

全局的与别名的都使用了, 在babel 的issues也有人提出这样的问题 https://github.com/babel/babel/issues/10271

options

corejs

value: false, 2, 3 or { version: 2 | 3, proposals: boolean }, defaults to false.

example: ['@babel/plugin-transform-runtime', { corejs: 3 }]

指定一个数字为 croe-js 的 major 版本, 需要注意的是 core-js 2 版本只支持全局变量(例如: Promise)和静态属性(例如: Array.from), 而3版本也支持实例属性(例如: [].includes)

默认情况下transform-runtime 不会polyfill 提案, 如果使用croe-js 3 则可以通过 proposal: true 开启。

这个选项的更改需要提供必要的运行时的程序依赖

这里是必须要安装的 因为transform-runtime的依赖是dev依赖 在安装的时候不会自动安装runtime

helpers

default: true

是否将内联的辅助代码替换为对 moduleName 的调用

regenerator

default: true

将generator的运行环境,切换为不污染全局变量的导入形式.

useESModules

default: false

example:

index.js

1 class Person { 2 constructor() { 3 this.name=123 4 } 5 } 6

config

1{ 2 "plugins": [ 3 [ 4 "@babel/plugin-transform-runtime", 5 { 6 "corejs":3 7 } 8 ], 9 "@babel/plugin-transform-classes" 10 ] 11} 12

transform result

1import _classCallCheck from "@babel/runtime-corejs3/helpers/classCallCheck"; 2 3 4let Person = function Person() { 5 _classCallCheck(this, Person); 6 7 8 this.name = 123; 9}; 10

加上这个配置

config

1{ 2 "plugins": [ 3 [ 4 "@babel/plugin-transform-runtime", 5 { 6 "corejs":3, 7+ "useESModules":true 8 } 9 ], 10 "@babel/plugin-transform-classes" 11 ] 12} 13

transform result

1import _classCallCheck from "@babel/runtime-corejs3/helpers/esm/classCallCheck"; 2 3let Person = function Person() { 4 _classCallCheck(this, Person); 5 6 7 this.name = 123; 8}; 9

这里可以看到 改变的辅助代码导出的路径 为true的是是从esm目录导出来的, 而区别在于在esm目录中是使用es语法导出的 在普通路径中是使用commonjs导出的 在某些情况下可能有作用, 比如webpack的tree-shaking

absoluteRuntime

这个没太看明白,看样子是和预转换相关的,也不太常用,需要的话可以研读一下文档

version

默认情况下 transform-runtime假定已经安装了@babel/rumtime(@babel/runtime-corejs) 7版本 如果安装了更高的版本 或者列为依赖项,这里可以使用最新的版本

注: 其实没太明白 导入的时候还能导入不同的版本么

最后的问题

大致就是 transform-runtime 与 preset-env 冲突的问题, 在官方的测试用例中, 都是

1 { 2 "plugins": [ 3 "@babel/plugin-transform-runtime", 4 "@babel/plugin-transform-regenerator" 5 ] 6 } 7

类似的例子

与preset-env 配合使用的问题在上边的例子中都有展现, 具体的解决方法可以看这个讨论: https://github.com/babel/babel/discussions/12601

当前所使用版本:

1 "@babel/cli": "^7.12.10", 2 "@babel/core": "^7.12.10", 3 "@babel/plugin-proposal-decorators": "^7.12.12", 4 "@babel/plugin-transform-arrow-functions": "^7.12.1", 5 "@babel/plugin-transform-classes": "^7.12.1", 6 "@babel/plugin-transform-runtime": "^7.12.10", 7 "@babel/preset-env": "^7.12.11", 8 "@babel/runtime": "^7.12.5", 9 "babel-plugin-polyfill-corejs3": "^0.1.0", 10 "core-js": "^3.8.1", 11 "lodash-es": "^4.17.20", 12 "@babel/plugin-transform-regenerator": "^7.12.1" 13