前端学习日记 # Babel

941 阅读18分钟

简介

Babel是下一代JavaScript语法的编译器,是基于Node.js开发的,现代前端开发必备。

作用

让开发者能够用上最新、最爽的javascript新特性(ES6+)来编写前端代码,又能兼容低版本浏览器(主要是IE),一句话,把ES6+(包括ES2015、ES2016...ESXXXX等) 转换成ES5

执行过程

image.png

由此可以看到babel的核心就是 parsetransformgenerator 三个部分

parser 编译器

@babel/parser用于将源码转换成AST树

无需单独安装,因为@babel/core内已包含,除非你想,也可以自己单独安装,命令如下

npm i @babel/parser -D

transform 转换过程

babel是依赖于babel插件进行代码转换的,默认babel是没有插件的,没有安装插件的babel只是会将源码生成AST树,然后在通过生成器生成和原来的源码一摸一样的代码,这样babel没有起到代码转换的效果!

babel插件发挥作用的地方基本都是在tranfrom这个阶段,只要安装好babel转换类插件就可以转换出想要的代码

Ps:babel插件分为 转换类插件工具类插件转换类插件需要手动安装,而工具类插件在安装@babel/core核心时已经内置了,它提供操作ast节点的功能

generator 生成器

@babel/generator用于将转换好的AST树生成代码

无需单独安装,因为@babel/core内已包含,除非你想,也可以自己单独安装,命令如下

npm i @babel/generator -D

安装

当项目用了ES6+特性,而且要兼容低版本浏览器时,需要使用babel来进行代码转换,需要先安装babel(核心包+命令包):

@babel/core
@babel/cli

@babel/core

侠义理解:我们常说的babel指的是@babel/core

介绍

整合几十个基本包,例如:@babel/parser编译器、@babel/generator生成器等,还包含了一些工具类插件,例如:@babel/types@babel/traverse等,用于提供操作ast节点的功能。

为何出现@babel/core包?
主要是为了便于使用,因为babel由三大部分组成,而且还依赖很多第三方包,如果我们自己一个一个安装的话,可能会出现版本不兼容问题,而且估计会疯掉,所以官方出了这个整合包

作用

用于解析、代码生成,默认缺失转换,因为转换依赖babel插件实现

安装

// 必须作为开发依赖
npm i @babel/core -D  

@babel/cli

babelwebpack类似,都把 CLI命令行工具 单独拆分出来,便于维护和按需导入,webpack命令行工具包是webpack-cli

介绍

babel官方提供的CLI命令行工具,主要是提供babel这个命令

作用

提供了一种通过命令来使用babel的方式,简称命令方式,其实现在很多工具也提供这类方式,例如:webpack提供的webpack-cli

注意: 虽然提供了这种命令方式来使用babel,但由于命令方式选项过于繁多,编写起来不方便,所以不适合开发和生产使用,一般只是学习时使用,开发和生产一般使用集成方式,后面会讲

安装

// 必须作为开发依赖
npm i @babel/cli -D 

配置

为何要配置?
打个比喻,运行命令 === 派你去北京出差(执行任务),配置好比执行任务的详情,告诉你应该是坐飞机去,还是坐汽车,还是坐高铁去,是早上去还是晚上去,是一伙去还是一个人去等,如果没有配置,就相当于使用默认配置(潜规则),所以一般命令都会附有很多配置项

虽然使用命令可以运行babel进行转码,但你要进行配置的话,如下命令,是极其恶心,而且很容易写错(特别是配置特别多的时候)

npx babel src -d lib --plugins=@babel/plugin-transform-arrow-functions --presets=@babel/preset-env

所以不推荐在命令上进行配置(不是不推荐命令,你要区分好哦 )。

【推荐】 命令还可以使用,但配置要抽离到一个独立文件进行统一管理,而这个文件名就是最让初学者最混乱的地方

文件名分类

文件名分类的话,有两大类:

