@符号是什么?
这是babel 7 的一大调整,原来的 bebel-xxx 包统一迁移到 babel域下(域由 @ 符号来标识); 一来便于区别官方与非官方的包; 二来避免可能的包命名冲突
babel-core
babel-core 的作用是把js代码分析成 ast,方便各个插件分析语法进行相应的处理
@babel/cli
是 babel 提供的命令行工具,用于命令行下编译源代码
安装方法:
npm install --save-dev @babel/core @babel/cli
babel插件
@
babel/plugin-transform-arrow-functions
将箭头函数转换为 ES5函数
@
babel/plugin-transform-block-scoping
转换 es6中的
**const**
为 v**ar**
@
babel/plugin-transform-classes
转换
class
为es5函数
.babelrc文件中配置
{
"plugins": [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-classes"
]
}
test.js文件
const alertMe = (msg) => {
window.alert(msg)
}
class Robot {
constructor (msg) {
this.message = msg
}
say () {
alertMe(this.message)
}
}
const marvin = new Robot('hello babel')
npx babel test.js
注:
只是,这样安装插件、配置 .babelrc
的过程非常乏味,而且容易出错。通常,我们不会关心到具体的某个ES2015特性支持情况这个层面,我们更关心浏览器版本这个层面。
比如,我不想关心 babel 插件的配置,只是希望给 babel 一个 我想支持IE10 的提示,babel 就帮我编译出能在 IE 10 上正常运行的 javascript 代码。
—-> 此时就需要
@babel/preset-env
它可以根据开发者的配置,按需加载插件 默认配置下,它跟babel-preset-latest是等同的,会加载从es2015开始的所有preset
.babelrc 中配置:
{
"presets": ["@babel/preset-env"], // 此时 上面的那些 plugins 就不需要再配置了
}
- 只想支持最新版Chrome
{
"presets": [
["@babel/preset-env", {
"targets": {
"browers": ["last 1 Chrome versions"]
}
}]
],
}
// 编译完包含箭头函数 和 const、class 的文件后,发现编译后的文件和之前一样,那是因为最新版的Chrome
// 已经支持该语法,也就不会编译了, 因此,@babel/preset-env 称为 JavaScript 的 Autoprefixer
历史背景:
最初,为了让开发者能够尽早用上新的JS特性,babel团队开发了babel-preset-latest。这个preset比较特殊,它是多个preset的集合(es2015+),并且随着ECMA规范的更新更增加它的内容
随着时间的推移,babel-preset-latest 包含的插件越来越多,这带来了如下问题
- 加载的插件越来越多,编译速度会越来越慢
- 随着用户浏览器的升级,ECMA规范的支持逐步完善,编译至低版本规范的必要性在减少(比如ES6 -> ES5),多余的转换不单降低执行效率,还浪费带宽。
- 因为上述问题的存在,babel官方推出了babel-preset-env插件。它可以根据开发者的配置,按需加载插件。配置项大致包括:
- 需要支持的平台:比如node、浏览器等。
- 需要支持的平台的版本:比如支持node@6.1等。
@
babel-polyfill
作用:
比如 IE 11 不支持 数组的 findIndex 这个方法,加入代码中写了
findIndex
,浏览器会报错, 如果使用@bebel/polyfill
,他将会给 IE11 浏览器 添加如下 polyfill, 进而是 IE11 支持
// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
if (!Array.prototype.findIndex) {
Object.defineProperty(Array.prototype, 'findIndex', {
value: function(predicate) {
// 1. Let O be ? ToObject(this value).
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
var thisArg = arguments[1];
// 5. Let k be 0.
var k = 0;
// 6. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kValue be ? Get(O, Pk).
// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
// d. If testResult is true, return k.
var kValue = o[k];
if (predicate.call(thisArg, kValue, k, o)) {
return k;
}
// e. Increase k by 1.
k++;
}
// 7. Return -1.
return -1;
}
});
}
上面这段代码的意思是,如果目标环境中已经存在 findIndex
,我们什么都不做,如果没有,我们就在 Array
的原型中定义一个。这便是 polyfill 的意义。babel-polyfill 同理。
背景介绍:
浏览器的特性支持情况千差万别,可以概括为两类:
- 大家都有,只是 A 语法与B 语法的区别(实现方式不同)
- 不是大家都有: 有的有,有的没有
babel 编译过程处理第一种情况,统一语法的形态,通常是高版本语法编译成低版本的,比如ES6 语法编译成 ES5 或 ES3。而babel-polyfill 处理第二种情况,让目标浏览器支持所有特性,不管它是全局的,还是原型的,或是其他。只要通过 babel-polyfill,不同浏览器在特性支持上就站到统一起跑线。
安装:
npm install --save @babel/polyfill
// --save 表明 上线后也是需要用的,不像 babel 只在编译阶段使用
使用方法:
需要在程序入口文件的顶部引用,且不可多次引用,如果代码中多次引用,执行时会抛错, 这是因为引入的 babel-polyfill 会在全局写入一个
_babelPolyfill
变量。第二次引入时,会检测该变量是否已存在,如果已存在,则抛出错误。
only one instance of @babel/polyfill is allowed
import "@babel/polyfill";
注意:
babel-polyfill 其实是
regenerator runtime
、core-js
,如果你的代码只需要其中一部分 polyfill,那么你可以考虑直接引入core-js
下特定 polyfill, 不必使用 babel-polyfill 这样的庞然大物。另一种方法是配合
@babel/preset-env
的useBuiltIns
配置。
babel-runtime
举个🌰:
如, IE11 不支持 Object.assign
,此时有两种方案:
- 引入
@bebel/polyfill
,补丁一打,Object.assign
就被创造出来 - 配置
@babel/plugin-transform-object-assign
第二种方案中,babel 会将所有的 Object.assign
替换成 _extends
这样一个辅助函数,但是,如果你的项目里面有 100 个文件,其中有 50 个里面写了 Object.assign
,那么,_extends
辅助函数会出现 50 次。
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
_extends({}, {});
// 如上代码 引用几次,就会出现几次
此时我们首先想到的就是,需要将 _extends
分离出去,然后在每一个文件中引入,那么这就是 @babel/runtime
的作用:
var _extends = require("@babel/runtime/helpers/extends");
_extends({}, {});
非常nice,但是上面代码块中的代码,是需要人为的去使用 bebel-runtime 去转换的,但是是没有人去手动转换的。
于是就会就出现了 @babel/plugin-transform-runtime
来替我们做这些转换。
@babel/plugin-transform-runtime
安装:
npm install --save-dev @babel/plugin-transform-runtime
npm install @babel/runtime
配置:
{
"plugins": [
"@babel/plugin-transform-object-assign",
"@babel/plugin-transform-runtime"
]
}
和@babel/polyfill 区别:
- @babel/polyfill 改造目标浏览器,让你的浏览器拥有本来不支持的特性;
- babel-runtime 改造你的代码,让你的代码能在所有浏览器上运行,但不改造浏览器。
babel-register
经过 babel 的编译后,我们的源代码与运行在生产下的代码是不一样的; 然而 babel-register 则提供一种动态编译,即可以让我们的 源代码(包含 新语法的 代码) 能真正运行在生产环境下,而不需要 经过 babel 的编译
安装:
npm install --save-dev @babel/register
使用:
require('@babel/register')
require('./app')
在入口文件中引用后, 我们的 app 文件中即可使用任意 es2015 的特性
弊端:
动态编译,导致程序在速度、性能上有所损耗
babel-node
我们上面说,babel-register 提供动态编译,能够让我们的源代码真正运行在生产环境下 - 但其实不然,我们仍需要做部分调整,比如新增一个入口文件,并在该文件中
require('@babel/register')
。而 babel-node 能真正做到一行源代码都不需要调整:
npm install --save-dev @babel/core @babel/node
npx babel-node app.js
只是,请不要在生产环境中使用 babel-node,因为它是动态编译源代码,应用启动速度非常慢。