前言

wecom-temp-cc31b5fbcaf55e68014991f84c2f9bc1.png
平台端项目执行命令 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 是一个安全且流行的做法。

二、工作原理(浅析)

image.png

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。

三、使用方法

总共存在三种方式:

  1. 使用单体文件 (standalone script) (了解) ```html <!DOCTYPE html>

  1. 2. 命令行 (cli)
  2. ```bash
  3. npm i @babel/cli @babel/core -D
  4. npx babel src -d dist
  1. 构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。

其中第三种使用场景比较常见,直接集成到构建工具中。

这三种方式只有入口不同而已,调用的 babel 内核,处理方式都是一样的。

四、配置方式

配置:更多配置方式

在我们告诉 Babel 该做什么之前,我们需要创建一个配置文件。需要在项目的根路径下创建 .babelrc 文件。然后输入以下内容作为开始:

  1. {
  2. "presets": [],
  3. "plugins": []
  4. }

这个文件就是用来让 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、箭头函数,要这样配置:

  1. {
  2. "plugins": [
  3. "@babel/plugin-transform-block-scoping",
  4. "@babel/plugin-transform-classes",
  5. "@babel/plugin-transform-arrow-functions"
  6. ]
  7. }

但是光 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 分为以下几种:


这里面还细分为

  • 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,如下:

  1. "presets": [
  2. [
  3. "@babel/preset-env",
  4. {
  5. // options ...
  6. }
  7. ],
  8. "stage-2"
  9. ]

1. 支持语法转换的配置

支持语法层面的转换通常是使用 @babel/preset-env这个预设,要支持最新的语法特性单独配置plugins或者使用 babel-preset-stage-x预设;

env 的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。例如目标浏览器支持 es2015,那么 es2015 这个 preset 其实是不需要的,于是代码就可以小一点(一般转化后的代码总是更长),构建时间也可以缩短一些。

下面列出几种比较常用的配置方法:

  1. {
  2. "presets": [
  3. ["@babel/env", {
  4. // boolean, defaults to false.
  5. "loose": false,
  6. // "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto".
  7. "modules": "auto",
  8. // "usage" | "entry" | false, defaults to false.
  9. "useBuiltIns": "usage",
  10. "corejs: 3,
  11. "targets": {
  12. "browsers": ["last 2 versions", "safari >= 7"]
  13. }
  14. }]
  15. ]
  16. }

如上配置将考虑所有浏览器的最新2个版本(safari大于等于7.0的版本)的特性,将必要的代码进行转换。而这些版本已有的功能就不进行转化了

2. 支持api层面的转换

方式一: 使用 @babel/polyfill,在入口处引入,官方这个包已经停止维护,不建议使用了;
  1. import '@babel/polyfill';

缺点:

  1. 这个包停止维护了;
  2. 引入了全量的polyfill;

方式二:使用 @babel/preset-env, 通过配置corejs以及useBuiltIns来支持新的api语法特性
  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env",
  5. {
  6. "targets": {
  7. "chrome": 67,
  8. "ie": 11
  9. },
  10. "corejs": 3,
  11. // usage entry
  12. "useBuiltIns": "usage"
  13. }
  14. ]
  15. ]
  16. }

优点:

  1. 可以通过配置targets、useBuiltIns来动态生成目标环境的polyfil

缺点:

  1. 新的api是挂在window上,会污染全局环境;
  2. 支持新语法特性的辅助函数内联在文件内,造成代码冗余;
  3. useBuiltIns为usage时候虽然可以按需引入减小polyfill体积,但是若引用的第三方库未转到es5,且目标浏览器不支持该库的某些语法特性时,就会报错;

方式三:使用 @babel/preset-env@babel/plugin-transform-runtime来支持;

·

  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env",
  5. {
  6. "targets": {
  7. "chrome": 67,
  8. "ie": 11
  9. },
  10. "corejs": 3,
  11. "useBuiltIns": "usage"
  12. }
  13. ]
  14. ],
  15. "plugins": [
  16. "@babel/plugin-transform-runtime"
  17. ]
  18. }

优点:

  1. 支持新语法特性api的辅助函数支持统一导入;

缺点:

  1. 新语法特性的api是挂在window上,会污染全局环境;

方式四:使用 @babel/preset-env@babel/plugin-transform-runtime来支持;
  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env"
  5. ]
  6. ],
  7. "plugins": [
  8. [
  9. "@babel/plugin-transform-runtime",
  10. {
  11. "corejs": 3
  12. }
  13. ]
  14. ]
  15. }

优点:

  1. 新语法特性的api是挂在一个{}上,不会污染全局环境;

缺点:

  1. 无法支持targets目标浏览器环境的设置;

总结
  1. 方式一、方式二不建议使用;
  2. 个人业务建议使用方式三,useBuiltIns 配置为 entry,入口处引入 core-js;
  3. 公共模块,建议使用方式四;

六、平台端babel升级配置方案

  1. 去掉入口处 @babel/polyfill ```javascript
  • import ‘@babel/polyfill’
  • import ‘core-js’ ```
  1. 更新包

    1. npm i @babel/preset-env@latest babel-preset-react-app -D
  2. 最终的 .babelrc ```json { “presets”: [ [ “@babel/preset-env”, {

    1. "useBuiltIns": "entry",
    2. "corejs": 3,
    3. "targets": {
    4. // https://caniuse.com/?search=async
    5. "chrome": "67"
    6. }

    } ], [ “babel-preset-react-app”, {

    1. "flow": false,
    2. "typescript": true

    } ] ], “plugins”: [ [ “import”, {

    1. "libraryName": "antd",
    2. "libraryDirectory": "lib",
    3. "style": true

    } ], “lodash”, [ “module-resolver”, {

    1. "root": ["."],
    2. "alias": {
    3. "@/pages": "./src/pages",
    4. "@/src": "./src",
    5. "@/utils": "./src/utils",
    6. "@/services": "./src/services",
    7. "qmkit": "./web_modules/qmkit",
    8. "biz": "./web_modules/biz",
    9. "web-common-modules": "./web-common-modules"
    10. }

    } ] ] }

  1. ---
  2. <a name="sdzZc"></a>
  3. ## 七、演示示例 demo
  4. [can i use](https://caniuse.com/?search=Array.form)
  5. ```javascript
  6. const num = 1;
  7. let arrowFn = () => 2;
  8. const arr = [...[1, 2, 3], 4, 5, 6];
  9. for (const item of arr) {
  10. console.log(item);
  11. }
  12. const bbb = 0 ?? 123;
  13. class A {}
  14. async function asyncFn() {
  15. console.log('exec');
  16. const res = await Promise.resolve('resolve data');
  17. console.log('res', res);
  18. console.log('end');
  19. }
  20. asyncFn();
  1. new Promise((resolve) => {
  2. resolve(123);
  3. });
  4. const arr = Array.from([1, 2, 3]);
  5. console.log('array from', arr);

八、参考