关于@babel/parser,@babel/generator,@babel/traverse,@babel/types使用

这篇博客详细介绍了Babel的四个关键库:@babel/parser用于JavaScript解析,@babel/generator负责将AST转换回代码,@babel/traverse则涉及AST遍历,@babel/types与AST相关。文章讨论了各库的使用方法,包括options参数、AST概念、sourceType配置和plugins选项。还提供了错误案例及解决方法,并引导读者理解AST的结构和用途。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于@babel/parser,@babel/generator,@babel/traverse, @babel/types使用

最近需要写一个脚本对项目代码进行修改故将研究进度记录在这里。
这三个关于babel的库都从属于@babel/core,所以@babel/core就可以使用这三个库。
参考文档:babel中文文档 (这三个库在babel文档中工具分类中)。
以下全部基于@babel/core version:7.13.8

@babel/parser

Babel Parser(以前为Babylon)是Babel中使用的JavaScript解析器

  • 默认启用最新的ECMAScript版本(ES2020)
  • 支持JSX, Flow, Typescript
babelParser.parse(code, [options])

使用方式如下:

import * as babelParser from '@babel/parser';
const code = 'const a = 1;';
babelParser.parse(code);

参数解释,通过@babel/parse的声明文件来解释

export function parse(
  input: string,//需要解析代码字符串
  options?: ParserOptions //可选参数,在解析过程中一些配置项 关于配置项内容在下面会讲解
): import("@babel/types").File; //返回是@babel/types 里面的File类型这个在下面介绍@babel/types时候会讲到
//关于ParserOptions 参数声明如下
export interface ParserOptions {
  /**
   * By default, import and export declarations can only appear at a program's top level.
   * 默认情况下,导入和导出声明只能出现在程序的顶层
   * Setting this option to true allows them anywhere where a statement is allowed.
   * 将此选项设置为true允许在任何允许语句的地方使用。
   */
  allowImportExportEverywhere?: boolean;

  /**
   * By default, await use is not allowed outside of an async function.
   * 默认情况下,不允许在异步函数之外使用await。
   * Set this to true to accept such code.
   * 将此设置为true以接受这样的代码。
   */
  allowAwaitOutsideFunction?: boolean;

  /**
   * By default, a return statement at the top level raises an error.
   * 默认情况下,顶层的return语句会引发错误。
   * Set this to true to accept such code.
   * 将此设置为true以接受这样的代码。
   */
  allowReturnOutsideFunction?: boolean;
 /**
 * By default, super use is not allowed outside of class and object methods. 
 * 默认情况下,不允许在类和对象方法之外super
 * Set this to true to accept such code. 
 * 将此设置为true以接受这样的代码。
  */
  allowSuperOutsideMethod?: boolean;

  /**
   * By default, exported identifiers must refer to a declared variable.
   * 默认情况下,导出的标识符必须引用声明的变量。
   * Set this to true to allow export statements to reference undeclared variables.
   * 将此设置为true以允许导出语句引用未声明的变量。
   */
  allowUndeclaredExports?: boolean;

  /**
   * By default, Babel always throws an error when it finds some invalid code.
   * 默认情况下,Babel在发现无效代码时总是抛出错误。
   * When this option is set to true, it will store the parsing error and
   * try to continue parsing the invalid input file.
   * 当该选项设置为true时,它将存储解析错误和尝试继续解析无效的输入文件
   */
  errorRecovery?: boolean;

  /**
   * Indicate the mode the code should be parsed in.
   * 指出代码应该被解析的模式。
   * Can be one of "script", "module", or "unambiguous". Defaults to "script".
   * 可以是“script”、“module”或“unambiguous”之一。默认为“script”。
   * "unambiguous" will make @babel/parser attempt to guess, based on the presence
   * of ES6 import or export statements.
   * “unambiguous”将使@babel/parser尝试猜测,基于存在ES6导入或导出语句的
   * Files with ES6 imports and exports are considered "module" and are otherwise "script".
   * 带有ES6导入和导出的文件被认为是“module”,否则就是“script”。
   */
  sourceType?: "script" | "module" | "unambiguous";

  /**
   * Correlate output AST nodes with their source filename.
   * 将输出AST节点与其源文件名关联起来。
   * Useful when generating code and source maps from the ASTs of multiple input files.
   * 在从多个输入文件的ast生成代码和源代码映射时很有用。
   */
  sourceFilename?: string;

