Skip to content

2.什么是节点类型

image-20230402145122886


在上篇提到的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

image-20230402162624138

image-20230402162654862

作业

在这一篇的结尾,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对象

javascript
/**
 * @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节点

javascript
/**
 * @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节点数据。

MIT License.