本文主要内容如下:
- ast是什么
- ast如何编译的
- ts
- ts-morph
- babel
AST是什么
AST(Abstract Syntax Tree,抽象语法树)是一种数据结构,用于表示源代码的抽象语法结构。AST通过将源代码转换为树状结构,使得代码的结构化表示更加清晰,便于进一步的分析和操作。这种树形结构的数据模型包含了代码的各个部分,如函数调用、变量声明、算术运算等,每个部分都对应树中的一个节点。AST的生成过程通常包括两个阶段:分词(Tokenization)和语法分析(Parsing)
- 分词:将整个代码字符串分割成最小的语法单元数组,即tokens。
- 语法分析:在分词的基础上,建立分析语法单元之间的关系,生成AST。
AST在JavaScript开发中的应用非常广泛,包括但不限于:
- 代码转译:如Babel通过AST转译ES6+代码到ES5代码。
- 代码压缩:通过AST重构代码,移除未使用的变量和函数,减少代码大小。
- 代码格式化:利用AST对代码进行格式化调整,使其符合规范。
- 代码检查:如ESLint通过AST检查代码是否遵循规定的编码规则。
- 代码高亮和自动补全:编辑器通过AST提供智能提示和代码高亮功能。
ast如何编译的
ts编译:
要将TypeScript编译成AST(抽象语法树),你需要使用TypeScript的编译器API。下面是一个简单的步骤和示例代码,展示了如何使用TypeScript编译器API来加载TypeScript源文件并获取其AST。 示例:
import * as ts from "typescript";
// 定义一个函数来打印AST节点
function printTree(node: ts.Node) {
console.log(ts.SyntaxKind[node.kind]); // 打印节点类型
ts.forEachChild(node, printTree); // 递归遍历子节点
}
// 创建一个源文件对象
const code = "console.log('Hello, World');";
const sourceFile = ts.createSourceFile("example.ts", code, ts.ScriptTarget.ES2015, true);
// 调用printTree函数来打印AST
printTree(sourceFile);
这个示例首先导入了TypeScript的编译器API,然后定义了一个printTree函数,该函数递归地遍历AST并打印每个节点的类型。接着,使用ts.createSourceFile方法创建了一个源文件对象,该对象代表了输入的TypeScript代码。最后,调用printTree函数来打印出AST的结构。
注意事项:
ts.createSourceFile方法的参数包括源文件名称、源代码、目标脚本目标版本以及是否包含类型注释。- 你可以根据需要修改
printTree函数,来实现更复杂的AST操作,比如搜索特定类型的节点或修改节点的属性。
特点:
- 直接访问:直接使用 TypeScript 编译器 API 时,您可以直接访问 TypeScript 编译器的全部功能,包括对编译过程的详细控制以及处理 TypeScript 语言功能各个方面的能力。
- 详细程度:与 TypeScript 编译器 API 交互需要更深入地了解 TypeScript 语言及其 AST 结构。
- 灵活性:虽然这种直接方法提供了更大的灵活性和控制力,但它也意味着要处理较低级别的细节,并且在实现某些功能时可能会更加复杂。
ts-morph
ts虽然更加灵活,但考虑到ast转换的易用性和可行性,推荐使用ts-morph解析ts
示例
import { Project, SyntaxKind } from 'ts-morph'
// 实例化一个 Project
const project = new Project({ jsx: true, tsConfigFilePath: 'tsconfig.json' })
// 添加特定的源文件到 project
//当你调用这个方法时,ts-morph 会扫描提供的文件路径或 glob 模式,并将匹配的文件作为源文件(SourceFile 类的实例)添加到 Project 中。这些源文件随后可以进行分析和修改。
const paths = [
{ path: 'packages/router.tsx', prefix: 'y_app' },
]
const loop = (element, prefix) => {
// 查找名为path的属性赋值
const pathPropertyAssignment = element.getProperties().find((prop) => prop.getName() === 'path')
const children = element.getProperties().find((prop) => prop.getName() === 'children')
const isNotCode = !element.getProperties().find((prop) => prop.getName() === 'code')
// const propertyAssignment = element?.getProperty('code')
//propertyAssignment?.remove()
// 如果找到了path属性赋值,就新增一个code属性赋值,值为path的值
if (pathPropertyAssignment && isNotCode) {
const initStr = pathPropertyAssignment.getInitializer().getText()
element.addPropertyAssignment({
name: 'code',
initializer: `'${prefix}_${initStr.replace(/^'\//, '').replace(/\/|-/g, '_')}`,
})
}
if (children) {
children
.getInitializer()
.getElements()
.forEach((el) => loop(el, prefix))
}
}
paths.forEach((item) => {
const { path, prefix } = item
const sourceFile = project.getSourceFile(path)
const defaultRouterListDeclaration = sourceFile?.getVariableDeclarations().find((declaration) => declaration.getName() === 'DefaultRouterList')
// 获取初始化器(也就是数组)
const arrayInitializer = defaultRouterListDeclaration?.getInitializer()
// 确保初始化器是数组
if (arrayInitializer && arrayInitializer.getKind() === SyntaxKind.ArrayLiteralExpression) {
// 遍历数组的每一项
arrayInitializer.getElements().forEach((element) => {
// 查找名为path的属性赋值
loop(element, prefix)
})
}
})
project.saveSync()
示例解析:
- 实现了自动给router.tsx每一个节点路由配置文件按照路由地址生成对应的路由code
- 每一个路由code都有一个统一的code前缀
y_app
ts解析网站
特点
- 抽象层:ts-morph 充当 TypeScript 编译器 API 上的高级抽象层。它通过提供更直观、更简洁的界面简化了导航和操作 AST 的过程。
- 易于使用:使用 ts-morph,与直接使用 TypeScript 编译器 API 相比,您可以以更直接的方式执行常见任务,例如添加或修改文件、类、函数和属性。
- 同步和异步操作:ts-morph 支持同步和异步操作,使其适用于各种用例,包括涉及性能考虑很重要的大型代码库的用例
babel
Babel 是一个编译器,它允许开发者使用最新版本的 JavaScript 语法,同时确保代码可以在旧版浏览器中运行。通过获取新特性转换为 ES5 语法,Babel 使得现代 JavaScript(如 ES6、ES7、ES8 等)与旧版浏览器兼容。它不仅支持最新的 JavaScript 语法,还提供了语法转换、填充(polyfill)、代码转换等功能。此外,Babel 还支持实验性的语言建议,并且可以通过插件系统扩展其功能。
Babel的流程主要包括三个步骤:解析、转换和生成。
解析阶段
- 解析器:首先,Babel会使用解析器将源代码解析成一个抽象语法树(AST)。 解析器称为解析器或词法分析。 在这个阶段,源代码被分割成一系列的令牌(标记),每个令牌代表代码一个元素,如关键字、标识符、操作符等。
- AST:解析后的结果是AST,类似于树状结构表示源代码语义信息数据结构。AST展现代码中不同部分的概念,而不考虑具体的语法细节。
转换阶段
- 在AST上应用一系列的转换规则,需要从一种JavaScript语法到另一种语法的转换。这一步骤通常涉及多个转换器(transformer),每个转换器负责处理特定的语法转换任务。
- 转换器可以修改AST,使其符合目标环境的要求,例如将箭头函数转化为普通函数,或将模块中的语句转化为整数。
生成
- 代码生成器:最后,Babel将修改后的AST转换回源代码,Chone调用代码生成。生成的代码应该与原始输入相似,但已经应用了所有必要的转换,以适应目标环境。
示例
下面是一个简单的示例,演示如何使用Babel将快捷函数转换为普通函数:
import { parse } from "@babel/parser";
import generate from "@babel/generator";
const code = "const double = a => a * 2;";
const ast = parse(code);
const output = generate(ast, {}, code);
console.log(output.code); // 输出转换后的代码
为了解决这个问题,我们需要一个parse函数来解析 AST,generate函数将返回 AST 的源代码。通过上述示例,Babel 能够使用最新 JavaScript 特性代码来修改浏览器的页面显示代码,从而保证浏览器的正常运行。
开发一个babel插件
babel插件主要通过visiter(访问器)实现,提供一个可以访问的函数,和@babel/traverse解析的第二个参数相同
示例:
export default function({ types: t }) {
return {
visitor: {
AssignmentExpression(path) {
// 修改节点
const node = path.node;
if (node.operator === '=') {
// 做一些修改
node.value = t.stringLiteral('newValue');
}
}
}
};
};
解析对比网站
需要知道visiter(访问器)应该访问哪个元素,可以通过下面网站进行对比解析