  /**
   * By default, the first line of code parsed is treated as line 1.
   * 默认情况下,第一行代码被解析为第1行。
   * You can provide a line number to alternatively start with.
   * 您可以提供一个行号作为开头。
   * Useful for integration with other source tools.
   * 对于与其他源工具的集成很有用。
   */
  startLine?: number;

  /**
   * Array containing the plugins that you want to enable.
   * 包含要启用的插件的数组
   */
  plugins?: ParserPlugin[];

  /**
   * Should the parser work in strict mode.
   * 解析器是否在严格模式下工作
   * Defaults to true if sourceType === 'module'. Otherwise, false.
   * 如果sourceType === 'module'默认为true。否则,假的。
   */
  strictMode?: boolean;

  /**
   * Adds a ranges property to each node: [node.start, node.end]
   * 是否向每个节点添加一个ranges属性:[node.start, node.end]
  ranges?: boolean;

  /**
   * Adds all parsed tokens to a tokens property on the File node.
   * 将所有已解析的标记添加到File节点上的tokens属性。
   */
  tokens?: boolean;

  /**
   * By default, the parser adds information about parentheses by setting
   * 默认情况下,解析器通过设置来添加关于圆括号的信息
   * `extra.parenthesized` to `true` as needed.
   * When this option is `true` the parser creates `ParenthesizedExpression`
   * 当该选项为“true”时,解析器会创建`ParenthesizedExpression`
   * AST nodes instead of using the `extra` property.
   */
  createParenthesizedExpressions?: boolean;
}
关于options

首先解释一下AST :Abstract syntax tree 的缩写翻译过来就是抽象语法树 至于抽象语法书的结构是如何的会在@babel/types,babel有提供一个在线查看网站https://astexplorer.net/

其次关于options的定义是我自己翻译如果有错误反应指正,再次强调注意版本,我是直接取的@babel/parser的声明文件,如果不太理解可以去学习一下ts的声明文件相关的知识。

options的相关都有很明确的解释我这里只额外说两项:

  • sourceType 它有三个配置项分别是:“script” | “module” | “unambiguous”,我们经常用的话一般用"script"和"module",而他们两在使用的时候怎么区分存在import/export我们就用"module"反之就是保持默认的"script",而“unambiguous”是表示我们以ES6的语法导入导出。

SyntaxError: ‘import’ and ‘export’ may appear only with ‘sourceType: “module”’
项目开发中遇到类似报错就表示我们sourceType配置错误

  • plugins 表示我们在解析的过程需要使用哪些插件,比如我们解析的是typeScript代码,那么我们就需要配置plugins: [‘typeScript’],至于支持的插件类型我继续以@babel/parse的声明文件来展示。
export type ParserPlugin =
  | "asyncGenerators"
  | "bigInt"
  | "classPrivateMethods"
  | "classPrivateProperties"
  | "classProperties"
  | "classStaticBlock"
  | "decimal"
  | "decorators"
  | "decorators-legacy"
  | "doExpressions"
  | "dynamicImport"
  | "estree"
  | "exportDefaultFrom"
  | "exportNamespaceFrom" // deprecated
  | "flow"
  | "flowComments"
  | "functionBind"
  | "functionSent"
  | "importMeta"
  | "jsx"
  | "logicalAssignment"
  | "importAssertions"
  | "moduleStringNames"
  | "nullishCoalescingOperator"
  | "numericSeparator"
  | "objectRestSpread"
  | "optionalCatchBinding"
  | "optionalChaining"
  | "partialApplication"
  | "pipelineOperator"
  | "placeholders"
  | "privateIn"
  | "throwExpressions"
  | "topLevelAwait"
  | "typescript"
  | "v8intrinsic"
  | ParserPluginWithOptions;
  export type ParserPluginWithOptions =
  | ["decorators", DecoratorsPluginOptions]
  | ["pipelineOperator", PipelineOperatorPluginOptions]
  | ["recordAndTuple", RecordAndTuplePluginOptions]
  | ["flow", FlowPluginOptions];

到这里我们,我们已经了解了@babel/parse中parse方法的使用,当然@babel/parse 里面不止parse一种方法,但是我在项目中基本只用了parse所以着重看了一下关于parse的api ,至于其他的方法,大家可以按需去研究一下。

