Babel工作的三个阶段
更详细一点:
•babylon 进行解析得到 AST
•plugin 用 babel-traverse 对 AST 树进行遍历转译,得到新的AST树
•用 babel-generator 通过 AST 树生成 ES5 代码
核心: babel-core: bable转译器本身,提供了babel的转译的API,如babel.transform等,用于对代码进行转译。wepack的babel的babel-loader就是调用这些API来完成转译的。 babylon : js的词法解析器。 -babel-traverse : 用于对AST(abstract syntax tree)抽象语法树的遍历,主要是给plugin用 babel-generator : 根据AST生成 代码
过程
Parse(解析):将源代码转换成更加抽象的表示方法(例如抽象语法树)
一般来说,Parse 阶段可以细分为两个阶段:词法分析(Lexical Analysis, LA)和语法分析(Syntactic Analysis, SA)。词法分析之后,代码就已经变成了一个 Tokens 数组了,现在需要通过语法分析把 Tokens 转化为上面提到过的 AST。
Transform(转换):对(抽象语法树)做一些特殊处理,让它符合编译器的期望
Babel 对于 AST 的遍历是深度优先遍历,对于 AST 上的每一个分支 Babel 都会先向下遍历走到尽头,然后再向上遍历退出刚遍历过的节点,然后寻找下一个分支。
Babel 会维护一个称作 Visitor 的对象,这个对象定义了用于 AST 中获取具体节点的方法。
var visitor = {
ArrowFunction(path) {
path.replaceWith(t.FunctionDeclaration(id, params, body));
}
};
Generate(代码生成):将第二步经过转换过的(抽象语法树)生成新的代码(babel-generator)
常见配置项
babel-polyfill
7.4+版本已废弃,取而代之的是core-js+regenerator,regenerator是async await generator等功能的实现 babel-polyfill 是为了模拟一个完整的ES2015+环境,babel-polyfill是一次性引入你的项目中的,并且同项目代码一起编译到生产环境。而且会污染全局变量。像Map,Array.prototype.find这些就存在于全局空间中。之所以出现他,是因为Babel默认只转换js语法,像Map,Set等API是不做转换的
为什么会被废弃?
1.babel-polyfill是整体引入的,会导致项目体积增大,(babel7之后可以使用useage解决)
2.对于静态方法会修改类函数,对于实例函数会直接修改原型
babel-runtime
babel-runtime 更像是一种按需加载的实现,比如你哪里需要使用 Promise,只要在这个文件头部 import Promise from 'babel-runtime/core-js/promise' 就行了
babel-transform-runtime(@babel/plugin-transform-runtime
作用:提取辅助函数和内置函数,自动polyfill不污染不污染全局变量 通常仅在开发时使用,但是运行时最终代码需要依赖 @babel/runtime,所以 @babel/runtime 必须要作为生产依赖被安装)
babel-runtime不会污染全局空间和内置对象原型。事实上babel-runtime是一个模块,你可以把它作为依赖来达成ES2015的支持。 比如环境不支持Promise,你可以在项目中加入
require(‘babel-runtime/core-js/promise’)
可以实现按需加载,但是在实际项目开发过程中,我们往往会写很多新的es6 api,每次都要手动引入相应的包比较麻烦,维护起来也不方便,每个文件重复引入也造成代码的臃肿。
要解决这个问题,就要用到 babel-plugin-transform-runtime,这个插件是用来复用辅助函数的。它会分析我们的 ast 中,是否有引用 babel-rumtime 中的垫片(通过映射关系),如果有,就会在当前模块顶部插入我们需要的垫片。
babel-runtime有个缺点,它不模拟实例方法,即内置对象原型上的方法,所以类似Array.prototype.find,你通过babel-runtime是无法使用的
babel-preset-env
不设置的话默认是2015~2020
能根据当前的运行环境,自动确定你需要的 plugins 和 polyfills。通过各个 es标准 feature 在不同浏览器以及 node 版本的支持情况,再去维护一个 feature 跟 plugins 之间的映射关系,最终确定需要的 plugins(@babel/preset-env 会根据你配置的目标环境,生成插件列表来编译。)
{
"presets": [
["env", {
"targets": {
"chrome": 52,
"browsers": ["last 2 versions", "safari 7"]
},
"modules": false,
"useBuiltIns": "usage",// 是否开启自动支持 polyfill,它能自动给每个文件添加其需要的poly-fill 开启此选项必须保证已经安装了babel-polyfills
"debug": false
}]
]
}
此时Babel 转换了浏览器不支持的箭头函数和 Class,但是 Promise 并没有变化。这是因为 Babel 只转换不兼容的新语法,而对新的 API,如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise、Object.assign() 等,是不会转换的。这时候就需要 polyfill 了
polyfill的方式:useBuiltIns
core-js
core-js 它是JavaScript标准库的 polyfill(垫片/补丁), 新功能的es'api'转换为大部分现代浏览器都可以支持运行的一个'api' 补丁包集合。
core-js在babel-preset-env中的应用
2.1."useBuiltIns: false" 此时不对'api 垫片做操作',你可以引入你想要的处理'es api'的垫片例如此时
需要在文件引入'import "core-js/stable"' 和 'import "regenerator-runtime/runtime"',则无视配置的浏览器兼容,
引入所有的 polyfill。
2.2."useBuiltIns: entry"根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手
文件引入'import "core-js/stable"' 和 'import "regenerator-runtime/runtime"',会自动根据 browserslist
替换成浏览器不兼容的所有 polyfill。这里需要指定 core-js 的版本(也就是要设置'corejs'字段版本)
2.3.useBuiltIns: usage" 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加。
总结 推荐使用."useBuiltIns: usage" 配置他自己动会做帮我们引入'corejs垫片',不用在手动全局引入,并且会对 指定浏览器版本进行配合
babel7
截止至2022 6月最新的Babel版本
- 不再只是
stage-x这种形式 - 主推env,减少配置成本
- 配置文件新增了
babel.config.js - 修改包名到Babel命名空间下
现代化构建
基于大部分现代浏览器对es6语法的支持,不再需要向下兼容到很低的浏览器版本。
1.设置babel的target为esModule,Babel会忽略browsers的配置,而是基于浏览器对esModule的支持生成代码
target:{
esmodule:true
}
2.基于现代浏览器<script type='module'/>以及<script nomodule/>来生成两个版本的代码
需要注意的是第一个标签会有跨域的问题。
3.在脚手架加入插件生成上述的结构,并且生成两个版本的代码,新的插入到前者,旧的插入到后者