关于Babel你需要知道的那些事

微信扫一扫,分享到朋友圈

关于Babel你需要知道的那些事

本文介绍了babel的配置和用法,对presets、plugins等概念及其用法进行了介绍;阐述了polyfill 和 transform-runtime 的作用和用法,对两者进行了对比;通过实验验证各自的特点和优势。 希望能对你有所帮助,欢迎交流~

一、什么是babel

将es6+进行转化,向后兼容 转化为es5使得他的绝大部分浏览器可以使用

下面地址可查看es6的支持情况: kangax.github.io/compat-tabl…

二、用法

安装babel相关的依赖

npm install --save-dev @babel/core @babel/cli @babel/preset-env    //@babel/cli实现命令行的能力
npm install --save @babel/polyfill
复制代码

配置

以前配置使用.babelrc文件,现在可以使用bable.config.js 放在项目根路径下;进行babel转义时,如果没有指定对应的配置,就会走这套配置。

命令行编译src下文件:

./node_modules/.bin/babel src --out-dir dist
或
npx babel src --out-dir dist
复制代码

babel也是可以进行文件配置的,而不是仅仅只可以像这里一样使用命令行,接下来看一下文件配置。 在跟目录下新建babel.config.js文件

const presets = [
[
"@babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
"ie": "10"
},
}
]
]
module.exports={
presets
}
复制代码

如果不指定特定的转义插件,就会走到配置文件

"scripts": {
"build": "babel src -d dist --presets=@babel/env",
"build:auto": "babel src -d dist",
"test": "echo "Error: no test specified" && exit 1"
},
复制代码

三、Plugins & Presets

Presets是Plugins的集合 Plugins用来为不同特定特性转化为es6,不同的特性转化需要引入不同的Plugins,这样引入就很繁杂和不好维护,特此才有了集合Presets 如箭头函数的转化:

npm install --save-dev @babel/plugin-transform-arrow-functions
./node_modules/.bin/babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions
复制代码

一般复杂的命令行可以通过脚本的方式方便维护:

"scripts": {
"build": "babel src -d dist",
"build:env": "babel src -d dist --presets=@babel/env",
"build:arrow": "babel src -d dist --plugins=@babel/plugin-transform-arrow-functions",
"test": "echo "Error: no test specified" && exit 1"
},
复制代码

一般多数es6语法转化使用的是 preset-env

npm install --save-dev @babel/preset-env
./node_modules/.bin/babel src --out-dir lib --presets=@babel/env
复制代码

四、polyfill

可以用polyfill来实现所有新的js功能,env只实现我们使用的功能的转换,实现目标浏览器中缺少的功能

安装

npm install --save @babel/polyfill
复制代码

使用

{
"presets": [
[
"@babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
},
"useBuiltIns": "usage",
}
]
]
}
复制代码

转义效果

转义前

const sayHi = () => {
console.log(77777777);
}
Promise.resolve().finally();
sayHi()
复制代码

转义后

"use strict";
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
require("core-js/modules/es7.promise.finally");
var sayHi = function sayHi() {
console.log(77777777);
};
Promise.resolve().finally();
sayHi();
复制代码

五、plugin详解

插件有很多可以安装指定插件后,在命令行、脚本、bablerc、babael.config.js 进行配置,在这以@babel/plugin-transform-member-expression-literals举例。

插件用法举例

@babel/plugin-transform-member-expression-literals 将和关键字同名的属性,修改为.[‘xxx’]的形式 输入:

obj.foo = "isValid";
obj.const = "isKeyword";
obj["var"] = "isKeyword";
复制代码

输出:

obj.foo = "isValid";
obj["const"] = "isKeyword";
obj["var"] = "isKeyword";
复制代码

使用:

//命令行
npx babel --plugins @babel/plugin-transform-member-expression-literals src -d dist
复制代码

//通过 Node API

const code = `obj.foo = "isValid";
obj.const = "isKeyword";
obj.var = "isKeyword";`
const result = require("@babel/core").transform(code, {
plugins: ["@babel/plugin-transform-member-expression-literals"]
});
console.log(result);
复制代码

效果:

