2.什么是节点类型
在上篇提到的AST工具网站中,对于javascript的解析提供了很多个解析器。其中极其出名的就是acron,因为它已经被babel采用。我先用这个库来实现一些简单的解析、装换操作。
Acorn是一个轻量级、快速的JavaScript解析器。它是用JavaScript编写的,支持ES2022的所有语言特性,解析速度非常快,是目前最流行的JS解析器之一。同时Acorn还支持将代码解析为抽象语法树(AST)。
@babel/parser
是一个 Babel 插件,它可以将 JavaScript 代码解析为 AST(抽象语法树)表示。它是 Babel 转换流程的第一个步骤 - 在执行任何转换操作之前,需要将源代码转换为 AST 表示,以便进行进一步的分析和转换。在最新版本的babel中,已经将解析器换成了上文提到的Acorn。可以使用与之前一样的 API 来使用 Acorn 7 解析器,每一个 Babel 转换流程的第一个步骤 - 仍然是将 JavaScript 代码解析为 AST 表示。
而且,由于Acorn解析器已被广泛使用和测试,因此从长远来看,从 Babylon 切换到 Acorn 更有利于 Babel 的生态体系。
这里与《重学前端》中的《JavaScript语法篇》发生联动,下面列出的是来自于《重学前端》的语句、词法划分。
语法篇共四篇,从介绍JS的源文件类型是脚本,还是模块开始,至右值表达式结束。开篇就介绍了脚本与模块的差异,从这里开始,引出了声明
和语句
,在AST中就被表现为了*Declaration
*Statement
。
作业
在这一篇的结尾,winter老师留了一个作业,是说利用babel将一个模块中的导出变量全部找出来。
大家可以看到在文章尾部的留言区,有一些大佬已经记下了一些实现,我们这个留言中提到的,官方仓库的一些示例来解决。
通过@babel/parser解析模块文件,然后通过遍历ExportNamedDeclaration,找出所有export的变量, spec参考:https://github.com/babel/babel/blob/master/packages/babel-parser/ast/spec.md#exports
下面是两种不同解法:
实现源码地址: https://github.com/stack-wuh/mini-cli/blob/main/core/babel-export/index.js
解法一: 直接遍历解析出来的ast对象
/**
* @NOTE 用parser解析源文件
* @NOTE 用ast对象处理导出的字段名
*/
const workflow = async () => {
const source = await fsp.readFile(filePath, { encoding: 'utf-8' })
const ast = parser.parse(source, { sourceType: 'module' })
const exportNamed = {}
const { body } = ast.program
body.forEach(node => {
if (!node.declaration) return
const { declarations } = node.declaration
const head = declarations[0]
exportNamed[head.id.name] = true
})
console.log('=====> workflow.exportNamed', exportNamed)
}
解法二: 利用traverse去遍历ast节点
/**
* @NOTE 利用parser解析源文件
* @NOTE 利用traverse遍历ast语法对象
*
*/
const workflowWithTraverse = async () => {
const source = await fsp.readFile(filePath, { encoding: 'utf-8' })
if (!source) return
const ast = parser.parse(source, { sourceType: 'module', plugins: ['jsx'] })
const exportNamed = {}
traverse.default(ast, {
ExportNamedDeclaration: path => {
const { name } = path.node.declaration.declarations[0].id
console.log('===> path.node', path.node.declaration.declarations[0].id)
exportNamed[name] = true
}
})
console.log('=====> workflowWithTraverse.exportNamed', exportNamed)
}
解法一有一个需要注意的问题,一旦源文件中出现了一个没有加export前缀的变量,node.declaration
这个对象是一个null值,所以需要加上一个非空的条件判断一下。
解法二是利用的提供的traverse库,可以直接找出全部的ExportNamedDeclaration
节点数据。