// 第一种
babel.config.json
babel.config.js
babel.config.cjs
babel.config.mjs   // 采用ESM模块化的写法

// 第二种
.babelrc           // 是 .babelrc.json 的别名
.babelrc.json
.babelrc.js
.babelrc.cjs
.babelrc.mjs

其实可以直接配置在 package.jsonbabel 字段,但不推荐

作用范围分类的话,有两大类:

// 第一种:针对项目
babel.config.json
babel.config.js
babel.config.cjs
babel.config.mjs   // 采用ESM模块化的写法

// 第二种:针对文件
.babelrc           // 是 .babelrc.json 的别名
.babelrc.json
.babelrc.js
.babelrc.cjs
.babelrc.mjs
package.json 中 babel 选项

功能分类的话,有两大类:

// 第一种:可编程配置
babel.config.js
babel.config.cjs
babel.config.mjs   // 采用ESM模块化的写法
.babelrc.js
.babelrc.cjs
.babelrc.mjs

// 第二种:静态配置
.babelrc           // 是 .babelrc.json 的别名
.babelrc.json
babel.config.json
package.json 中 babel 选项

可编程类配置文件可以调用Node的任意API,所以灵活性更大,功能更强大

最常用的配置文件分别是:babel.config.jsbabel.config.json.babelrc.babelrc.js 这四种

babel.config.js 与 .babelrc.js

这两个配置文件的代码一样,使用的都是CommonJS模块规范

module.exports = function (api) {
  api.cache(true);

  const presets = [ ... ];
  const plugins = [ ... ];

  return {
    presets,
    plugins
  };
}

或者

const presets = [ ... ];
const plugins = [ ... ];

module.exports = { presets, plugins };

babel.config.json 与 .babelrc

这两个配置文件的代码一样,直接是一个json对象

 {
    "presets": [...],
    "plugins": [...]
 }

babel.config.js 和 .babelrc 区别

babel.config.js(json/cjs/mjs)是babel7引入的,主要解决babel6一些问题

1、babel.config.js(json/cjs/mjs)作为全局配置(项目根目录下),.babelrc(.json/js/cjs/mjs)作为局部配置(局部优先级大于全局)
2、以前babel会递归向上查找.babelrc局部配置,而现在检索行为会停在package.json所在层级。

babel.config.js(json/cjs/mjs)是全局配置(项目配置),.babelrc(.json/js/cjs/mjs )是局部配置

请看

查看2

插件(Plugin)

babel默认不内置代码转换功能,代码转换全依赖babel插件(转化类)实现,所以插件对于babel非常重要。 我们一般不会自己开发babel转换类插件,实际项目中往往都是直接使用现成插件(官方或第三方)。从官网插件列表中,我们发现插件是非常丰富的

PS:下面所讲的 插件 都是指 转换类插件

安装与使用

安装很简单,在官方插件列表中找到需要的插件,使用下面命令就可以了

// 需要把箭头函数转译成ES5的普通函数,需要安装
npm i @babel/plugin-transform-arrow-functions --save-dev

// 需要把ES6的函数参数转译成ES5的函数参数,需要安装
npm i @babel/plugin-transform-parameters -D

安装完,马上使用,使用有2钟方式

1、命令方式
2、配置方式(推荐)

/* 命令方式 */
npx babel src -d lib --plugins=@babel/plugin-transform-arrow-functions

// 两个以上插件一起用的话,用","隔开
npx babel src -d lib --plugins=@babel/plugin-transform-arrow-functions,
@babel/plugin-transform-parameters

/* 配置方式 */

// 在项目根目录下,新建babel.config.json配置文件
{
    "plugins": [
        "@babel/plugin-transform-arrow-functions",
        "@babel/plugin-transform-parameters"
    ]
}

省略前缀写法

每个插件都是以plugin-作为前缀,每次使用插件时,都要重复编写挺麻烦的,所以babel官方提供了省略plugin-前缀的简易写法

