文章目录
- babel作用
- babel版本
- babel核心包
- babel配置文件
- 对代码转换
- babel插件
- babel预设
- polyfill
- 按需导入实践
- 方式一:只按需配置@babel/preset-env
- 方式二:@babel/preset-env(useBuiltIns entry + corejs 3)
- 方式三:@babel/preset-env(useBuiltIns entry + corejs 3)+ @babel/plugin-transform-runtime
- 方式四:@babel/preset-env(useBuiltIns usage + corejs 3)+ @babel/plugin-transform-runtime
- 方式:@babel/preset-env + @babel/plugin-transform-runtime(corejs 3)
- 最佳实践
- 总结
转译过程一般分为三个阶段
![在这里插入图片描述](https://img-blog.csdnimg.cn/0897f47ff2194ba3828e807805419095.png)
- parse: 包含词法分析、语法分析和语义分析三个阶段,最终生成抽象语法树(AST)
- transform:会调用各种插件对 AST 进行增删改。
- generate:把 AST 转换成最终的目标代码并生成 sourcemap。
babel作用
- Babel的转换分为两部分syntax语法(let, const、箭头函数等)和api(Promise,Set,Symbol,Array.from,async )
babel版本
- Babel 7:采用了Monorepo单仓库的方式来管理其工具集,所以包的名字都是@babel/开头的比如@babel/core,@babel/cli
- Babel 6:包的名字都是babel-core,babel-cli
babel核心包
@babel/core:Babel转码的核心包,包括了整个 babel 工作流(已集成@babel/types),不安装 @babel/core,无法使用 babel 进行编译
@babel/cli: 终端运行工具,这样我们可以直接使用终端命令对指定的文件进行转译
@babel/node 提供了 babel-node 环境命令,babel-node 是 babel-cli 的一部分,它不需要单独安装。
@babel/register:babel-register 模块改写 require 命令,为它加上一个钩子。此后,每当使用 require 加载 .js、.jsx、.es 和 .es6 后缀名的文件,就会先用 babel 进行转码。
@babel/parser:解析器,将代码解析为 AST
@babel/traverse:遍历/修改 AST 的工具
@babel/generator:生成器,将 AST 还原成代码
@babel/types:包含手动构建 AST 和检查 AST 节点类型的方法
@babel/template:可将字符串代码片段转换为 AST 节点
babel配置文件
// .babelrc 文件配置项写法
{
"presets": [],
"plugins": []
}
// babelrc.js | babel.config.js(推荐)文件配置项写法
module.expors = {
"presets": [],
"plugins": []
}
// 内嵌在package.json文件中
{
"name": "babel_test",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"babel": {
"presets": [],
"plugins": []
}
}
babel插件和预设的执行顺序
- 插件比预设先执行
- 插件执行顺序是:plugins数组从前向后执行
- 预设执行顺序是:presets数组从后向前执行
对代码转换
babel插件
- 转译相应的api,我们就需要去安装相应的插件
- babel-plugin-transform-xx:转换插件,作用于@babel/core,负责转换 AST 的形态。绝大多数情况下我们都是在编写转换插件
- babel-plugin-syntax-xx:语法插件,主要是扩展编译能力,比如不在 async 函数作用域里面使用 await,如果不引入 @babel/plugin-syntax-top-level-await,是没办法编译成 AST 树的。并且会报 Unexpected reserved word ‘await’ 这种类似的错误。作用于 @babel/parser
- babel-plugin-proposal-xx:用来编译和转换在提案中的属性,比如 class-properties、decorators。
// babel.config.js
module.exports = {
presets: [],
plugins: ["@babel/plugin-transform-arrow-functions"] //转换箭头函数
}
// 转译可选链
plugins: ["@babel/plugin-proposal-optional-chaining"]
Babel 插件本质上就是编写各种 visitor 去访问 AST 上的节点,并进行 traverse遍历修改。当遇到对应类型的节点,visitor 就会做出相应的处理,从而将原本的代码 transform 成最终的代码。
export default function (babel) {
// 即@babel/types,用于生成AST节点
const { types: t } = babel;
return {
name: "ast-transform", // not required
//将输入代码的所有标识符(Identifier)类型的节点名称颠倒。
visitor: {
Identifier(path) {
path.node.name = path.node.name.split("").reverse().join("");
},
},
};
}
插件名简写
如果插件名称为 @babel/plugin-XXX,可以使用短名称@babel/XXX :
{
"plugins": [
"@babel/transform-arrow-functions" //同 "@babel/plugin-transform-arrow-functions"
]
}
如果插件名称为 babel-plugin-XXX,可以使用短名称 XXX,该规则同样适用于带有 scope 的插件:
{
"plugins": [
"newPlugin", //同 "babel-plugin-newPlugin"
"@scp/myPlugin" //同 "@scp/babel-plugin-myPlugin"
]
}
@babel/plugin-transform-runtime 和 @babel/runtime
- 可实现按需加载
- @babel/plugin-transform-runtime则是开发依赖,编译时负责处理@babel/runtime,这也是为什么一个开发依赖一个生产依赖
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime // 如果使用了 @babel/runtime-corejs3 不需要安装这个
- 如果使用 @babel/plugin-transform-runtime 进行polyfill,即配置corejs:3
npm install @babel/plugin-transform-runtime --save-dev
npm install @babel/runtime-corejs3 --save //corejs2版本不支持实例上的方法转换
{
"devDependencies": {
"@babel/cli": "^7.17.6",
"@babel/core": "^7.17.9",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11"
},
"dependencies": {
"@babel/runtime-corejs3": "^7.17.9"
}
}
-
@babel/plugin-transform-runtime 运行依赖于 @babel/runtime|@babel/runtime-corejs3,我们需要在生产环境中安装 @babel/runtime|@babel/runtime-corejs3。
- “corejs”: 2。 不支持 实例方法 (例如 [1,2,3].includes(1))的转换
-
@babel/runtime,它内部集成了
- core-js: 转换一些内置类 (Promise, Symbols等等) 和静态方法 (Array.from 等)。绝大部分转换是这里做的。自动引入。
- regenerator: 作为 core-js 的拾遗补漏,主要是 generator/yield 和 async/await 两组的支持。当代码中有使用 generators/async 时自动引入
- useESModules:设置是否使用ES6的模块化用法,默认false;用webpack等打包工具的时候,我们可以设置为true,以便做静态分析
-
作用:
- 解决命名污染:没有直接修改 prototype,而是使用 _includes 去替代 includes 方法,这样就有效的避免全局环境的污染。
- 解决重复引入:所有的 polyfill 实现都被保存在 @babel/runtime-corejs3 这个包里面,我们需要的时候通过 require 导入进来就可以直接使用了
- 仅支持按需加载polyfill
// .babelrc
{
"presets": [[
"@babel/env" //只进行语法转换,不进行polyfill,交给@babel/plugin-transform-runtime进行
]],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3。
}
]
]
}
@babel/plugin-transform-runtime 会覆盖 @babel/preset-env中配置,
{
"presets": [
[
"@babel/preset-env",
// {
// "useBuiltIns": "usage", //就算没有注释 useBuiltIns 其行为也会被覆盖
// "corejs": 3
// }
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3 // 新增
}
]
]
}
babel预设
- 相当于插件的集合包,可以理解为安装了一个预设,就相当于安装了一系列的插件,通过预设可以减少开发者的配置
- 主要作用是对我们所使用的并且目标浏览器中缺失的功能进行代码转换和加载 polyfill
- 在不进行任何配置的情况下,@babel/preset-env 所包含的插件将支持所有最新的JS特性(ES2015,ES2016等,不包含 stage 阶段),将其转换成ES5代码
-
预设的一些命名
- -es2015系列:在Babel 6的时候,每当TC39发布新的ES标准语法的时候,都会有相应的预设,分别对应着每一年的新语法
- 比如babel-preset-es2015、babel-preset-es2016、babel-preset-es2017
- babel-preset-latest:在发布babel-preset-es2017之后,新推出的TC39每年发布的进入标准的ES语法的转换器预设集合,里边包含了之前所有的预设;
- -stage-0系列:分别对应着TC39发布新的ES标准语法的草案阶段,Babel 7之后babel-preset-stage-XXX版本的预设因为会造成理解混乱,所以也就不再更新;
- 比如:babel-preset-stage-0、babel-preset-stage-1、babel-preset-stage-2,stage是向下兼容 0>1>2>3>4 所包含的插件数量依次减少,stage-0,…
- -es2015系列:在Babel 6的时候,每当TC39发布新的ES标准语法的时候,都会有相应的预设,分别对应着每一年的新语法
@babel/preset-env
简写格式:@babel/env
- 主要作用是对我们所使用的并且目标浏览器中缺失的功能进行代码转换和加载 polyfill(前提是需配置 useBuiltIns),还包含了之前babel-preset-latest预设所有的功能、也可以转译草案中(babel-preset-stage-xxx)的语法
- 在不进行任何配置的情况下,@babel/preset-env 所包含的插件将支持所有最新的JS特性(ES2015,ES2016等,不包含 stage 阶段),将其转换成ES5代码
- 只配置targets或 browserslist,具有语法转换 + 添加辅助函数功能,但是没有polyfill
- 配置useBuiltIns,会根据配置进行polyfill
module.exports = {
presets: ["@babel/preset-env"],
plugins: []
}
根据不同环境按需引入(语法转换)
- 通过compat-table(维护每个特效在不同环境的支持情况),browserslist(具体的浏览器列表)实现精细控制
- 如果没有在 Babel 配置文件中(如 .babelrc)设置 targets 或 ignoreBrowserslistConfig,将会使用 browserslist 配置源。
// .babelrc
{
"presets": [
[
"@babel/env", // 也可以写@babel/preset-env两者是等价的
{
"targets": { //针对浏览器版本进行转换
"browsers": "Chrome 99"
}
}
]
]
}
useBuiltIns 根据使用的情况按需引入polyfill
- useBuiltIns
-
false:不对 polyfill 做任何操作
-
entry:根据 target 中浏览器版本的支持,将 polyfill 拆分引入,仅引入有浏览器不支持的 polyfill
- babel会根据我们.browserslistrc文件中的环境配置(这里是chrome 60版本),来补齐chrome 60版本所有不支持的新增ES6API
- 当使用useBuiltIns:entry时,你可以直接引入指定的polyfill比如import “core-js/proposals/string-replace-all”
- 相当于根据当前的运行环境缺失的API进行polyfill补齐
-
usage:检测代码的使用情况,仅仅加载代码中用到的 polyfill
- 但会带来一个问题,在node_modules内的库相关的API,如果代码中没使用,不会进行polyfill
-
- corejs:这个配置项只有当useBuiltIns:usage或useBuiltIns:entry时才有效,指定@babel/preset-env引入的core-js版本,corejs:n或corejs:{ version: n, proposals: 布尔 }
- corejs: 3 代表用core-js@3版本进行polyfill,需要手动npm install core-js -S来安装此版本
- corejs: 2 代表用core-js@2版本进行polyfill,需要手动npm install core-js@2 -S来安装此版本
- corejs配制成对象
如果useBuiltIns配置了false,就需要自行引入@babel/polyfill
// 这里其实可以不用显示安装 regenerator-runtime 模块,这是因为此模块已经被 @babel/preset-env 内置了
// 注意这是运行时依赖,无需 -D:
yarn add core-js regenerator-runtime
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry", // 新增
"corejs": 3 // 新增
}
]
],
}
import 'core-js';
import 'regenerator-runtime/runtime';
const p = new Promise(() => {});
如果useBuiltIns配置了entry,在项目入口引入 import "core-js和import “regenerator-runtime/runtime”。注意整个应用(bundle)只能使用一次。如果多次引入会报错。
- 除了useBuiltIns:false情况,其他都必须指定core-js版本
// 不用显示安装 regenerator-runtime 模块,这是因为此模块已经被 @babel/preset-env 内置了。
yarn add core-js@3
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry", // 新增
"corejs": 3 // 新增
}
]
],
}
import 'core-js';
import 'regenerator-runtime/runtime';
const p = new Promise(() => {});
如果useBuiltIns配置了usage
- 这种按需引入应该是最优的处理方式,但是不会处理第三方依赖包的引入模块,所以如果第三方依赖包使用了高级语法而未处理兼容性的话会出错。
const p = new Promise(() => {});
const f = async () => {};
// babel.config.js
module.exports = {
presets: [["@babel/preset-env", {
targets: { // 和.browserslistrc文件的配置一致
chrome: 30
},
useBuiltIns: 'usage',// 用什么方式进行polyfill
corejs:3, // corejs 的版本设置为 3,因为在 corejs@2 中已经不会再添加新的特性了,同时我们需要手动去安装 corejs@3
}]],
plugins: []
}
{
"devDependencies": {
"@babel/cli": "^7.17.6",
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.16.11"
},
"dependencies": {
"core-js": "^3.21.1"
}
}
缺点:
- 全局环境污染
- 代码的重复引入,像 _classCallCheck,_defineProperties这样的函数只要用到了 class 就会被引入进来,就会产生重复引入的问题
polyfill
- polyfill是为了补齐ES6、7等更高版本的API
- Promise、Map、Symbol、Proxy等
- Array.prototype.find(),Array.prototype.flat()等
// babel.config.js文件中
module.exports = {
presets: ["@babel/preset-env"],
plugins: []
}
//代码
let promise = Promise.resolve('success') //@babel/preset-env并不会对Promise这些高版本api转换
let obj = Object.assign({}, { age: 33 })
直接引入polyfill
- @babel/polyfill 相当于引入下面两个库(@babel/polyfill 已经被废弃,不再更新)
- require(“core-js”); 版本为2
- require(“regenerator-runtime/runtime”);
- @babel/core或@babel/plugin-transform-regenerator的版本大于7.18.0的时候 ,不用安装regenerator-runtime/runtime,已经包括进去了
// 使用CDN
<script src="https://cdn.bootcdn.net/ajax/libs/babel-polyfill/7.12.1/polyfill.js"></script>
// 下载polyfill.js然后本地引用
<script src="./polyfill.js"></script>
// 或者@babel/polyfill
// 实际是这俩个包的组合:core-js/stable(提供polyfill)和regenerator-runtime/runtime(提供generator和async方法,可以用runtime实现)
import '@babel/polyfill'
// 相当于
import 'core-js/stable' //core-js版本为3时,才能引入stable
import 'regenerator-runtime/runtime' // @babel/core版本是7.18.0以上的,则可以不安装regenerator-runtime并且不导入这个包,
不推荐直接引入polyfill
- @babel/polyfill:@babel/core 7.4.0版本开始官方已经不再推荐使用@babel/polyfill包了,其中用的core-js版本是2.0版本,以后也不会再升级更新,而2.0版本是不能够被转化一些API的,比如Array.prototype.flat()方法和Array.prototype.includes()方法
- 所以我们需要安装core-js的最新版本和regenerator-runtime这两个包
- polyfill完整的文件非常的大,所以就会大大增加了我们项目打包后的体积
- 使用的时候会导致helper方法(如:_classCallCheck)重复的导入
- 转化会造成全局污染:直接改写的prototype
core-js
- core-js是一种polyfill,它提供了旧版本浏览器缺失的所有的ES6+ API的方法与实现
- es:里面只包含有稳定的ES功能。
- proposals:里面包含所有stage阶段的API
- stable:它里面包含了,只有稳定的ES功能跟网络标准
所以,可以这么使用:
- 当我们只需要垫平某个稳定的ES6+ API,我们可以用es这个文件夹里的polyfill来垫平 (import X from ‘es/xx’)
- 当我们需要用到提案阶段的API时,我就用proposals这个文件夹里的polyfill来垫平(import X from ‘proposals/xx’)
- 当我们想垫平所有稳定版本的ES6+ API,可以导入用stable文件夹(import ‘core-js/stable’)
- 当我们想垫平所有的ES6+ API(包括提案阶段),可以直接import ‘core-js’
按需导入实践
方式一:只按需配置@babel/preset-env
// babel.config.js
module.exports = {
presets: [["@babel/preset-env", {
targets: { // 和.browserslistrc文件的配置一致
chrome: 30
},
useBuiltIns: 'usage',// 用什么方式进行polyfill
corejs:3, // corejs 的版本设置为 3,因为在 corejs@2 中已经不会再添加新的特性了,同时我们需要手动去安装 corejs@3
}]],
plugins: []
}
缺点
- 全局污染:改写了prototype
- 重复定义:会重复定义需要polyfill的api
方式二:@babel/preset-env(useBuiltIns entry + corejs 3)
- 需要在代码入口导入 core-js 和 regenerator-runtime/runtime,会根据配置的目标环境拆分成所需要的所有 polyfill
module.exports = {
presets: [["@babel/preset-env", {
targets:{
// ...
}
useBuiltIns: 'entry',
corejs:3,
}]],
plugins: []
}
缺点
- 全局污染:改写了prototype
- 重复定义:会重复定义需要polyfill的api
方式三:@babel/preset-env(useBuiltIns entry + corejs 3)+ @babel/plugin-transform-runtime
需要在代码入口导入 core-js 和 regenerator-runtime/runtime
优点:
- 会根据配置的目标环境拆分成所需要的所有 polyfill
- 不会重复定义辅助函数
缺点:
- 全局污染
方式四:@babel/preset-env(useBuiltIns usage + corejs 3)+ @babel/plugin-transform-runtime
优点:
- 会根据代码使用所需要的polyfill
- 不会重复定义辅助函数
缺点:
- 全局污染
- 因为是usage,可能造成node_modules里没有polyfill
方式:@babel/preset-env + @babel/plugin-transform-runtime(corejs 3)
优点:
- 会根据代码使用所需要的polyfill
- 不会重复定义辅助函数
- 不会全局污染
缺点:
- 可能造成node_modules里没有polyfill
最佳实践
- 日常业务
module.exports = {
// 作用:语法转换、polyfill api、按需导入
presets: [["@babel/preset-env", {
// targets不做配置,默认使用我们 .browserslistrc文件中的配置
// targets: {},
// 对所用语法进行polyfill,但依旧是改写prototype
useBuiltIns: 'usage',
corejs: {
// 使用最新版本的core-js进行polyfill
version: '^3.25.2',
// 开启草案中的polyfill转译
proposals: true
},
// 使用es6模块进行
modules: false
}]],
plugins: ["@babel/plugin-transform-runtime"] //作用:避免重复定义
}
- 类库
// babel.config.js
module.exports = {
presets: ["@babel/preset-env"], //只使用其转换语法功能
// 按需polyfill、避免污染全局和重复定义
plugins: [["@babel/plugin-transform-runtime", {
corejs: {
// 注意,要安装@babel/runtime-corejs3的包
version: 3,
proposals: true
}
}]]
}
总结
- Babel是一个新语法转旧语法的平台,它只对synax进行转义,对于api需要使用其对应的插件进行转化。
- 当前babel版本最优的使用方式应该是@babel/preset-env搭配@babel/plugin-transform-runtime,能够很好的处理高级语法的兼容性问题。