其他新es版本的新特性的语法特性转化在此不再赘述。

模块化转化插件

babel提供多种形式的模块化转化支持:

·modules-amd

·modules-commonjs

·modules-systemjs

·modules-umd

测试的时候同一份代码可以通过不同的插件转化,效果不一样

转化配置

"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build:amd": "babel src/test.js --out-file dist/test-amd.js --plugins @babel/plugin-transform-modules-amd",
"build:common": "babel src/test.js --out-file dist/test-common.js --plugins @babel/plugin-transform-modules-commonjs",
"build:system": "babel src/test.js --out-file dist/test-system.js --plugins @babel/plugin-transform-modules-systemjs",
"build:umd": "babel src/test.js --out-file dist/test-umd.js --plugins @babel/plugin-transform-modules-umd",
"build": "npm run build:amd && npm run build:common && npm run build:system && npm run build:umd"
},
复制代码

注:命令行可以合并在一起 如果命令行不指定插件就会去找.babelrc等配置 如果没有找到 就会不进行任何处理

原代码 test.js

export default 42;
复制代码

转化后的

amd

define(["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
var _default = 42;
_exports.default = _default;
});
复制代码

common

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _default = 42;
exports.default = _default;
复制代码

system

System.register([], function (_export, _context) {
"use strict";
return {
setters: [],
execute: function () {
_export("default", 42);
}
};
});
复制代码

umd

(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["exports"], factory);
} else if (typeof exports !== "undefined") {
factory(exports);
} else {
var mod = {
exports: {}
};
factory(mod.exports);
global.test = mod.exports;
}
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
var _default = 42;
_exports.default = _default;
});
复制代码

在实验插件

www.babeljs.cn/docs/plugin…

一些有提案但还没有纳入正式标准的用法也有对应的插件的开发,可以使用对应插件就可以体验最新的提案。提案一般都是优势胜过劣势的,可以大胆的使用,一定可以提升研发的效能。

代码瘦身插件 Minification

www.babeljs.cn/docs/plugin…

使用他们可以去除无用代码或用更简洁的方式替换原来的代码,减少代码的体积。 去除console debugger的可以使用

六、polyfill vs runtime

polyfill

垫片,内容多,体积大 运行原代码前 先将垫片垫进去,

runtime

运行时 环境隔离,体积小,不做侵入

preset-env

plugins 的集成 可以按需引入使用useBuiltIns

七、总结与验证

转化的对象有两种:语法和API;

语法转化:

关于语法的转化是固定的,而且是开发依赖就已经转化好的,不涉及对新代码的引入,所以一般没有问题。

在配置文件中引入对应的语法插件或preset就可以 如:

const presets = [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
"ie": "10"
},
},
]
]
module.exports = {
presets
}
复制代码

API支持

支持api都是需要在代码中先引入新api的定义代码的, 有两种方式:

1.使用polyfill

2.使用@babel/plugin-transform-runtime

1.使用polyfill

使用polyfill的劣势:

1)自身总体很大: 可以考虑按需加载 2)会污染内置函数 静态方法 实例方法等,如果在项目的末端(开发项目本身或命令行终端)一般是没有影响的,可是如果在组件中要提供给别人用就会干扰其他人了:解决方案是使用@babel/plugin-transform-runtime

使用polyfill的方法

1.可以直接在代码开头引入polyfill,这样很大一般不推荐

2.使用@babel/env这个preset 里面有项配置useBuiltIns可以指定按需加载。 babel.config.js

const presets = [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
"ie": "10"
},
"useBuiltIns": "usage"  //usage代表按需加载  此外还可以配置false:不按需加载  entry:从入口加载
},
]
]
module.exports = {
presets
}
复制代码

转化前的源码

const sayHi = () => {
console.log(77777777);
}
Promise.resolve().finally();
sayHi()
复制代码

转化后的代码

"use strict";
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
require("core-js/modules/es7.promise.finally");
// import '@babel/polyfill'
var sayHi = function sayHi() {
console.log(77777777);
};
Promise.resolve().finally();
sayHi();
复制代码

3.自己手动按需引入,这样一般效率会低很多 需要对各个core里面的内容都非常清楚,其实要求非常高。