上面2钟方式的省略写法如下:

/* 命令方式 */
npx babel src -d lib --plugins=@babel/transform-arrow-functions

// 两个以上插件一起用的话,用","隔开
npx babel src -d lib --plugins=@babel/transform-arrow-functions,@babel/transform-parameters

/* 配置方式 */

// 在项目根目录下,新建babel.config.json配置文件
{
    "plugins": [
        "@babel/transform-arrow-functions",
        "@babel/transform-parameters"
    ]
}

执行顺序

当配置多个插件时(命令方式一样),按书写从前到后的顺序执行,例如:

// babel.config.json配置文件
{
    "plugins": [
        "@babel/transform-arrow-functions",
        "@babel/transform-parameters"
    ]
}

执行顺序为:先执行@babel/transform-arrow-functions,然后执行@babel/transform-parameters

配置选项

Babel配置文件中,除了声明所用到的Babel插件外,还可以对用到的Babel插件进行配置(前提条件是该插件提供可配置项啦,否则配置个寂寞?),写法如下:

// 在babel.config.json中
{
    "plugins": [ 
        [ 
            "transform-async-to-module-method", 
            { 
                "module": "bluebird", 
                "method": "coroutine" 
            }
        ] 
    ]
}

问题

我们已经学会安装使用插件,babelES6+语法转换,都是靠一个个plugins完成的。再看一眼插件列表,~ ~突然懵逼了,这么多插件难道要我一个一个安装使用吗?

答案:当然不会啦,如果要一个个安装使用,估计没人会使用babel来代码转换,babel官方给出了一个解决方案 —— 预设(preset),后面会讲解

注意事项

转换类插件功能 进行分类,大概分为三类:

1、ES6+语法转换插件

这种插件是最常用的
主要对ES6+最新的语法糖进行转换,并不负责转换ES6+新增的api和全局对象。 例如:let/const就可以被转换,而includes/Object.assign等新API并不能被转换,常用的ES6+语法转换插件有:@babel/plugin-transform-arrow-functions@babel/plugin-transform-parameters

2、补充转换插件

对上面ES6+语法转换插件的一种补充,解决它不能解决的问题
主要对ES6+新增的api和全局对象进行转换 例如:babel-plugin-transform-runtime这个插件能够转换Object.assign,同时也可以引入@babel/polyfill,进一步对includes这类新API保证在浏览器的兼容性。

一般项目使用 @babel/polyfill 补充包,而 工具/库 才使用 babel-plugin-transform-runtime 插件

3、其他转换插件

TS、Flow、React(JSX)转换插件,转译JSX语法TS语法,移除类型声明

预设(Preset)

Babel插件尽可能拆成小的粒度,开发者可以按需引入。比如对ES6转ES5的功能,Babel官方拆成了20+个插件。但在实际开发中,逐个插件引入不仅效率低下,而且很容易出错。为此,Babel官方提供了一个Babel插件集合 —— 预设(Preset),只要安装导入一个预设,就相当于把多个Babel插件安装导入了

安装与使用

安装很简单,在官方找到需要的预设,使用下面命令安装

// 安装把ES6+语法转成ES5的预设
npm i @babel/preset-env -D

已被纳入规范的语法(ES2015, ES2016, ...., ES2021, Modules)所需要使用的plugins都包含在@babel/preset-env

安装完,马上使用,使用有2钟方式

1、命令方式
2、配置方式(推荐)

/* 命令方式 */
npx babel src -d lib --presets=@babel/preset-env

// 使用两个以上预设的话,用","隔开
npx babel src -d lib --presets=@babel/preset-env,@babel/preset-typescript

/* 配置方式 */

// 在项目根目录下,新建babel.config.json配置文件
{
    "presets": [
       [ 
       "@babel/preset-env", 
           { 
               targets: { 
                   edge: "17", 
                   chrome: "64",
                   firefox: "60", 
                   safari: "11.1",
                   ie: "10"
               } 
           } 
        ]
    ]
}