@babel/types

其实讲这个主要是想讲一下AST即抽象语法树。
我们上面讲到parse的返回结果是一个抽象语法树。那么这个抽象语法是什么样的呢。怎么去理解这个抽象语法树。

通过上面@babel/parse 的声明文件中可以看到parse的返回值是@babel/types中File类型。
那我们来用parse解析一段代码看一下能拿到什么或者可以通过在线模拟来分析一下得到抽象语法树是什么样的。
假如我们有这样一段代码:

const a = 123;

然后我们通过parse解析得到了AST长什么样呢?

{
  "type": "File",
  "start": 0,
  "end": 15,
  "loc": {
    "start": {
      "line": 1,
      "column": 0
    },
    "end": {
      "line": 2,
      "column": 0
    }
  },
  "errors": [],
  "program": {
    "type": "Program",
    "start": 0,
    "end": 15,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 2,
        "column": 0
      }
    },
    "sourceType": "module",
    "interpreter": null,
    "body": [
      {
        "type": "VariableDeclaration",
        "start": 0,
        "end": 14,
        "loc": {
          "start": {
            "line": 1,
            "column": 0
          },
          "end": {
            "line": 1,
            "column": 14
          }
        },
        "declarations": [
          {
            "type": "VariableDeclarator",
            "start": 6,
            "end": 13,
            "loc": {
              "start": {
                "line": 1,
                "column": 6
              },
              "end": {
                "line": 1,
                "column": 13
              }
            },
            "id": {
              "type": "Identifier",
              "start": 6,
              "end": 7,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 6
                },
                "end": {
                  "line": 1,
                  "column": 7
                },
                "identifierName": "a"
              },
              "name": "a"
            },
            "init": {
              "type": "NumericLiteral",
              "start": 10,
              "end": 13,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 10
                },
                "end": {
                  "line": 1,
                  "column": 13
                }
              },
              "extra": {
                "rawValue": 123,
                "raw": "123"
              },
              "value": 123
            }
          }
        ],
        "kind": "const"
      }
    ],
    "directives": []
  },
  "comments": []
}

是不是觉得有点看不懂 那我们去掉一些影响我们的代码再来看一下

{
    "type": "File",//类型是一个文件
    "errors": [],
    "program": {
      "type": "Program",//程序
      "sourceType": "module",
      "interpreter": null,
      "body": [//代码
        {
          "type": "VariableDeclaration",//声明一个变量语句
          "declarations": [//声明一个变量语句的具体内容
            {
              "type": "VariableDeclarator",//声明语句
              "id": {
                "type": "Identifier",//一个变量
                "name": "a"//变量名字是‘a’
              },
              "init": {
                "type": "NumericLiteral",//是一个数字类型
                "value": 123//值是123
              }
            }
          ],
          "kind": "const"//使用const 声明
        }
      ],
      "directives": []
    },
    "comments": []
  }

这样分析之后我们可以通过一个AST复原一个完整的代码已经这个代码在我们文件的第几行(位置信息是我们去掉觉得影响我们分析的东西)。
对于这个可用的json我们可以看到很多地方都有type这个type类型可以在https://www.babeljs.cn/docs/babel-types这个文档里面查看各自的含义。
那么我们拿到这个AST之后该怎么使用呢, AST怎么转化为我们的code代码呢?接着往下看。

@babel/generator

@babel/generator 是将AST转化为code代码的方式,理论上它应该是最后一步,但是这里我们却先讲它,因为它比较简单就是一个转化过程。

import * as babelParser from '@babel/parser';
import generate from '@babel/generator';

const code = 'const a = 1;';
babelParser.parse(code);
const transformCode = generate(this.ast).code;
console.log(transformCode);
//得到的结果应该是 const a = 1;

从上面的示例可以看到genrate只是一个将AST转化为code的一个函数。很好理解。
稍微看一下源码中关于generate的声明

/**
 * Turns an AST into code, maintaining sourcemaps, user preferences, and valid output.
 * @param ast - the abstract syntax tree from which to generate output code.
 * @param opts - used for specifying options for code generation.
 * @param code - the original source code, used for source maps.
 * @returns - an object containing the output code and source map.
 */
export default function generate(ast: t.Node, opts?: GeneratorOptions, code?: string | { [filename: string]: string; }): GeneratorResult;