2. plugin-transform-runtime 和 runtime

@babel/plugin-transform-runtime && @babel/runtime两个一般搭配起来一起用

来源背景

Babel 会使用很小的辅助函数来实现类似 _createClass 等公共方法。默认情况下,它将被添加(inject)到需要它的每个文件中。这样就会有一个问题:如果一个项目的多个不同的文件用到了同一个api,此时如果使用polyfill的方式,每个文件都会单独在头部引入,这样重复的代码就太多了,为了解决这个问题可以使用@babel/plugin-transform-runtime

这套方案的核心优势是:

1.同一个新api在这个项目中不同地方支持它时,引入的支持代码都从库@babel/runtime中引入,这样就避免重复了。

2.可以避免全局污染

plugin-transform-runtime避免重复的效果演示

转换前的代码

class Point { constructor(x, y) { this.x = x; this.y = y; }; getX() { return this.x; } } let cp = new ColorPoint(25, 8);
复制代码

如果用folyfill按需转化 配置如下:

const presets = [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
"ie": "10"
},
"useBuiltIns": "usage"
},
]
]
module.exports = {
presets,
}
复制代码

转化后的效果为:

"use strict";
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 Point = /*#__PURE__*/function () {
function Point(x, y) {
_classCallCheck(this, Point);
this.x = x;
this.y = y;
}
_createClass(Point, [{
key: "getX",
value: function getX() {
return this.x;
}
}]);
return Point;
}();
var cp = new ColorPoint(25, 8);
复制代码

可以看到有很多inject插入 如果使用plugin-transform-runtime插件

const presets = [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
"ie": "10"
},
"useBuiltIns": "usage"
},
]
]
const plugins = [["@babel/plugin-transform-runtime"]]
module.exports = {
presets, plugins
}
复制代码

转换后的代码

"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 Point = /*#__PURE__*/function () {
function Point(x, y) {
(0, _classCallCheck2.default)(this, Point);
this.x = x;
this.y = y;
}
(0, _createClass2.default)(Point, [{
key: "getX",
value: function getX() {
return this.x;
}
}]);
return Point;
}();
var cp = new ColorPoint(25, 8);
复制代码

可以看到没有直接插入 而是通过@babel/runtime的方式引入的

避免全局污染效果演示

源码如下:

let isHas = [1, 2, 3].includes(2);
new Promise((resolve, reject) => {
resolve(100);
});
复制代码

如果没有使用plugin-transform-runtime时,使用polyfill按需加载,配置文件如下

const presets = [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
"ie": "10"
},
"useBuiltIns": "usage"
},
]
]
module.exports = {
presets,
}
复制代码

转化后的效果

"use strict";
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
require("core-js/modules/es7.array.includes");
require("core-js/modules/es6.string.includes");
var isHas = [1, 2, 3].includes(2);
new Promise(function (resolve, reject) {
resolve(100);
});
复制代码

可以发现Array.prototype 上新增了 includes 方法,并且新增了全局的 Promise 方法,污染了全局环境。

如果使用plugin-transform-runtime,配置如下:

const presets = [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
"ie": "10"
},
// "useBuiltIns": "usage"
},
]
]
const plugins = [["@babel/plugin-transform-runtime", { "corejs": 3 }]]
module.exports = {
presets, plugins
}
复制代码

plugin-transform-runtime配置选项中要配指定要引入的corejs,不再使用”useBuiltIns”: “usage” 因为他会和plugin-transform-runtime引入重复,这是转化后的效果:

"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _context;
var isHas = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 2);
new _promise.default(function (resolve, reject) {
resolve(100);
});
复制代码

没有直接去修改 Array.prototype,或者是新增 Promise 方法,避免了全局污染。 plugin-transform-runtime的优势可以总结为: 减少体积; 避免全局污染; 按需加载

微信扫一扫,分享到朋友圈

关于Babel你需要知道的那些事

分布式事务实战汇总

上一篇

前端工程化 - 聊聊 Webpack v3 到 Webpack v5 的核心架构变迁

下一篇

你也可能喜欢

关于Babel你需要知道的那些事

长按储存图像,分享给朋友