省略前缀写法

每个预设都是以preset-作为前缀,每次使用预设时,都要重复编写挺麻烦的,所以babel官方提供了省略preset-前缀的简易写法

上面2钟方式的省略写法如下:

/* 命令方式 */
npx babel src -d lib --presets=@babel/env

// 两个以上预设一起用的话,用","隔开
npx babel src -d lib --presets=@babel/env,@babel/typescript

/* 配置方式 */

// 在项目根目录下,新建babel.config.json配置文件
{
    "presets": [
       [ 
       "@babel/env", 
           { 
               targets: { 
                   edge: "17", 
                   chrome: "64",
                   firefox: "60", 
                   safari: "11.1",
                   ie: "10"
               } 
           } 
        ]
    ]
}

配置选项

Babel配置文件中,除了声明所用到的Babel预设外,还可以对用到的Babel预设进行配置(前提条件是该预设提供可配置项啦,否则配置个寂寞?),写法与插件的配置选项一样!

  • targets

用于指定编译后能运行在的目标浏览器(最低版本),以便可以按需引入 pluginployfill

{
    "presets": [
       [ 
       "@babel/env", 
           { 
               targets: { 
                   edge: "17", 
                   chrome: "64",
                   firefox: "60", 
                   safari: "11.1",
                   ie: "10"
               } 
           } 
        ]
    ]
}

targets 和 browerslist 合并取最低版本

  • modules

用于指定代码转换后的模块化类型(ES模块化转成什么类型),默认值是auto,最终转成CommonJS模块化

如果想转成(保留)ES模块化,可设置为false

  • useBuiltIns

用于指定加载ployfill的方式,默认是false

可选值有3种:"usage" | "entry" | false

1、"entry":去掉目标浏览器已支持的ployfill模块,将浏览器不支持的都引入对应的ployfill模块。
2、"usage":打包时会自动根据实际代码的使用情况,结合targets按需引入代码里实际用到部分polyfill模块。
3、false:不加载任何ployfill

推荐使用"usage"

// 使用entry时,在入口main.js中引入

(babel v7.0版之前,推荐使用) 
//import '@babel/polyfill'; 

(babel v7.4版之前,推荐使用) 
import "core-js/stable"; 
import "regenerator-runtime/runtime";

(babel v7.4版之后,推荐使用) 
import "core-js";

执行顺序

和插件执行顺序刚好相反,多个Preset按照声明次序逆序执行。

例如:需要将react中的jsx转为js,然后将js在转换为es5,所以需要将react的插件放在后面,让他先执行。

// 在babel.config.json中配置
{
    presets: ["@babel/env", "@babel/react"]
}

如果既有Babel插件,又有Babel预设,那么执行顺序如何呢?

答案: 插件 的优先级高于 预设,先执行插件(按照声明的插件顺序执行),再执行预设(按照声明的预设逆序执行)

按需加载插件

@babel/preset-env包含 ES2015ES2016、....... 等最新ES6+特性转换插件,但现在很多最新浏览器也支持ES6+部分新特性,如果你的代码只需运行在这些高级(新)浏览器上,那已支持的ES6+新特性就无需对应的Babel插件转换,大大提高代码转译性能

@babel/preset-env已支持按需加载功能,只要我们指定目标浏览器及其版本,@babel/preset-env就会根据目标浏览器的特性(未支持ES6+特性)加载对应Babel插件进行代码转换

目标浏览器指定有2种方式:
1、在Babel配置文件中通过targets属性指定
2、使用.browserslistrc文件来指定 (推荐)

