babel 测试记录
babel 是什么?
babel 是由插件构成的。
执行命令:
npm install --save-dev @babel/core @babel/cli @babel/preset-env创建 babel.config.json(需要 bable 版本 7.8.0 以上)
{
"presets": [
[
"@babel/preset-env",
{
"targets": "chrome 44",
"useBuiltIns": "usage",
"corejs": "3.27.1"
}
]
]
}然后执行命令,把所有在 src 下目录的代码编译到 lib 目录下。
npx babel src --out-dir lib配置 preset-env 的 useBuiltIns 为 usage
测试 Arry.fill
以 Arry.fill 函数为例,chrome 最低支持版本 45
babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": "chrome 44",
"useBuiltIns": "usage"
}
]
]
}main.js
const array1 = [1, 2, 3, 4];
console.log(array1.fill(6));打包 npx babel src --out-dir lib。
控制台会输出这么句话。 意思就是叫我们,当 useBuiltIns 设置为 usage 或则 entry 时最好指定 corejs 选项版本。(在 package.json 里 core-js 的版本。)否则 corejs 默认指定 2.x 版本。
打包后的文件。
"use strict";
require("core-js/modules/es.array.fill.js");
var array1 = [1, 2, 3, 4];
console.log(array1.fill(6));经过以下测试,得出结论:
当配置 preset-env 的选项 targets(例如设置为 chrome 44),useBuiltIns 为 usage,babel 会分析你代码使用到的新特性,动态引入目标浏览器缺失的 polyfill。这种方式会修改原型上的方法。
测试 Promise
测试 Promise
ie 所有版本都不支持 Promise。
配置文件。
{
"presets": [
[
"@babel/preset-env",
{
"targets": "ie 11",
"useBuiltIns": "usage"
}
]
]
}main.js
console.log(Promise);打包。可以看到引入了相关模块。
"use strict";
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
console.log(Promise);测试实例方法 "foobar".includes("foo")
测试实例方法 "foobar".includes("foo")
chrome 支持字符串实例方法 inclueds 的最低版本为 41。
{
"presets": [
[
"@babel/preset-env",
{
"targets": "chrome 40",
"useBuiltIns": "usage"
}
]
]
}main.js
console.log("foobar".includes("foo"));打包
"use strict";
require("core-js/modules/es.string.includes.js");
console.log("foobar".includes("foo"));测试数组的 flat 方法
测试数组的 flat 方法。
chrome 支持数组的 flat 方法最低版本为 69
{
"presets": [
[
"@babel/preset-env",
{
"targets": "chrome 68",
"useBuiltIns": "usage"
}
]
]
}main.js
const arr = [1, 2, 3, [1, 2, 3]]
console.log(arr.flat());打包后。
"use strict";
var arr = [1, 2, 3, [1, 2, 3]];
console.log(arr.flat());然而,打包后并没有引入 flat 的相关模块。
接下来修改配置
{
"presets": [
[
"@babel/preset-env",
{
"targets": "chrome 40",
"useBuiltIns": "usage",
+ "corejs": "3"
}
]
]
}再次打包,发现就会引入 flat 的相关模块。
"use strict";
require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.unscopables.flat.js");
var arr = [1, 2, 3, [1, 2, 3]];
console.log(arr.flat());得出结论,有些较新的 api 只有 core-js@3 才有,core-js@2 没有。所以推荐使用 core-js@3。
配置 preset-env 的 useBuiltIns 为 entry
先说结论,设置 preset-env 的 useBuiltins 为 entry 时,需要我们自己手动引入全部的 polyfill。
然后 preset-env 会根据 target 来引入目标浏览器不支持的全部特性。不会分析用户写的代码来按需引入特性。
配置
{
"presets": [
[
"@babel/preset-env",
{
+ "targets": "chrome 70",
+ "useBuiltIns": "entry",
"corejs": "3",
"modules": "auto"
}
]
]
}main.js
// stable 代表稳定特性
+ import "core-js/stable"
test()
async function test() {
console.log('你说多少钱来着?');
const res = await say()
console.log(res);
}
function say() {
return new Promise(resolve => {
setTimeout(() => {
resolve(100)
}, 1000);
})
}注:官网有写这么一段话:
If you are compiling generators or async function to ES5, and you are using a version of @babel/core or @babel/plugin-transform-regenerator older than 7.18.0, you must also load the regenerator runtime package. It is automatically loaded when using @babel/preset-env's useBuiltIns: "usage" option or @babel/plugin-transform-runtime
翻译过来大概是:如果你正在将 generators 或者 async 函数编译为 es5 能识别的语言,并且你使用的 @babel/core 包或者 @babel/plugin-transform-regenerator 包的版本小于 7.18.0,那么你还必须加载 regenerator runtime 包(import "regenerator-runtime/runtime")。于是我想测试如果包版本小于 7.18.0 并且不引入 regenerator runtime 包会发生什么错误。可好像什么问题都不会有。但我看见网上还有很多教程,都告诉你要同时引入两个包:
import "core-js/stable";
// 最新版的 babel 不引入这个包也没问题。
import "regenerator-runtime/runtime";最后我想说的是,有小伙伴能测试出来当使用的 @babel/core 包或者 @babel/plugin-transform-regenerator 包的版本小于 7.18.0 时,并且不引入 regenerator runtime 包会发生什么错误吗?还请大佬告知。虽然为了保险引入也没关系。这个包体积很小。
打包后,可以看到引入了很多 polyfill 函数。
"use strict";
require("core-js/modules/es.array.reduce.js");
require("core-js/modules/es.array.reduce-right.js");
require("core-js/modules/es.array.unscopables.flat.js");
require("core-js/modules/es.array.unscopables.flat-map.js");
require("core-js/modules/es.math.hypot.js");
require("core-js/modules/es.object.from-entries.js");
require("core-js/modules/es.regexp.flags.js");
require("core-js/modules/es.typed-array.set.js");
require("core-js/modules/es.typed-array.sort.js");
require("core-js/modules/web.immediate.js");
require("core-js/modules/web.queue-microtask.js");
require("core-js/modules/web.url.to-json.js");
test();
async function test() {
console.log('你说多少钱来着?');
const res = await say();
console.log(res);
}
function say() {
return new Promise(resolve => {
setTimeout(() => {
resolve(100);
}, 1000);
});
}执行打包后的文件,1s 后成功打印 100
修改下配置
{
"presets": [
[
"@babel/preset-env",
{
- "targets": "chrome 70",
+ "targets": "chrome 100",
"useBuiltIns": "entry",
"corejs": "3",
"modules": "auto"
}
]
]
}重新打包,可以看到引入的 polyfill 少了很多,只有两个了。
"use strict";
require("core-js/modules/es.regexp.flags.js");
require("core-js/modules/web.immediate.js");
test();
async function test() {
console.log('你说多少钱来着?');
const res = await say();
console.log(res);
}
function say() {
return new Promise(resolve => {
setTimeout(() => {
resolve(100);
}, 1000);
});
}配置 preset-env 的 modules 选项
{
"presets": [
[
"@babel/preset-env",
{
"targets": "chrome 40",
"useBuiltIns": "usage",
"corejs": "3",
+ "modules": false
}
]
]
}打包
// main.js
const arr = [1, 2, 3, [1, 2, 3]]
console.log(arr.flat());
// 打包后
import "core-js/modules/es.array.flat.js";
import "core-js/modules/es.array.unscopables.flat.js";
var arr = [1, 2, 3, [1, 2, 3]];
console.log(arr.flat());这个参数项的取值可以是"amd"、"umd" 、 "systemjs" 、 "commonjs" 、"cjs" 、"auto" 、false。在不设置的时候,取默认值"auto"。
该项用来设置是否把 ES6 的模块化语法改成其它模块化语法。
我们常见的模块化语法有两种:(1)ES6 的模块法语法用的是 import 与 export;(2)commonjs 模块化语法是 require 与 module.exports。
在该参数项值是'auto'或不设置的时候,会发现我们转码前的代码里 import 都被转码成 require 了。
如果我们将参数项改成 false,那么就不会对 ES6 模块化进行更改,还是使用 import 引入模块。
使用 ES6 模块化语法有什么好处呢。在使用 Webpack 一类的打包工具,可以进行静态分析,从而可以做 tree shaking 等优化措施。
@babel/runtime 的作用
把 babel 配置文件改成这样:(将 useBuiltIns 设置为 false,只开启语法转换)
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": false
}
]
]
}main.js 中写入这段代码
class Person {
say() {
console.log(123);
}
}打包,查看编译后的代码:
"use strict";
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
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, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
var Person = /*#__PURE__*/function () {
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [{
key: "say",
value: function say() {
console.log(123);
}
}]);
return Person;
}();可以看到在这个文件中定义了许多辅助函数。@babel/preset-env 在做语法转换的时候,注入了这些函数声明,以便语法转换后使用。
如果只有一个文件还好,但是在工程中,如果每个文件都使用了 class,那么就会在这个文件顶部定义这些相同的辅助函数,造成了重复和冗余。
如何进行复用?可以使用 @babel/runtime 包,里面专门定义了做语法转换时需要用到的辅助函数。
上面用到的辅助函数有 _typeof、_classCallCheck、_defineProperties、_createClass、_toPropertyKey、_toPrimitive,这些函数你能在 node_modules 里的 @babel/runtime 包里进行查看,会发现函数体是一模一样的。
以 _classCallCheck 举例,它的位置在
内容如下:
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
module.exports = _classCallCheck, module.exports.__esModule = true, module.exports["default"] = module.exports;可以发现与在文件里定义的 _classCallCheck 函数一模一样。
@babel/plugin-transform-runtime 的作用一
上面说到了 @babel/runtime 的作用,可以从里面引入辅助函数,替换做语法转换时生成的内联辅助函数
那么然后呢?我们手动一个一个的把在文件里定义的辅助函数改成从 @babel/runtime 里面引入吗?当然不可能。这里需要使用到一个插件 @babel/plugin-transform-runtime 。
@babel/plugin-transform-runtime 有三大作用,第一个就是自动移除语法转换后内联的辅助函数(inline Babel helpers),使用 @babel/runtime/helpers 里的辅助函数来替代。这样就减少了我们手动引入的麻烦。
使用它:
1.下载
npm install --save-dev @babel/plugin-transform-runtime2.配置 babel
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": false
}
]
],
+ "plugins": ["@babel/plugin-transform-runtime"]
}重新编译,查看编译后的文件:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var Person = /*#__PURE__*/function () {
function Person() {
(0, _classCallCheck2["default"])(this, Person);
}
(0, _createClass2["default"])(Person, [{
key: "say",
value: function say() {
console.log(123);
}
}]);
return Person;
}();可以看到已经帮我们自动引入辅助函数了。
引申问题:每个转换后的文件上部都会注入这些相同的函数声明,那为何不用 webpack 一类的打包工具去掉重复的函数声明,而是要单独再引一个辅助函数包?
webpack 在构建的时候,是基于模块来做去重工作的。每一个函数声明都是引用类型,在堆内存不同的空间存放,缺少唯一的地址来找到他们。所以 webpack 本身是做不到把每个文件的相同函数声明去重的。因此我们需要单独的辅助函数包,这样 webpack 打包的时候会基于模块来做去重工作。
@babel/plugin-transform-runtime 作用二、三
@babel/plugin-transform-runtime 有三大作用。
第一个是自动移除语法转换后内联的辅助函数。那另外两个呢?
第二个,当代码里有 API 需要 polyfill 兼容,自动引入 @babel/runtime-corejs3 或者 @babel/runtime-corejs3,来进行兼容。(需要开启配置)
第三个,当代码里使用了 generator/async 函数,自动引入 @babel/runtime-corejs3 或者@babel/runtime-corejs3 下的 regenerator(此配置默认开启)
并且二、三引入 polyfill 的时候,不会修改原型链或者在 window 上挂载对象。可以做到不污染运行环境。
怎么才能使用二、三两个功能呢?
对 @babel/plugin-transform-runtime 进行配置。下面是默认配置
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"version": "7.0.0-beta.0"
}
]
]
}corejs 可以设置为 false, 2, 3。
设置为 false,只会将语法转化时用到的内联辅助函数变为从 @babel/runtime 里引入。
设置为 2,将从 @babel/runtime-corejs2 包里引入 polyfill(辅助函数也将会从这个包里引入)。
设置为 3,将从 @babel/runtime-corejs3 包里引入 polyfill(辅助函数也将会从这个包里引入)。
@babel/runtime、@babel/runtime-corejs2、@babel/runtime-corejs3 只需要下载其中一个即可。取决于你设置的 corejs 值。
@babel/runtime-corejs2 包只支持全局变量(比如 Promise)和静态属性(比如 Array.from),不支持实例方法(例如 [].inclues)。@babel/runtime-corejs3 全都支持。
将 corejs 设置为 2
先来试试将 corejs 设置为 2。
配置 babel
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": false
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
- "corejs": false
+ "corejs": 2,
"helpers": true,
"regenerator": true,
"version": "7.0.0-beta.0"
}
]
]
}下载 @babel/runtime-corejs2。(其实不下也可以,因为我们只是测试而已,想看看编译后有没有引入这个包,并不会运行编译后的文件)
npm install --save @babel/runtime-corejs2main.js 打包前
class Person {
say() {
console.log(123);
}
}main.js 打包后。可以看到辅助函数是从 @babel/runtime-corejs2 包里引入的。
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
var Person = /*#__PURE__*/function () {
function Person() {
(0, _classCallCheck2["default"])(this, Person);
}
(0, _createClass2["default"])(Person, [{
key: "say",
value: function say() {
console.log(123);
}
}]);
return Person;
}();改变一下 main.js
class Person {
say() {
const p = Promise.resolve('ok')
console.log(p);
}
}再执行打包,可以看到,Promise 是使用从包里面引入的,而不是挂载到全局对象 window 上。
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
var Person = /*#__PURE__*/function () {
function Person() {
(0, _classCallCheck2["default"])(this, Person);
}
(0, _createClass2["default"])(Person, [{
key: "say",
value: function say() {
var p = _promise["default"].resolve('ok');
console.log(p);
}
}]);
return Person;
}();再改变 main.js
class Person {
say() {
const arr = [1,2,3,4]
console.log(arr.includes(1));
}
}编译后发现数组的 inclues 实例方法并没有比 polyfill 修复。
将 corejs 设置为 3
这时我们重新来过,将 corejs 设置为 3。
配置 babel
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": false
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
- "corejs": 2
+ "corejs": 3
"helpers": true,
"regenerator": true,
"version": "7.0.0-beta.0"
}
]
]
}下载 @babel/runtime-corejs3,并卸载 @babel/runtime-corejs2
npm install --save @babel/runtime-corejs3main.js
class Person {
say() {
const arr = [1,2,3,4]
console.log(arr.includes(1));
}
}编译打包后,发现数组的实例方法 includes 被 polyfill 修复了。
这也验证了@babel/runtime-corejs2 包只支持全局变量(比如 Promise)和静态属性(比如 Array.from),不支持实例方法(例如 [].inclues)。@babel/runtime-corejs3 全都支持的说法。
测试 async
当我们使用 async 函数时,@babel/plugin-transform-runtime 会自动引入 @babel/runtime-corejs2 或者 @babel/runtime-corejs3 下的 regenerator 包吗?
结论是的。。
配置 babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": false
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 3,
"helpers": true,
- "regenerator": true,
+ "regenerator": false,
"version": "7.0.0-beta.0"
}
]
]
}main.js
test()
async function test() {
await console.log(123);
}编译后
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
test();
function test() {
return _test.apply(this, arguments);
}
function _test() {
_test = (0, _asyncToGenerator2["default"])( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return console.log(123);
case 2:
case "end":
return _context.stop();
}
}, _callee);
}));
return _test.apply(this, arguments);
}这之后我们再设置 "regenerator": true,执行编译后的文件
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
+ var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
test();
function test() {
return _test.apply(this, arguments);
}
function _test() {
_test = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return console.log(123);
case 2:
case "end":
return _context.stop();
}
}, _callee);
}));
return _test.apply(this, arguments);
}参考链接
https://babeljs.io/ babel 官网
https://github.com/zloirock/core-js polyfill 核心包
https://www.jiangruitao.com/babel/ babel 教程
https://caniuse.com/ can i use
掘金的一些文章