Babel7
以下是各个 ECMAScript 版本引入的一些主要新语法和功能的汇总
ES5 / ECMAScript 5(2009年)
- 严格模式
"use strict"
。 JSON
对象。Array.prototype.forEach()
、Array.prototype.map()
、Array.prototype.filter()
、Array.prototype.reduce()
等数组方法。Object.keys()
、Object.create()
等对象方法。
ES6 / ECMAScript 2015(也称为 ES2015 或 ECMAScript 6,2015年)
let
和const
变量声明。- 箭头函数。
- 模板字符串。
- 解构赋值。
- 默认参数和剩余参数。
- 类和继承。
- 模块化
import
和export
。 Promise
。- 增强的对象字面量。
ES7 / ECMAScript 2016(2016年)
Array.prototype.includes()
方法。- 指数操作符
**
。
ES8 / ECMAScript 2017(2017年)
- 异步函数
async
和await
。 Object.values()
和Object.entries()
。- 字符串填充方法
String.prototype.padStart()
和String.prototype.padEnd()
。
ES9 / ECMAScript 2018(2018年)
- 对象扩展运算符。
- 异步迭代器。
- 正则表达式命名捕获组。
ES10 / ECMAScript 2019(2019年)
Array.prototype.flat()
和Array.prototype.flatMap()
。String.prototype.trimStart()
和String.prototype.trimEnd()
。
ES11 / ECMAScript 2020(2020年)
- 可选链操作符
?.
。 - 空值合并操作符
??
。
ES12 / ECMAScript 2021(2021年)
- 数字分隔符。
Promise.allSettled()
。String.prototype.replaceAll()
。
以上列举了每个版本中的一些主要语法和功能。每个版本都还包含了其他一些小的改进和新增功能。要详细了解每个版本中的所有变化,可以查阅官方的 ECMAScript 规范或其他权威资源。
解析 const和箭头函数是es6的语法
npx babel src/index.js -d dist/ --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions
本文根据babel@7 进行测试的
前言
Babel
是一个 JavaScript 编译器,主要用于将 ECMAScript 2015+
版本的代码转换为向后兼容的 JavaScript 代码,以便能够在旧版本的浏览器或其他环境中运行。但是,Babel 本身是无法转换代码的,它的转换功能是通过不同的插件来实现的,Babel 插件是用于指定转换规则的工具,每个插件都可以处理不同的语法或功能转换。
作用
将某些低版本容器(主要是浏览器,主要是IE…)不支持的js语法或api,用该容器支持的语法或api重写,使开发者可以使用更前沿的方式愉快的编写代码。
但实际上更准确点说,是一堆插件在做代码的转换,babel本身是个容器,负责代码解析、转换抽象语法树,然后通过各种插件做代码转换,最后根据转换后的抽象语法树生成最终的代码。这个过程以后再细说,这里想说的就是插件对于babel的作用,而我们使用者可能比较关心的,也就是在做代码转换时,会用到哪些插件。
babel-cli官方最新版本为7.24.3
Babel 本身就是一个可以独立使用的工具,但是目前开发中很少直接使用 Babel,都是和 Webpack ,Rolllup,vite等构建工具来配合使用。
编译器的作用就是可以将一种代码转换为另一种代码。
Babel 不仅可以转换 ES6+ 的语法,也可以转换 React、TypeScript。
Babel 和 PostCSS 都是微内核架构,工具本身只编写自身核心的代码,具体的功能通过不同的插件来实现。
ES6+ 的语法通过 Babel 就可以转换,为什么还需要 Polyfill?
一些 ES6+ 的语法特性,例如箭头函数、模板字符串、常量声明等,只涉及到语法层面的改变
,因此可以在不引入新的 JavaScript 对象或方法的情况下通过简单的语法转换为旧版本的 JavaScript。
但是,有些 ES6+ 特性涉及到新的 JavaScript 对象、方法或内置功能
,例如 Map、Set、Symbol、Promise、async/await 等,这些特性不能通过简单的语法转换来实现,因此需要使用 Polyfill。
原理:
Babel 的工作原理整体上可以分为三个阶段:解析阶段 Parsing、转换阶段 Transformation、生成阶段 Code Generation。
具体来说,可以分为以下步骤:
-
词法分析:读取源代码文件,对文件中的每个单词做分析。其实就是读取源代码文件中的每个单词。
-
生成 tokens 数组:将词法分析出来的单词生成 tokens 数组。其实就是对词法分析读取出来的单词进行分类。
-
语法分析:语法分析 tokens 数组中的每一项代表什么含义。其实就是分析 tokens 数组的每一项其真正的语法表示的是什么。
-
生成 AST 抽象语法树:根据语法分析出来的每一项的真正含义生成 AST 抽象语法树。
-
对 AST 抽象语法树进行遍历。
-
访问 AST 抽象语法树中的每个结点。
-
在访问结点的过程中对结点应用对应的插件,进行结点的修改。
-
遍历完之后就生成新的 AST 抽象语法树。
-
最后根据新的抽象语法树生成转化后的新的代码。
配置文件:
通常,需要指定 Babel 的编译规则来编译代码。Babel 的配置文件默认会在当前目录寻找文件,有:.babelrc
、.babelrc.js
、babel.config.json
、babel.config.js
、package.json
,它们的配置项都是一样的,作用也一样,只需要选择一种即可。
// JSON
{
"presets": [...],
"plugins": [...]
}
// JavaScript
const presets = [ ... ];
const plugins = [ ... ];
module.exports = { presets, plugins };
// package.json
{
"name": "my-package",
"version": "1.0.0",
"babel": {
"presets": [ ... ],
"plugins": [ ... ],
}
}
babel7主要就是两个包,@babel/cli和@babel/core,cli用于执行命令行,core则是babel用于解析、转换、代码生成的核心包。在项目下执行以下命令即可完成安装。
有了这两个包以后,就可以对指定文件执行babel命令了
@babel/core
@babel/core:Babel 的核心代码。必须安装。
```bash
npm install --save-dev @babel/core
```
基本用法:可以在 JS 程序中直接引入并使用。
const babel = require("@babel/core");
babel.transformSync(code, optionsObject);
@babel/cli
@babel/cli
:是 babel 提供的内建命令行工具。如果需要在命令行中直接使用 Babel,就需要安装 @babel/cli ,否则不需要安装。
安装命令:
npm install --save-dev @babel/cli
基本用法:
src目录文件index.js代码如下:
const fn = () => {
Array.isArray([1, 2, 3]);
};
现在在项目根目录下执行
npx babel src/index.js -d dist/
即可在dist目录下生成同名文件index.js,而里面代码与src/index.js中的代码完全一样,生成的dist/index.js内容如下:
const fn = () => {
Array.isArray([1, 2, 3]);
};
之所以代码完全一样,其实就是上面所说的,babel在没有使用任何插件时,就是把代码变成抽象语法树,再把抽象语法树原封不动的变成代码,中间没有做任何处理,当然代码也就原样还原回来了。下面,让我们来给babel加点儿料~
Plugins
const和箭头函数是es6的语法,相应的,@babel/plugin-transform-block-scoping插件用于转换const和let,@babel/plugin-transform-arrow-functions插件用于转换箭头函数。安装完这两个插件之后,分别执行:
@babel/plugin
@babel/plugin*
:代码转换功能以插件的形式出现,插件是小型的 JS 程序,用于指导 Babel 如何对代码进行转换。
安装命令:
npm install --save-dev @babel/plugin-transform-arrow-functions // 将箭头函数转换为 ES5 兼容的函数表达式的插件
基本用法:
npx babel src/index.js -o dist/index.js --plugins=@babel/plugin-transform-arrow-functions // 多个插件之间用逗号隔开
扫完一眼plugins列表,估计和我一样一脸懵逼,这么多插件谁能记得住用得到哪些啊,babel能帮忙整理下打个包给我用么?当然可以,presets就是用来干这事儿的。
Presets
一个特定的preset可以简单理解为是一组特定的plugins的集合。不同的presets包含着不同的plugins,当然适用的场景也就各不相同了。比如@babel/preset-react包含了写react需要用到的@babel/plugin-syntax-jsx,@babel/plugin-transform-react-jsx,@babel/plugin-transform-react-display-name等插件;@babel/preset-es2017包含了@babel/plugin-transform-async-to-generator插件。
而最为常用,也是被官网推荐的,是@babel/preset-env。默认情况下,所有已被纳入规范的语法(ES2015, ES2016, ES2017, ES2018, Modules)所需要使用的plugins都包含在env这个preset中。
还是以上面例子来说
先安装@babel/preset-env
@babel/preset
@babel/preset-env
:是 Babel 提前预设好的一系列 Babel 中的插件的组合。如果要使用的 Babel 插件很多,一个一个安装配置是很麻烦的,就可以使用 Babel 中的预设。
plugins 插件数组的执行顺序是从左到右执行的,presets 预设数组的执行顺序是从右到左执行的。
插件 plugins 在预设 presets 之前执行。
安装命令:
npm install --save-dev @babel/preset-env // 在代码中使用了多种 ES6+ 的语法,一个一个地使用插件很麻烦,就可以使用一个名称为 env 的 preset
基本用法:
npx babel src/index.js -o dist/index.js --presets=@babel/env
@babel/preset-env
的参数项:
- target:用来设定目标浏览器。默认值为空对象。
presets: [
[
'@babel/preset-env',
{
targets: {
ie: "11"
}
}
]
]
- useBuiltIns:用来设置预设以什么样的方式来使用 Polyfill。默认值为 false。
属性值有:
- false:不使用 Polyfill。
- usage:无需手动引入 Polyfill。代码中需要哪些 Polyfill,Babel 会自动按需加载需要的 Polyfill。
- entry:需要在入口文件中手动引入 Polyfill。只要是目标浏览器需要的 polyfill,且包含在入口文件引入的
core-js
和regeerator-runtime
包中,不管代码中是否需要都全部引入。
使用 entry 属性值时,还需要在打包的入口文件处引入 core-js 和 regenerator-runtime。
css // 不是直接引入 core-js。而是引入 core-js/stable,表示引入 core-js 中成为标准的那些部分;也可以引入 core-js 中某些具体的功能。 import 'core-js/stable' import 'regenerator-runtime/runtime'
- corejs:设置 corejs 的版本。默认值为 2。
corejs 的属性值需要与安装的 core-js 的版本能对应上,否则会报错。corejs 取值为 2 的时候,需要安装并引入 core-js@2 版本;如果 corejs 取值为3,必须安装并引入 core-js@3 版本。
-
modules:用来设置是否把 ES6 的模块化语法改成其它模块化语法。默认值为 auto,可以取值amd、umd、systemjs、commonjs、cjs、auto、false。
-
debug:如果需要在命令行打印加载项,可以设置 debug。
主体部分与同时使用两个plugins是完全一样的。实际上,presets可以理解为就是把其包含的plugins依次执行一遍。
当然env这个presets不是万能的,其只包含了规范中的语法转换,尚未被纳入规范的处于各个阶段的提案,比如目前处于stage-2(draft)阶段的装饰器语法,光是用presets是不会帮我们转好的,还得单独再使用@babel/plugin-proposal-decorators这个专门用于转换装饰器代码的插件。
值得一提的是,babel7明确指出用stage-x命名的presets已被弃用。具体原因见
关于stage-x各代表什么含义?
在 Babel 中,stage-x
表示 ECMAScript 提案的不同阶段,其中 x
代表阶段的编号,从 0 到 4。这些阶段表示了 ECMAScript 规范中新功能的开发进度,以及这些功能被社区接受和推动的程度。具体解释如下:
-
Stage 0 - Strawman(稻草人阶段):
- 提案处于初始阶段,只是一个想法或者概念,尚未形成正式的提案。
-
Stage 1 - Proposal(提案阶段):
- 提案已经提交到 TC39(ECMAScript 标准的技术委员会)进行讨论,并且已经得到了初步的认可,但尚未成为官方的 ECMAScript 提案。
-
Stage 2 - Draft(草案阶段):
- 提案已经在 TC39 中被正式接受,成为了 ECMAScript 的草案,并且开始详细讨论和规划实现细节。
-
Stage 3 - Candidate(候选人阶段):
- 提案已经成为了 ECMAScript 的候选人,表示它已经被确定为规范的一部分,并且可以进入到浏览器和 JavaScript 引擎中进行实现和测试。
-
Stage 4 - Finished(完成阶段):
- 提案已经经过了所有必要的评审和测试,并且已经被确定为规范的一部分,可以作为下一个 ECMAScript 版本的一部分发布。
因此,stage-x
presets 表示一组转换规则,用于处理处于不同阶段的 ECMAScript 提案功能。这些 presets 包含了针对各个阶段提案的转换规则,可以帮助开发者在项目中使用尚未正式纳入规范的 JavaScript 新特性。然而,由于语义上的不清晰以及规范的变化,Babel 在版本 7 中废弃了以 stage-x
命名的 presets,并推荐开发者直接使用具体的插件来处理特定的 ECMAScript 提案功能。
https://babeljs.io/blog/2018/07/27/removing-babels-stage-presets
如果希望和之前一样使用处于各阶段的提案功能,建议直接通过引入相应的plugins:
{
plugins: [
// Stage 0
"@babel/plugin-proposal-function-bind",
// Stage 1
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-logical-assignment-operators",
["@babel/plugin-proposal-optional-chaining", { loose: false }],
["@babel/plugin-proposal-pipeline-operator", { proposal: "minimal" }],
["@babel/plugin-proposal-nullish-coalescing-operator", { loose: false }],
"@babel/plugin-proposal-do-expressions",
// Stage 2
["@babel/plugin-proposal-decorators", { legacy: true }],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
// Stage 3
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
["@babel/plugin-proposal-class-properties", { loose: false }],
"@babel/plugin-proposal-json-strings",
],
}
这些插件会将对应阶段的语法特性转换为向后兼容的代码,以确保它们可以在当前浏览器环境中正常运行。通过配置这些插件,你可以在项目中使用最新的 ECMAScript 语法特性,而不必担心浏览器的兼容性问题
@babel/ployfill
Polyfill可以理解为是补丁,其实就是实现某些功能或特性的 JavaScript 脚本。有一些语法特性浏览器是不认识的,使用的话必然报错(例如:Promise、Generator、Symbol 等,以及一些实例方法 Array.prototype.includes 等),此时就可以使用 Polyfill 来打一个补丁,将相关功能的代码包含进项目中。
Polyfill 并不是 Babel 特有的功能,只不过此处使用的是 Babel 中的 Polyfill。
@babel/polyfill
:由 core-js
和 regenerator-runtime
组成。
- 前者是 JS 标准库,包含不同版本 JavaScript 语法的实现;
- 后者是 facebook 开源库,用来实现对 Promise、Generator、async/await 函数等的支持。
Babel 7.4.0 之前
,是使用 @babel/polyfill
包,但是该包现在已经不建议使用了,因为 @babel/polyfill
把两个 npm 包全部都引入到了打包后的文件里了,导致打包后的体积过大;而且@babel-polyfill 可能会污染全局变量,给很多类的原型链上都作了修改,这就有不可控的因素存在。
Babel7.4.0 之后
,建议单独使用core-js
和 regenerator-runtime
。
安装命令:
npm install --save core-js regenerator-runtime
基本用法:
import "core-js/stable";
import "regenerator-runtime/runtime";
如果您正在将生成器或异步函数编译为 ES5,并且您使用的 @babel/core 或 @babel/plugin-transform-regenerator 版本早于 7.18.0 ,则还必须加载 regenerator runtime 包。
如果确切地知道所需要的功能,可以只引入使用的方法。
import 'core-js/features/array/from';
import 'core-js/features/set';
import 'core-js/features/promise';
@babel/runtime
@babel/runtime
:可以用于提取公共函数,但是提取出来之后,代码里不会自动引用这些公共函数。
@babel/plugin-transform-runtime
可以自动引用公共函数。
例如:使用 class 类的时候,Babel 会在前面添加一个帮助函数。
// 源代码
class Test {}
// 编译后的代码
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Test = function Test() {
_classCallCheck(this, Test);
};
在编译后的代码里,帮助函数被直接迁入到代码中了。如果有多个脚本文件,每个都用到了 class,那么每个编译后的文件都包含一个一模一样的帮助函数,造成了冗余。可以使用@babel/runtime
和 @babel/plugin-transform-runtime
来解决:
-
安装依赖:
npm i --S @babel/runtime @babel/plugin-transform-runtime
。 -
在 Babel 配置文件中进入如下配置:
plugins: [
'@babel/plugin-transform-runtime'
]
- 再次编译,可以看到不再是直接把帮助函数迁入代码中,而是使用 require 加载公共帮助函数。
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var Test = function Test() {
(0, _classCallCheck2.default)(this, Test);
};
鄙人觉得 7.18.0以后就不用这些运行时包 regenerator-runtime @babel/runtime
@babel/register
@babel/register
提供了动态编译,换句话说,源代码能够真正运行在生产环境下,不需要 babel 编译这一环节了。但是动态编译会导致程序在速度、性能上有所损耗。
安装:
npm install --save-dev @babel/register
然后在入口文件引入:
require('@babel/register')
require('./app')
在入口文件头部引入 @babel/register 后,在 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
-
新建一个文件夹 test-babel。
-
在终端进入这个文件夹下,执行 npm install --save-dev @babel/core @babel/cli 安装 Babel。
-
在 test-babel 文件夹下新建 src/index.js,并编写一些 ES6+ 的新语法。
-
在终端执行 npx babel src/index.js -o dist/index.js,会自动生成 dist/index.js。但此时,dist/index.js 的代码和 src/index.js 的代码并没有太大区别,因为现在所安装的 Babel 只是一个有命令行的空壳而已,还需要为 Babel 添加一些插件来让 babel 工作。
-
在终端执行 npm install --save-dev @babel/preset-env 来安装 @babel/preset-env 插件。
-
在根目录下新建 babel.config.js 的配置文件进行配置,告知 Babel 来使用插件。
module.exports = {
presets: [
[
'@babel/preset-env'
]
]
};
- 在终端再次执行 npx babel src/index.js -o dist/index.js,会发现 dist/index.js 已经转换为 ES5 的语法了。
- 文件 a.js .babelrc
a.js
import 'core-js/stable'
import 'regenerator-runtime/runtime'
class A{
}
.babelrc
{"presets": [
[
"@babel/preset-env",
{
"targets": {
"ie": "11"
},
"useBuiltIns":"entry",
// "corejs":3
}
]
]
}
“corejs”:3 如果配置这个 ,那么这些能让新的语法实现的 导入就会打包到文件中 说白啦就是 core-js/stable的内部文件会一起打包到文件里,增加代码量,不建议设置,全量引用
- a.js .babelrc
a.js
class A{
}
.babelrc
{"presets": [
[
"@babel/preset-env",
{
"targets": {
"ie": "11"
},
"useBuiltIns":"usage",
//"corejs":3
}
]
],
"plugins": [
// "@babel/plugin-transform-arrow-functions",
["@babel/plugin-transform-runtime",{
"corejs": "3"
}]
]
}
自动导入 和 按需
项目开发
useBuiltIns
使用usage,尽量使用社区广泛使用的优质库以优化打包体积,不使用暂未进入规范的特性。plugin-transform-runtime只使用其移除内联复用的辅助函数的特性,减小打包体积。
{
"presets": [
[
"@babel/preset-env",
{
// targets 官方推荐使用 .browserslistrc 配置
"useBuiltIns": "usage",
"corejs": {
"version": 3,
"proposals": false
}
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false // 默认值,即使如此依然需要 yarn add @babel/runtime
}
]
]
}
并在入口文件处 import 如下内容
import ‘core-js/stable’;
import ‘regenerator-runtime/runtime’;
// 入口文件代码
类库开发
类库开发尽量不使用污染全局环境的polyfill,因此@babel/preset-env只发挥语法转换的功能,polyfill由plugin-transform-runtime来处理,推荐使用core-js@3,并且不使用未进入规范的特性。
{
"presets": [
[
"@babel/preset-env",
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": true
},
"useESModules": true
}
]
]
}
参考文章
Babel7-1
babel7-2
babel7-3拿到项目就是copy一下