前言
平台端项目执行命令 yarn run build prod
,有一句警告语 @babel/polyfill is deprecated. Please, use required parts of core-js and regenerator-runtime/tuntime separately
一、是什么?
简单来说可以把 JavaScript 中 es2015+ 的新语法转化为 es5,让低端运行环境(如老版本IE浏览器和低版本node)能够认识并执行。
不过 Babel 的用途并不止于此,它支持语法扩展,能支持像 React 所用的 JSX 语法,同时还支持用于静态类型检查的流式语法(Flow Syntax)以及 TypeScript。
更重要的是,Babel 的一切都是简单的插件,谁都可以创建自己的插件,利用 Babel 的全部威力去做任何事情,比如:eslint、prettier、装饰器语法的实现等等。
为什么要转成 es5?
严格来说,babel 也可以转化为更低的规范。但以目前情况来说,es5 规范已经足以覆盖绝大部分浏览器,因此常规来说转到 es5 是一个安全且流行的做法。
二、工作原理(浅析)
babel的工作过程分为三个阶段:parsing(解析)、transforming(转化)、printing(生成)
- parsing阶段 babel 内部的 babylon 负责将es6代码进行语法分析和词法分析后转换成抽象语法树(AST);
transforming 阶段内部的 babel-traverse 根据目标环境(浏览器/node)对抽象语法树进行变换操作;
babel自6.0起,就不再对代码进行transform,现在只负责上图中的parse和generate过程,代码的transform过程全都交给一个个plugin去做。所以在没有配置任何plugin的情况下,经过babel输出的代码是没有改变的。
printing 阶段内部的 babel-generator 根据抽象语法树生成对应的代码;
其中第二步的转化是重中之重,babel的插件机制也是在这一步发挥作用的,plugins在这里进行操作,转化成新的AST,再交给第三步的babel-generator。
三、使用方法
总共存在三种方式:
- 使用单体文件 (standalone script) (了解) ```html <!DOCTYPE html>
2. 命令行 (cli)
```bash
npm i @babel/cli @babel/core -D
npx babel src -d dist
- 构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。
其中第三种使用场景比较常见,直接集成到构建工具中。
这三种方式只有入口不同而已,调用的 babel 内核,处理方式都是一样的。
四、配置方式
配置:更多配置方式
在我们告诉 Babel 该做什么之前,我们需要创建一个配置文件。需要在项目的根路径下创建 .babelrc
文件。然后输入以下内容作为开始:
{
"presets": [],
"plugins": []
}
这个文件就是用来让 Babel 做你要它做的事情的配置文件。
五、插件和配置
插件:
1. 插件分类:
在加入plugins测试之前我们需要知道一些前置知识,babel将ECMAScript 2015+ 版本的代码分为了两种情况处理:
- 语法层: let、const、class、箭头函数、扩展运算符、可选链、async等,这些需要在构建时进行转译,是指在语法层面上的转译;
- api方法层:Promise、Array.form、Map、Set等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义;
babel对这两种情况的转译是不一样的,我们需要给出相应的配置。
2. 没有设置任何插件的情况:
没有配置文件或者未设置任何插件,Babel 默认不做任何处理;
针对不同的语法特性,会有对应的插件,比如要支持let、const、class、箭头函数,要这样配置:
{
"plugins": [
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-classes",
"@babel/plugin-transform-arrow-functions"
]
}
但是光 ES2015 的新语法特性就有20+个,这样配置太麻烦了~
3. 预设:
预设:一组插件的集合;
比如 ES2015 是一套规范,包含大概十几二十个转译插件。
- check-es2015-constants // 检验const常量是否被重新赋值
- transform-es2015-arrow-functions // 编译箭头函数
- transform-es2015-block-scoped-functions // 函数声明在作用域内
- transform-es2015-block-scoping // 编译const和let
- transform-es2015-classes // 编译class
- transform-es2015-computed-properties // 编译计算对象属性
- transform-es2015-destructuring // 编译解构赋值
- transform-es2015-duplicate-keys // 编译对象中重复的key,其实是转换成计算对象属性
- transform-es2015-for-of // 编译for…of
- transform-es2015-function-name // 将function.name语义应用于所有的function
- transform-es2015-literals // 编译整数(8进制/16进制)和unicode
- transform-es2015-modules-commonjs // 将modules编译成commonjs
- transform-es2015-object-super // 编译super
- transform-es2015-parameters // 编译参数,包括默认参数,不定参数和解构参数
- transform-es2015-shorthand-properties // 编译属性缩写
- transform-es2015-spread // 编译展开运算符
- transform-es2015-sticky-regex // 正则添加sticky属性
- transform-es2015-template-literals // 编译模版字符串
- transform-es2015-typeof-symbol // 编译Symbol类型
- transform-es2015-unicode-regex // 正则添加unicode模式
- transform-regenerator // 编译generator函数
如果每次要开发者一个个添加并安装,配置文件很长不说,npm install
的时间也会很长,更不谈我们可能还要同时使用其他规范呢。
为了解决这个问题,babel 还提供了一组插件的集合。因为常用,所以不必重复定义 & 安装。(单点和套餐的差别,套餐省下了巨多的时间和配置的精力)。
preset 分为以下几种:
官方内容,目前包括 @babel/preset-env, @babel/preset-react, @babel/preset-typescript, @babel/preset-flow ;
babel-preset-stage-x,这里面包含的都是当年最新规范的草案,每年更新。
这里面还细分为
- Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
- Stage 1 - 提案: 初步尝试。
- Stage 2 - 初稿: 完成初步规范。
- Stage 3 - 候选: 完成规范和浏览器初步实现。
- Stage 4 - 完成: 将被添加到下一年度发布。
此外,低一级的 stage 会包含所有高级 stage 的内容,例如 stage-1 会包含 stage-2, stage-3 的所有内容。
stage-4 在下一年更新会直接放到 env 中,所以没有单独的 stage-4 可供使用。
- babel-preset-react-app create-react-app 官方预设;
4. 执行顺序
很简单的几条原则:
- Plugin 会运行在 Preset 之前。
- Plugin 会从前到后顺序执行。
- Preset 的顺序则 刚好相反(从后向前)。
preset 的逆向顺序主要是为了保证向后兼容,因为大多数用户的编写顺序是 [‘es2015’, ‘stage-0’]。这样必须先执行 stage-0 才能确保 babel 不报错。因此我们编排 preset 的时候,也要注意顺序。
配置
简略情况下,插件和 preset 只要列出字符串格式的名字即可。但如果某个 preset 或者插件需要一些配置项(或者说参数),就需要把自己先变成数组。第一个元素依然是字符串,表示自己的名字;第二个元素是一个对象,即配置对象。
最需要配置的当属 env,如下:
"presets": [
[
"@babel/preset-env",
{
// options ...
}
],
"stage-2"
]
1. 支持语法转换的配置
支持语法层面的转换通常是使用 @babel/preset-env
这个预设,要支持最新的语法特性单独配置plugins或者使用 babel-preset-stage-x
预设;
env 的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。例如目标浏览器支持 es2015,那么 es2015 这个 preset 其实是不需要的,于是代码就可以小一点(一般转化后的代码总是更长),构建时间也可以缩短一些。
下面列出几种比较常用的配置方法:
{
"presets": [
["@babel/env", {
// boolean, defaults to false.
"loose": false,
// "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto".
"modules": "auto",
// "usage" | "entry" | false, defaults to false.
"useBuiltIns": "usage",
"corejs: 3,
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}]
]
}
如上配置将考虑所有浏览器的最新2个版本(safari大于等于7.0的版本)的特性,将必要的代码进行转换。而这些版本已有的功能就不进行转化了
2. 支持api层面的转换
方式一: 使用 @babel/polyfill
,在入口处引入,官方这个包已经停止维护,不建议使用了;
import '@babel/polyfill';
缺点:
- 这个包停止维护了;
- 引入了全量的polyfill;
方式二:使用 @babel/preset-env
, 通过配置corejs
以及useBuiltIns
来支持新的api语法特性
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": 67,
"ie": 11
},
"corejs": 3,
// usage | entry
"useBuiltIns": "usage"
}
]
]
}
优点:
- 可以通过配置targets、useBuiltIns来动态生成目标环境的polyfil
缺点:
- 新的api是挂在window上,会污染全局环境;
- 支持新语法特性的辅助函数内联在文件内,造成代码冗余;
- useBuiltIns为usage时候虽然可以按需引入减小polyfill体积,但是若引用的第三方库未转到es5,且目标浏览器不支持该库的某些语法特性时,就会报错;
方式三:使用 @babel/preset-env
和 @babel/plugin-transform-runtime
来支持;
·
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": 67,
"ie": 11
},
"corejs": 3,
"useBuiltIns": "usage"
}
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
优点:
- 支持新语法特性api的辅助函数支持统一导入;
缺点:
- 新语法特性的api是挂在window上,会污染全局环境;
方式四:使用 @babel/preset-env
和 @babel/plugin-transform-runtime
来支持;
{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
优点:
- 新语法特性的api是挂在一个
{}
上,不会污染全局环境;
缺点:
- 无法支持targets目标浏览器环境的设置;
总结
- 方式一、方式二不建议使用;
- 个人业务建议使用方式三,useBuiltIns 配置为 entry,入口处引入 core-js;
- 公共模块,建议使用方式四;
六、平台端babel升级配置方案
- 去掉入口处
@babel/polyfill
```javascript
- import ‘@babel/polyfill’
- import ‘core-js’ ```
更新包
npm i @babel/preset-env@latest babel-preset-react-app -D
最终的
.babelrc
```json { “presets”: [ [ “@babel/preset-env”, {"useBuiltIns": "entry",
"corejs": 3,
"targets": {
// https://caniuse.com/?search=async
"chrome": "67"
}
} ], [ “babel-preset-react-app”, {
"flow": false,
"typescript": true
} ] ], “plugins”: [ [ “import”, {
"libraryName": "antd",
"libraryDirectory": "lib",
"style": true
} ], “lodash”, [ “module-resolver”, {
"root": ["."],
"alias": {
"@/pages": "./src/pages",
"@/src": "./src",
"@/utils": "./src/utils",
"@/services": "./src/services",
"qmkit": "./web_modules/qmkit",
"biz": "./web_modules/biz",
"web-common-modules": "./web-common-modules"
}
} ] ] }
---
<a name="sdzZc"></a>
## 七、演示示例 demo
[can i use](https://caniuse.com/?search=Array.form)
```javascript
const num = 1;
let arrowFn = () => 2;
const arr = [...[1, 2, 3], 4, 5, 6];
for (const item of arr) {
console.log(item);
}
const bbb = 0 ?? 123;
class A {}
async function asyncFn() {
console.log('exec');
const res = await Promise.resolve('resolve data');
console.log('res', res);
console.log('end');
}
asyncFn();
new Promise((resolve) => {
resolve(123);
});
const arr = Array.from([1, 2, 3]);
console.log('array from', arr);