Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
一、Preset 和 Plugin
1. Preset
预设: 是plugin组成的合集,将一些插件整合为一个包
2. Plugin
插件:其实Preset 跟Plugin没有根本区别,它是一些插件的集合
二、Babel常见Preset 和 常见Plugin
1. 常见Preset
1、@babel/preset-env 它是一个智能预设,可以将高版本的JavaScript代码根据内置的规则转译成为低版本的javascript代码
2、@babel/preset-react
3、@babel/preset-typescript
对于ts,有两种方式将其转为js:
- 使用tsc命令,结合cli命令行参数方式 或者tsconfig配置文件进行ts编译
- 使用babel,通过@babel/preset-typescript进行ts代码编译
2. 常见Plugin
1、@babel/plugin-transform-runtime
三、前端基建中的Babel配置详解
1. Webpack打包工具中日常使用的Babel配置 主要涉及的三个插件
1、babel-loader
2、@babel/core
3、@babel/preset-env
2. babel-loader
本质上是一个函数,匹配对应的文件jsx?/tsx?交给babel-loader
// options babel-loader 相关参数
function babelLoader(sourceCode, options) {
//
return targetCode
}
关于options: babel-loader支持直接通过loader的参数形式注入
同时也在loader函数内部通过读取 .babelrc/babel.config.js/babel.config.json等文件注入配置
3. @babel/core
babel-loader 仅仅是 识别 匹配文件和 接收 对应参数的函数, 那么babel在编译代码过程中核心的库就是@babel/core这个库。
babel-core是核心编译库,对代码进行词法分析、语法分析、语义分析,生成AST抽象语法树(@babel/parse),从而对这颗树操作, 再编译成为新的代码(@babel/generator);
babel-core 是 @babel/parse 和 @babel/generator 两个包的合体
const core = require('@babel/core')
function babelLoader(sourceCode, options) {
// 通过transform 方法编译传入的源代码
core.transform(sourceCode);
return targetCode
}
4. @babel/preset-env
babel-loader 是一个函数,在函数内部 通过 @babel/core 核心库 对 源代码进行编译;
但是代码的转译需要告诉babel 是以什么规则进行转化? @babel/preset-env就是告诉babel 以什么样的规则进行转化
const core = require('@babel/core')
function babelLoader(sourceCode, options) {
// 通过transform 方法编译传入的源代码
core.transform(sourceCode, {
presets: ['@babel/preset-env'],
plugins: [...],
});
return targetCode
}
四、Babel 相关的polyfill
1. polyfill概念
1、最新ES 语法: const/let
2、最新ES API: Promise
3、最新ES 实例/静态方法: String.prototype.includes()
1)@babel/preset-env 只能帮助babel转化最新ES 语法, 不能转化 最新ES API 和 最新ES 实例/静态方法,所以就需要额外引入polyfill来实现;
2)语法层面的转化preset-env完全可以胜任,但是一些内置方法模块,仅仅通过preset-env的语法转化是无法进行识别转化的; 所以就需要一系列类似”垫片“的工具进行补充实现这部分内容的低版本代码实现; 这就是所谓的polyfill的作用。
2. 针对polyfill的解决方案
两种方案
1) preset-env 搭配 @babel/polyfill, 这种方式可能会污染全局作用域
2) @babel/plugin-transform-runtime 搭配 @babel/runtime, 这种方式不会污染全局作用域
3. @babel/polyfill
3.1--定义
通过@babel/polyfill通过往全局对象上添加属性以及直接修改内置对象的Prototype上添加方法实现polyfill。
比如说我们需要支持String.prototype.include,在引入babelPolyfill这个包之后,它会在全局String的原型对象上添加include方法从而支持我们的Js Api。
我们说到这种方式本质上是往全局对象/内置对象上挂载属性,所以这种方式难免会造成全局污染。
3.2--应用
在@babel/preset-env中存在一个useBuiltIns参数,这个参数决定了如何在preset-env中使用@babel/polyfill
{
"presets": [
[
"@babel/preset-env",{
"useBuiltIns": false
}
]
]
}
useBuiltIns--"usage"| "entry"| false
1、false
当我们使用preset-env传入useBuiltIns参数时候,默认为false。它表示仅仅会转化最新的ES语法,并不会转化任何API和方法。
2、entry
当传入entry时,需要我们在项目入口文件中手动引入一次@babel/polyfill, 它会根据我们配置的浏览器兼容性列表(browserList)然后全量引入不兼容的polyfill
// 项目入口文件中需要额外引入polyfill
import "@babel/polyfill"
// babel
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "entry"
}]
]
}
Tips
1)在Babel7.4.0之后,@babel/polyfill被废弃它变成另外两个包:"core-js/stable"、 "regenerator-runtime/runtime";
2)core-js 2.0中是使用"@babel/polyfill" core-js3.0版本中变化成为了上边两个包
3)在我们使用useBuiltIns:entry/usage时,需要额外指定core-js这个参数
4)默认为使用core-js 2.0,所谓的core-js就是我们上文讲到的“垫片”的实现。它会实现一系列内置方法或者Promise等Api**
5)core-js 2.0 是跟随preset-env一起安装的,不需要单独安装
配置entry缺点 配置为entry时,preset-env会基于浏览器browserList,全量引入不兼容的polyfill,造成引入体积太大。
所谓全量引入:假如代码里只使用了Array.from这个方法,但是polyfill不仅仅引入Array.from,同时也会引入Promise、Array.prototype.includes等其他没有使用的方法,造成引入的体积太大
3、usage
当配置为usage时,会根据配置的浏览器兼容,以及代码中 使用到的Api 进行引入polyfill按需添加;
当使用usage时,我们不需要额外在项目入口中引入polyfill了,它会根据我们项目中使用到的进行按需引入
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"core-js": 3,
}]
]
}
配置usage的优缺点
1)优点:配置为usage时,preset-env会基于各个模块去分析它们使用到的polyfill从而进行引入;智能的在需要的地方都引入;
2)缺点:在usage情况下,如果我们存在很多个模块,那么无疑会多出很多冗余代码(import语法)
a.js
import "core-js/modules/es.promise";
b.js
import "core-js/modules/es.promise";
4. @babel/runtime
@babel/polyfill存在污染全部变量的副作用,在实现polyfill时Babel还提供了另外一种方式 @babel/runtime
1、@babel/runtime更像是一种按需加载的解决方案: 比如哪里需要使用到Promise,@babel/runtime就会在他的文件顶部添加import promise from 'babel-runtime/core-js/promise'。
2、对于preset-env的useBuintIns配置项,polyfill是preset-env帮我们智能引入
3、@babel/runtime则会将引入方式由智能完全交由我们自己,我们需要什么自己引入什么。 它的用法很简单,只要我们去安装npm install --save @babel/runtime后,在需要使用对应的polyfill的地方去单独引入就可以了。比如:
// a.js 中需要使用Promise 我们需要手动引入对应的运行时polyfill
import Promise from '@babel/runtime/core-js/promise'
const promsies = new Promise()
Tips
1、@babel/runtime是一个运行时“哪里需要引哪里”的工具库
2、@babel/runtime绝大多数情况下都会配合@babel/plugin-transfrom-runtime进行使用,达到智能化runtime的polyfill引入
@babel/runtime在我们手动引入一些polyfill的时候,它会给我们的代码中注入一些类似_extend(), classCallCheck()之类的工具函数,这些工具函数的代码会包含在编译后的每个文件中,比如:
class Circle {}
// babel-runtime 编译Class需要借助_classCallCheck这个工具函数
function _classCallCheck(instance, Constructor) { //... }
var Circle = function Circle() { _classCallCheck(this, Circle); };
如果我们项目中存在多个文件使用了class,那么无疑在每个文件中注入这样一段冗余重复的工具函数将是一种灾难
缺点
1、@babel/runtime 无法做到智能化分析,只能手动引入
2、@babel/runtime 编译过程中会重复生成冗余代码
5. @babel/plugin-transform-runtime
主要解决 @babel/runtime 两个缺点
1、@babel/runtime 无法做到智能化分析,只能手动引入 @babel/plugin-transform-runtime 插件会智能化的分析我们的项目中所使用到需要转译的js代码,从而实现模块化从babel-runtime中引入所需的polyfill实现
2、@babel/runtime 编译过程中会重复生成冗余代码 @babel/plugin-transform-runtime插件提供了一个helpers参数,这个helpers参数开启后可以将上边提到编译阶段重复的工具函数,比如classCallCheck, extends等代码转化称为require语句。此时,这些工具函数就不会重复的出现在使用中的模块中了。
// @babel/plugin-transform-runtime会将工具函数转化为require语句进行引入
// 而非runtime那样直接将工具模块代码注入到模块中
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() { _classCallCheck(this, Circle); };
@babel/plugin-transform-runtime 配置参数
{ "plugins":
[
[ "@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"version": "7.0.0-beta.0"
}
],
],
}
五、总结:
通常选择是会在开发类库时遵守不污染全局为首先使用@babel/plugin-transform-runtime, 在业务开发中使用@babel/polyfill
插件在 Presets 前运行。 插件顺序从前往后排列。 Preset 顺序是颠倒的(从后往前)。
---自己总结用