export interface GeneratorResult {
    code: string;
    map: {
        version: number;
        sources: string[];
        names: string[];
        sourceRoot?: string;
        sourcesContent?: string[];
        mappings: string;
        file: string;
    } | null;
}
export interface GeneratorOptions {
    /**
     * Optional string to add as a block comment at the start of the output file.
     */
    auxiliaryCommentBefore?: string;

    /**
     * Optional string to add as a block comment at the end of the output file.
     */
    auxiliaryCommentAfter?: string;

    /**
     * Function that takes a comment (as a string) and returns true if the comment should be included in the output.
     * By default, comments are included if `opts.comments` is `true` or if `opts.minifed` is `false` and the comment
     * contains `@preserve` or `@license`.
     */
    shouldPrintComment?(comment: string): boolean;

    /**
     * Attempt to use the same line numbers in the output code as in the source code (helps preserve stack traces).
     * Defaults to `false`.
     */
    retainLines?: boolean;

    /**
     * Should comments be included in output? Defaults to `true`.
     */
    comments?: boolean;

    /**
     * Set to true to avoid adding whitespace for formatting. Defaults to the value of `opts.minified`.
     */
    compact?: boolean | "auto";

    /**
     * Should the output be minified. Defaults to `false`.
     */
    minified?: boolean;

    /**
     * Set to true to reduce whitespace (but not as much as opts.compact). Defaults to `false`.
     */
    concise?: boolean;

    /**
     * The type of quote to use in the output. If omitted, autodetects based on `ast.tokens`.
     */
    quotes?: "single" | "double";

    /**
     * Used in warning messages
     */
    filename?: string;

    /**
     * Enable generating source maps. Defaults to `false`.
     */
    sourceMaps?: boolean;

    /**
     * The filename of the generated code that the source map will be associated with.
     */
    sourceMapTarget?: string;

    /**
     * A root for all relative URLs in the source map.
     */
    sourceRoot?: string;

    /**
     * The filename for the source code (i.e. the code in the `code` argument).
     * This will only be used if `code` is a string.
     */
    sourceFileName?: string;

    /**
     * Set to true to run jsesc with "json": true to print "\u00A9" vs. "©";
     */
    jsonCompatibleStrings?: boolean;
}
@babel/traverse

后续补充。。。。待续

### 使用 Babel 解析 AST Babel 提供了一套完整的工具链来处理 JavaScript 代码,其中包括解析、遍历和生成抽象语法树(AST)。为了使用 Babel 解析 AST,通常需要以下几个步骤: #### 安装依赖包 首先,确保安装了必要的 npm 包。可以通过命令行执行如下指令完成安装: ```bash npm install @babel/parser @babel/traverse @babel/generator ``` 这些包分别负责不同的功能:`@babel/parser` 用来将源码转化为 AST;`@babel/traverse` 则允许开发者访问并修改 AST 中的各个节点;最后,`@babel/generator` 可以把经过变换后的 AST 再次转回为字符串形式的代码。 #### 创建解析器实例 下面是一个具体的例子展示如何利用上述模块创建一个简单的程序来解析一段给定的 JavaScript 函数定义,并对其进行简单改造再输出新的版本[^1]。 ```javascript const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; // 输入待解析的 JS 代码片段 let sourceCode = ` function add(a, b){ return a + b; }`; try { // 将输入的 js 字符串编译成 ast 对象 let astTree = parser.parse(sourceCode); // 遍历整个 ast 结构并对特定类型的节点应用自定义逻辑 traverse(astTree, { Identifier(path) { if (path.node.name === 'a') { path.node.name = 'firstParam'; } if (path.node.name === 'b') { path.node.name = 'secondParam'; } }, ReturnStatement(path) { console.log('Found return statement:', path.toString()); } }); // 把修改过的 ast 转换成最终想要得到的新js代码 let outputCode = generate(astTree).code; console.log(outputCode); } catch(error) { console.error("Error during parsing or traversing:", error.message); } ``` 这段脚本展示了怎样加载原始代码作为字符串传入 `parser.parse()` 方法从而获得对应的 AST 表达式。之后借助 `traverse()` 来查找所有的标识符 (`Identifier`) 并更改参数名称。对于返回语句(`ReturnStatement`)仅打印日志信息而不做任何改动。最后调用 `generate().code` 获取更新后的代码文本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神经佳丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值