PS:指定目标浏览器,除了用于@babel/preset-env按需加载对应转换插件外,还用于autoprefixer根据目标浏览器添加的 CSS 浏览器前缀(CSS3兼容处理

查看 browserslist 的更多配置

// .browserslistrc 配置案例
ie >= 8
Firefox >= 3.5
chrome  >= 35
opera >= 11.5

注意事项

1、@babel/preset-env完全替代babel-preset-es2015/es2016/es2017/......(以前使用的ES6+转换插件),说白了就是把babel-preset-es2015/es2016/es2017/......废弃掉。另外@babel/preset-env默认不支持stage-x预设(已废弃)

2、从Babel7开始,官方就把stage-x(预设babel-preset-stage-0/1/2/3/4)废弃掉,请看

3、预设只是插件的一个集合,默认@babel/preset-env只会对ES6+新语法进行转换,也就是我们看到的箭头函数、const/let这类。如果进一步需要转换内置对象、实例方法(api),那就需要接下来介绍的polyfill补充。

PS:为何开头是说 “默认” 两字呢?因为polyfill@babel/preset-env有关联的,所以只要在@babel/preset-env内配置useBuiltIns选项就可以启用polyfill,默认是禁用polyfill,详细的内容下面会讲

补充(polyfill)

官方的'polypill'是@babel/polyfillBabel 7.4.0 开始@babel/polyfill这个包已经废弃

上文也提到了,比如像PromiseGeneratorSymbol这种新内置对象、数组includes()新API,咱们的 babel 并没有给转译,那是因为 babel插件 默认是只会去转译ES6+语法的。这个时候就要出动@babel/polyfill补充包来模拟ES6+环境:

安装与使用

安装命令如下:

npm i @babel/polyfill -S   // 要生产安装 -S

为何要用-S而不是-D安装 ? 看下使用polyfill转换后的代码,你就能明白原因了

// 原来的代码 
var hasTwo = [1, 2, 3].includes(2); 

// 转后的代码 
require("core-js/modules/es7.array.includes"); 
require("core-js/modules/es6.string.includes"); 
var hasTwo = [1, 2, 3].includes(2);

使用非常简单,只要给@babel/preset-env配置一个useBuiltIns选项就可以了,其中useBuiltIns选项可选值有3种:"usage""entry" 、 false,默认是false

一般我们把useBuiltIns设为"usage",那么babel转换代码时,就会根据以下2个条件进行 按需导入 补充

  1. 目标浏览器
  2. 代码(含ES6+的新API和内置新对象等)
// babel.config.json配置
{
    "presets": [
       [ 
       "@babel/env", 
           { 
               targets: { 
                   edge: "17", 
                   chrome: "64",
                   firefox: "60", 
                   safari: "11.1",
                   ie: "10"
               },
               useBuiltIns: "usage"
           } 
        ]
    ]
}

已废弃

由于 Babel 7.4.0 开始@babel/polyfill这个包已经废弃,上面介绍的内容仅适合 Babel 7.4.0 之前版本,但即使是7.4.0之前的版本,也建议你不要用@babel/polyfill,因为@babel/polyfill一年没更新了,但ES6+新语法每年都在新增,换句话说,就是今年最新的JS语法@babel/polyfill已不支持了

前置知识

@babel/polyfill是由core-js2regenerator-runtime组成的一个集成包
(即@babel/polyfill = core-js@2 + regenerator-runtime)

regenerator-runtime:用于对generatorasync新特性提供补充 (es6 异步关键词的实现)
core-js:用于对ES6+新特性(例如:includes()Array.from等)提供补充(es6 的对象和方法实现)

解决方案

官方推荐单独使用core-jsregenerator-runtime包,然后设置@babel/preset-envcorejs选项为3

如果你安装了@babel/polyfill包,你可以先卸载:npm uninstall @babel/polyfill

安装core-jsregenerator-runtime补充包,当前最新版本是3.17.3

npm i core-js regenerator-runtime --save  // 记住必须用 --save 安装

regenerator-runtime包可以不按装,因为@babel/preset-env已经内置了,手动安装只是更新下包版本,万事俱备只剩下配置了

// babel.config.json配置
{
    "presets": [
       [ 
           "@babel/env", 
           { 
               targets: { 
                   edge: "17", 
                   chrome: "64",
                   firefox: "60", 
                   safari: "11.1",
                   ie: "10"
               },
               useBuiltIns: "usage",
               corejs: 3
           } 
        ]
    ]
}

到此,就完美解决@babel/polyfill废弃问题

为何使用core-js@3,而不是core-js@2 ?

@babel/polyfill内置core-js包版本默认是 2

说一下使用 core-js@3 的原因,core-js@2 分支中已经不会再添加新特性,新特性都会添加到 core-js@3。例如你使用了 Array.prototype.flat(),如果你使用的是 core-js@2,那么其不包含此新特性。为了可以使用更多的新特性,建议大家使用 core-js@3

为何不再推荐使用@babel/polyfill呢?

1、@babel/polyfill已经一年多没更新了,而core-js@3不断在更新,core-js@3取代了@babel/polyfill

2、因为@babel/polyfill不支持从core-js@2平滑的过渡到core-js@3。所以core-js官方现在推荐我们使用polyfill的时候直接引入core-jsregenerator-runtime/runtime这两个包完全取代  @babel/polyfil 来为了防止重大更改

注意事项

对于库/工具, 如果不需要用到像Array.prototype.includes这样的实例方法, 可以使用transform runtime插件, 而不是使用污染全局的@babel/polyfill

对于应用程序, 我们建议安装使用@babel/polyfillBabel 7.4.0+ 之后版本用上面的方式)

