哈喽,大家好,我是Fine。今天为大家带来一篇关于AST的知识扫盲。
以下是正文:
第一段实习就接到了个工程化的需求-写插件,对我这种工程化小白来说无疑是巨大的挑战,向多位前辈请教过后,决定先从 webpack 学起,然后再到编译器+AST,一步一步把工程化理解体系搭起来。如果觉得这篇文章对你有帮助,欢迎点赞关注😘
Babel :将 es6+的 JavaScript 代码编译成 目标环境支持的语法环境,并且对目标环境不支持的 api 自动 polyfill。编译过程主要分为三个阶段 (解析,转换,生成)
AST(抽象语法树):是
Babel
、tsc
等编译器 进行代码转换的核心数据结构,表示了源代码的抽象语法结构,以树状的形式表现编程语言的语法结构,每个节点都表示源代码中的一种结构。使得Babel
能够理解和操作代码。
TSC(TypeScript Compiler): typescript 官方的编译器,可以将 typescript 代码转换为 JavaScript 代码,也可以像 Babel 那样对代码进行编译,同样使用了 AST 作为代码转换和优化的核心数据结构,和 babel 的区别在后面第五点有介绍~
说白了就是以 JSON 形式存在的一棵树:Babel
,tsc
,Vue-cli
和 EsLint
等很多的工具和库的核心都是通过 AST 抽象语法树这个概念来实现对代码的检查、分析等操作的。在前端当中 AST 的使用场景非常广,比如在 Vue.js
当中,我们在代码中编写的 template
转化成 render function 的过程当中第一步就是解析模版字符串生成 AST。
JS 的许多语法为了给开发者更好的编程体验(例如:ES6+ 新增的语法),并不适合程序的理解,所以需要把源码转化为 AST 来更适合程序分析,浏览器的编译器一般会把源码转化为 AST 来进行下一步的分析来执行其他操作。通过了解 AST
这个概念,对深入了解前端的一些框架和工具是很有帮助的
参考文章:前端工程化基石 -- AST 🔥
我这里就不赘述了,用一张精美的图片展示:
Babel 是一个广泛使用的 JavaScript 编译器,它能够将现代 JavaScript 代码转换为向后兼容的 JavaScript 版本,以便在旧环境中运行。Babel 的核心功能之一就是它的插件系统,允许开发者自定义代码转换的逻辑。下面,我将结合一些实际的代码示例来介绍 Babel 的插件系统(配置文件+preset 预设)。
在 Babel 中,配置文件(如
.babelrc
或babel.config.json
)和 preset 是两个不同的概念,但它们共同作用于 Babel 的转换过程。以下是它们之间的区别和详细说明:
用于指定转换代码的规则、插件和预设。开发者可以根据项目的需要创建和配置自己的
.babelrc
文件或者babel.config.json
。
.babelrc
:文件相对的配置文件,适用于项目的特定部分或子目录。babel.config.json
:项目范围的配置文件,通常放在项目的根目录下,对整个项目生效。
一组插件的集合,开发者可以通过使用预设来快速配置 Babel
的转换行为,而不需要一个个手动添加和配置各个插件。例如,@babel/preset-env
是一个常用的 Babel
预设,它根据目标环境的配置,自动选择需要的转换规则,以便将较新的 JavaScript
语法转换为目标环境兼容的代码。
常见预设:
@babel/preset-env
:根据目标环境和配置选项自动选择适合的插件,以实现对最新的 ECMAScript 语法和功能的转换。@babel/preset-react
:用于转换 JSX 语法和 React 相关的特性。@babel/preset-typescript
:用于转换 TypeScript 代码。与配置文件的关系:
presets
数组的一个元素。
配置文件:是一个包含 Babel 配置选项的 JSON(或其他格式)文件,用于指定 Babel 的行为。 Preset:是一组预定义的转换规则集合,用于简化配置过程并实现对特定环境或特性的转换。
babel.config.json
你正在使用一个monorepo(可以理解为在一个项目中会有多个子工程) 你希望编译node_modules以及symobllinked-project中的代码
.babelrc
你的配置仅适用于项目的单个部分 需要在子目录/文件中运行一些特定的转换,比如你可能不希望一些第三方库被转码
package.json
也可以直接将 .babelrc
中的配置信息作为 babel
键(key) 添加到 package.json
文件中:
{
"name": "my-package",
"babel": {
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime"]
}
推荐使用
babel.config.js
来作为整个项目的babel配置,
.babelrc
只会影响本项目中的代码babel.config.js
会影响整个项目中的代码,包含node_modules中的代码需要了解这两个配置文件更具体区别的可以看一下这篇文章 不容错过的 Babel7 知识
babel 的编译总体流程主要分成三个阶段:
1 parse(解析) 阶段: 通过@babel/parser
将代码转化为 AST
2 transform(转换)阶段: 通过 @babel/traverse
对 AST 进行操作
3 generate(生成)阶段: 通过 @babel/generator
将 AST 转化为源代码
解析:parse 阶段的目的是把源代码字符串转化成机器能够理解的 AST,这个过程分成词法分析、语法分析
转换:对 parse 阶段生成的 AST 进行遍历,针对不同的节点进行操作,在这个阶段中,我们可以通过@babel/traverse 操作 AST
生成:将 AST 转化为源代码,并生成 source-map。在这个阶段,我们可以通过 @babel/generate 将操作后的 AST 转化为源代码
一个 Babel 插件其实就是一个函数,它接收一个包含
types
工具箱的对象作为参数,并返回一个对象,该对象包含一个visitor
属性,visitor
属性是一个对象,其方法会在遍历 AST 时被调用。
这个插件将会把 ES6 的 **
运算符转换为 Math.pow
函数调用。以下是插件的代码示例:
// transform-to-mathpow.js
module.exports = function(babel) {
const { types: t } = babel;
return {
name: "transform-to-mathpow",
visitor: {
BinaryExpression(path) {
if (path.node.operator === "**") {
const mathpowAstNode = t.callExpression(
t.memberExpression(t.identifier("Math"), t.identifier("pow")),
[path.node.left, path.node.right]
);
path.replaceWith(mathpowAstNode);
}
},
},
};
};
在这个插件中,我们首先检查当前节点是否为二元表达式,并且操作符是否为 **
。如果是,我们就创建一个新的 CallExpression
节点,表示 Math.pow
函数的调用,并将原来的二元表达式的左右两边作为参数传递给 Math.pow
。最后,我们用新的 CallExpression
节点替换原来的二元表达式节点。
要使用上面创建的插件,我们需要在项目的配置文件(babel.config.json 或者 .babelrc)中添加它:
{
"plugins": ["./transform-to-mathpow.js"]
}
Babel 插件和预设的执行顺序是其在转换 JavaScript 代码过程中的一个重要方面
假设有以下 Babel 配置文件:
{
"presets": ["preset-a", "preset-b"],
"plugins": ["plugin-1", "plugin-2"]
}
在这个配置中:
preset-b
(先执行),然后是 preset-a
(后执行)。plugin-1
(先执行),然后是 plugin-2
(后执行)。preset-a
和 preset-b
内部包含的插件将分别按照它们各自的内部规则执行。其实 tsc 和 babel 在本质上是同一类东西(都是编译器),只是实现的功能不同,如:
虽然babel是一个JavaScript编译器,但是通过添加 preset 预设也可以对 ts 进行编译。(插件系统真的强大!)
项目运行环境无需过多 polyfill 的支持,项目源代码到输出代码过程仅需 ts=>js, 无对源码的个性化处理,就选
tsc
。
项目运行环境需要适配目标浏览器/Node.js 环境等,源代码到输出目标代码过程需要除 ts=>js 编译外的个性化处理,追求更快的编译速度,但无需类型检查和类型声明文件的输出,就使用
babel
编译。
既要引入 polyfill 适配目标浏览器版本,又要进行类型检查并输出类型声明文件,就使用 babel 进行编译,用 tsc 进行类型检查和声明文件输出。
这篇文章为了帮助大家更好地学习 AST 和编译器,就只引入了适用性更广 babel 和 tsc,没有引入其他在编译速度上更有优势的(如 swc、esbuild)。如果你对这篇文章哪个地方有更深的见解,欢迎在评论区留下你的观点。
本文转自 https://juejin.cn/post/7447444766005018624
如有侵权,请联系删除。
欢迎大家访问我们的刷题网站(https://fe.ecool.fun/)或者小程序 前端面试题宝典 进行刷题,1200多道全网最全的前端面试题,让你一网打尽。近期还有会员卡免费领,全场打折的活动不容错过!刷题会员周卡免费送
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。