开发工具库/组件库

如果需要开发工具库或者组件库,就不能使用@babel/polyfill,因为@babel/olyfill会导致全局污染,这时需要用到@babel/runtime-corejs3@babel/plugin-transform-runtime

@babel/runtime-corejs3

这个包主要提供所有语法转换会用到的辅助函数,安装如下

npm i --save @babel/runtime-corejs3

@babel/runtime最大的问题就是无法模拟实例上的方法,比如数组的includes方法,但使用新增的@babel/runtime-corejs3之后支持了,所以直接安装@babel/runtime-corejs3取代@babel/runtime

这样就解决了代码复用和最终文件体积大的问题。不过,这么多辅助函数要一个个记住并手动引入,平常人是做不到的,我也做不到。这个时候,Babel插件@babel/plugin-transform-runtime就来帮我们解决这个问题。

@babel/plugin-transform-runtime

这个包主要解决2个问题 1、辅助函数按需引入问题 2、解决全局污染问题

安装如下:

npm i @babel/plugin-transform-runtime -D

优化配置如下:

// babel.config.json配置
{
    "presets": [
       [ 
           "@babel/env", 
           { 
               targets: { 
                   edge: "17", 
                   chrome: "64",
                   firefox: "60", 
                   safari: "11.1",
                   ie: "10"
               }
           } 
        ]
    ],
    "plugins": [
        ["@babel/plugin-transform-runtime", {
            "corejs": 3
        }]
    ]
}

PS:
默认corejs为false,意思是按需加载@babel/runtime包中辅助函数
corejs为2,意思是按需加载@babel/runtime-corejs2包中辅助函数
corejs为3,意思是按需加载@babel/runtime-corejs3包中辅助函数 [推荐]

参考与感谢

这篇文章写作初衷是源于本人记性差(老了),经常学过的知识,随着时间的推移,总是忘记,所以决心把所学的记下来,当以后忘记时可以来个回忆。

在写之前,在网上做了资料收集和看了大量各位大佬们写的博客文章,也发现了很多很好很好的文章,非常感谢他们

我用尽自己仅有不多的思维能力,按我理解的方式进行梳理了一遍,然后记录下来,断断续续写了3天,终于搞好,希望也对你有点点帮助,喜欢的可以点个赞哦!!!