recast vs acorn-walk vs esprima vs estraverse vs estree-walker
JavaScript AST (Abstract Syntax Tree) Manipulation
recastacorn-walkesprimaestraverseestree-walkerSimilar Packages:

JavaScript AST (Abstract Syntax Tree) Manipulation

JavaScript AST (Abstract Syntax Tree) Manipulation libraries provide tools for analyzing, transforming, and generating JavaScript code by working with its syntactic structure. These libraries parse JavaScript code into a tree-like representation (the AST), allowing developers to traverse, modify, or generate code programmatically. They are useful for tasks like code analysis, linting, transpiling, and building tools like formatters and minifiers. Each library has its own strengths, such as performance, ease of use, and support for specific AST specifications, making them suitable for different use cases in the JavaScript ecosystem.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
recast19,528,7955,229246 kB210a year agoMIT
acorn-walk011,34253.8 kB15a month agoMIT
esprima07,133-1498 years agoBSD-2-Clause
estraverse0967-394 years agoBSD-2-Clause
estree-walker042017.6 kB103 years agoMIT

Feature Comparison: recast vs acorn-walk vs esprima vs estraverse vs estree-walker

AST Traversal

  • recast:

    recast integrates AST traversal with code transformation and pretty-printing. It allows you to traverse the AST while also modifying it and generating new code from the transformed tree. This dual functionality makes it a powerful tool for refactoring and transforming JavaScript code while maintaining its original structure.

  • acorn-walk:

    acorn-walk provides a simple and efficient way to traverse ASTs generated by Acorn. It supports both pre-order and post-order traversal, allowing you to visit nodes in a flexible manner. The API is straightforward, making it easy to integrate into projects that require basic tree walking.

  • esprima:

    esprima focuses on parsing JavaScript code and generating a detailed AST. While it does not provide built-in traversal methods, its AST structure is well-defined and can be easily traversed using external libraries like estraverse or acorn-walk. This separation of concerns allows for more specialized traversal implementations.

  • estraverse:

    estraverse is designed specifically for traversing ESTree-compliant ASTs. It offers a comprehensive API for both simple and complex traversals, including the ability to skip nodes, replace them, or perform actions at specific points in the traversal. Its flexibility makes it suitable for a wide range of traversal tasks.

  • estree-walker:

    estree-walker provides a lightweight and efficient way to traverse ESTree-compliant ASTs. It uses a simple walker pattern that allows for easy customization of traversal behavior. The library is minimalistic, which makes it fast and easy to use for projects that require quick and straightforward tree walking.

Code Transformation

  • recast:

    recast excels at code transformation while preserving the original formatting. It allows you to modify the AST and generate new code from it, making it ideal for refactoring tasks. Recast handles the complexities of maintaining code style and structure during transformations, which is a significant advantage for projects that require high-quality output.

  • acorn-walk:

    acorn-walk is primarily a traversal library and does not provide built-in support for code transformation. However, it allows you to visit and manipulate nodes during traversal, enabling custom transformations to be implemented as needed. For more complex transformations, you may need to combine it with other libraries that handle AST manipulation.

  • esprima:

    esprima focuses on parsing and generating ASTs rather than transforming them. It provides a detailed representation of the code structure, which can be used by other libraries for transformation tasks. Esprima’s role is to provide accurate and compliant ASTs that can be manipulated by external tools and frameworks.

  • estraverse:

    estraverse is a traversal library that facilitates code transformation by allowing you to visit and modify nodes in the AST. While it does not perform transformations itself, it provides the necessary hooks and structure to implement custom transformations, making it a valuable tool for developers working on AST manipulation and code refactoring projects.

  • estree-walker:

    estree-walker is designed for traversal and does not include built-in transformation capabilities. However, it allows for easy manipulation of nodes during traversal, enabling developers to implement custom transformations as they walk the tree. Its simplicity and efficiency make it a good choice for projects that require lightweight transformation logic.

Performance

  • recast:

    recast is more resource-intensive than simple traversal libraries due to its capabilities for parsing, transforming, and pretty-printing code. While it provides powerful transformation features, developers should be aware of the potential performance implications when working with large codebases or performing complex transformations.

  • acorn-walk:

    acorn-walk is lightweight and optimized for performance, especially when traversing ASTs generated by the Acorn parser. Its minimalistic design ensures that traversal operations are fast and efficient, making it suitable for applications where performance is critical.

  • esprima:

    esprima is known for its fast parsing capabilities, but the performance of AST traversal and manipulation depends on how the generated AST is used. Esprima is optimized for quick parsing, making it a good choice for applications that need to analyze code quickly. However, for large-scale AST manipulation, performance will also depend on the algorithms and techniques used by external libraries.

  • estraverse:

    estraverse is designed to be efficient for traversing ESTree-compliant ASTs, but its performance can vary based on the complexity of the traversal and the number of nodes being processed. It is generally fast for most use cases, but developers should be mindful of potential performance bottlenecks when implementing complex traversal logic.

  • estree-walker:

    estree-walker is designed for performance, with a focus on minimal overhead during traversal. Its lightweight nature makes it one of the fastest options for walking ESTree-compliant ASTs, making it ideal for applications that require quick and efficient tree traversal without unnecessary complexity.

Ease of Use: Code Examples

  • recast:

    recast Example

    import recast from 'recast';
    
    const code = `const x = 1;`;
    const ast = recast.parse(code);
    
    recast.types.visit(ast, {
      visitVariableDeclaration(path) {
        console.log('Found a variable declaration:', path.node);
        this.traverse(path);
      },
    });
    
    const transformedCode = recast.print(ast).code;
    console.log(transformedCode);
    
  • acorn-walk:

    acorn-walk Example

    import { parse } from 'acorn';
    import { simple } from 'acorn-walk';
    
    const code = `const x = 1;`;
    const ast = parse(code);
    
    simple(ast, {
      VariableDeclaration(node) {
        console.log('Found a variable declaration:', node);
      },
    });
    
  • esprima:

    esprima Example

    import esprima from 'esprima';
    
    const code = `const x = 1;`;
    const ast = esprima.parseScript(code);
    console.log(JSON.stringify(ast, null, 2));
    
  • estraverse:

    estraverse Example

    import { parse } from 'acorn';
    import estraverse from 'estraverse';
    
    const code = `const x = 1;`;
    const ast = parse(code);
    
    estraverse.traverse(ast, {
      enter(node) {
        console.log('Visiting node:', node.type);
      },
    });
    
  • estree-walker:

    estree-walker Example

    import { parse } from 'acorn';
    import { walk } from 'estree-walker';
    
    const code = `const x = 1;`;
    const ast = parse(code);
    
    walk(ast, {
      enter(node) {
        console.log('Entering node:', node.type);
      },
    });
    

How to Choose: recast vs acorn-walk vs esprima vs estraverse vs estree-walker

  • recast:

    Choose recast if you need a powerful tool for parsing, transforming, and pretty-printing JavaScript code while preserving the original formatting. It is ideal for complex code transformations and refactoring tasks, as it integrates well with existing code and supports custom transformations.

  • acorn-walk:

    Choose acorn-walk if you need a lightweight and fast library for traversing ASTs generated by Acorn. It is ideal for simple traversal and manipulation tasks without the overhead of a full-featured framework.

  • esprima:

    Choose esprima if you need a standards-compliant, fast JavaScript parser that generates a detailed AST. It is suitable for projects that require accurate parsing and analysis of JavaScript code, including support for ES6 and beyond.

  • estraverse:

    Choose estraverse if you need a flexible and extensible library for traversing and manipulating ESTree-compliant ASTs. It provides a simple API for walking the tree and allows for easy customization of traversal behavior.

  • estree-walker:

    Choose estree-walker if you want a minimalistic and efficient walker for ESTree-compliant ASTs. It is designed for performance and simplicity, making it a great choice for projects that require fast and lightweight tree traversal.

README for recast

recast, v. CI Join the chat at https://gitter.im/benjamn/recast

  1. to give (a metal object) a different form by melting it down and reshaping it.
  2. to form, fashion, or arrange again.
  3. to remodel or reconstruct (a literary work, document, sentence, etc.).
  4. to supply (a theater or opera work) with a new cast.

Installation

From npm:

npm install recast

From GitHub:

cd path/to/node_modules
git clone git://github.com/benjamn/recast.git
cd recast
npm install .

Import style

Recast is designed to be imported using named imports:

import { parse, print } from "recast";
console.log(print(parse(source)).code);

import * as recast from "recast";
console.log(recast.print(recast.parse(source)).code);

If you're using CommonJS:

const { parse, print } = require("recast");
console.log(print(parse(source)).code);

const recast = require("recast");
console.log(recast.print(recast.parse(source)).code);

Usage

Recast exposes two essential interfaces, one for parsing JavaScript code (require("recast").parse) and the other for reprinting modified syntax trees (require("recast").print).

Here's a simple but non-trivial example of how you might use .parse and .print:

import * as recast from "recast";

// Let's turn this function declaration into a variable declaration.
const code = [
  "function add(a, b) {",
  "  return a +",
  "    // Weird formatting, huh?",
  "    b;",
  "}"
].join("\n");

// Parse the code using an interface similar to require("esprima").parse.
const ast = recast.parse(code);

Now do whatever you want to ast. Really, anything at all!

See ast-types (especially the def/core.ts) module for a thorough overview of the ast API.

// Grab a reference to the function declaration we just parsed.
const add = ast.program.body[0];

// Make sure it's a FunctionDeclaration (optional).
const n = recast.types.namedTypes;
n.FunctionDeclaration.assert(add);

// If you choose to use recast.builders to construct new AST nodes, all builder
// arguments will be dynamically type-checked against the Mozilla Parser API.
const b = recast.types.builders;

// This kind of manipulation should seem familiar if you've used Esprima or the
// Mozilla Parser API before.
ast.program.body[0] = b.variableDeclaration("var", [
  b.variableDeclarator(add.id, b.functionExpression(
    null, // Anonymize the function expression.
    add.params,
    add.body
  ))
]);

// Just for fun, because addition is commutative:
add.params.push(add.params.shift());

When you finish manipulating the AST, let recast.print work its magic:

const output = recast.print(ast).code;

The output string now looks exactly like this, weird formatting and all:

var add = function(b, a) {
  return a +
    // Weird formatting, huh?
    b;
}

The magic of Recast is that it reprints only those parts of the syntax tree that you modify. In other words, the following identity is guaranteed:

recast.print(recast.parse(source)).code === source

Whenever Recast cannot reprint a modified node using the original source code, it falls back to using a generic pretty printer. So the worst that can happen is that your changes trigger some harmless reformatting of your code.

If you really don't care about preserving the original formatting, you can access the pretty printer directly:

var output = recast.prettyPrint(ast, { tabWidth: 2 }).code;

And here's the exact output:

var add = function(b, a) {
  return a + b;
}

Note that the weird formatting was discarded, yet the behavior and abstract structure of the code remain the same.

Using a different parser

By default, Recast uses the Esprima JavaScript parser when you call recast.parse(code). While Esprima supports almost all modern ECMAScript syntax, you may want to use a different parser to enable TypeScript or Flow syntax, or just because you want to match other compilation tools you might be using.

In order to get any benefits from Recast's conservative pretty-printing, it is very important that you continue to call recast.parse (rather than parsing the AST yourself using a different parser), and simply instruct recast.parse to use a different parser:

const acornAst = recast.parse(source, {
  parser: require("acorn")
});

Why is this so important? When you call recast.parse, it makes a shadow copy of the AST before returning it to you, giving every copied AST node a reference back to the original through a special .original property. This information is what enables recast.print to detect where the AST has been modified, so that it can preserve formatting for parts of the AST that were not modified.

Any parser object that supports a parser.parse(source) method will work here; however, if your parser requires additional options, you can always implement your own parse method that invokes your parser with custom options:

const acornAst = recast.parse(source, {
  parser: {
    parse(source) {
      return require("acorn").parse(source, {
        // additional options
      });
    }
  }
});

To take some of the guesswork out of configuring common parsers, Recast provides several preconfigured parsers, so you can parse TypeScript (for example) without worrying about the configuration details:

const tsAst = recast.parse(source, {
  parser: require("recast/parsers/typescript")
});

Note: Some of these parsers import npm packages that Recast does not directly depend upon, so please be aware you may have to run npm install @babel/parser to use the typescript, flow, or babel parsers, or npm install acorn to use the acorn parser. Only Esprima is installed by default when Recast is installed.

After calling recast.parse, if you're going to transform the AST, make sure that the .original property is preserved. With Babel, for instance, if you call transformFromAST, you must pass cloneInputAst: false in its options. (More detail).

Source maps

One of the coolest consequences of tracking and reusing original source code during reprinting is that it's pretty easy to generate a high-resolution mapping between the original code and the generated code—completely automatically!

With every slice, join, and re-indent-ation, the reprinting process maintains exact knowledge of which character sequences are original, and where in the original source they came from.

All you have to think about is how to manipulate the syntax tree, and Recast will give you a source map in exchange for specifying the names of your source file(s) and the desired name of the map:

var result = recast.print(transform(recast.parse(source, {
  sourceFileName: "source.js"
})), {
  sourceMapName: "source.min.js"
});

console.log(result.code); // Resulting string of code.
console.log(result.map); // JSON source map.

var SourceMapConsumer = require("source-map").SourceMapConsumer;
var smc = new SourceMapConsumer(result.map);
console.log(smc.originalPositionFor({
  line: 3,
  column: 15
})); // { source: 'source.js',
     //   line: 2,
     //   column: 10,
     //   name: null }

Note that you are free to mix and match syntax trees parsed from different source files, and the resulting source map will automatically keep track of the separate file origins for you.

Note also that the source maps generated by Recast are character-by-character maps, so meaningful identifier names are not recorded at this time. This approach leads to higher-resolution debugging in modern browsers, at the expense of somewhat larger map sizes. Striking the perfect balance here is an area for future exploration, but such improvements will not require any breaking changes to the interface demonstrated above.

Options

All Recast API functions take second parameter with configuration options, documented in options.ts

Motivation

The more code you have, the harder it becomes to make big, sweeping changes quickly and confidently. Even if you trust yourself not to make too many mistakes, and no matter how proficient you are with your text editor, changing tens of thousands of lines of code takes precious, non-refundable time.

Is there a better way? Not always! When a task requires you to alter the semantics of many different pieces of code in subtly different ways, your brain inevitably becomes the bottleneck, and there is little hope of completely automating the process. Your best bet is to plan carefully, buckle down, and get it right the first time. Love it or loathe it, that's the way programming goes sometimes.

What I hope to eliminate are the brain-wasting tasks, the tasks that are bottlenecked by keystrokes, the tasks that can be expressed as operations on the syntactic structure of your code. Specifically, my goal is to make it possible for you to run your code through a parser, manipulate the abstract syntax tree directly, subject only to the constraints of your imagination, and then automatically translate those modifications back into source code, without upsetting the formatting of unmodified code.

And here's the best part: when you're done running a Recast script, if you're not completely satisfied with the results, blow them away with git reset --hard, tweak the script, and just run it again. Change your mind as many times as you like. Instead of typing yourself into a nasty case of RSI, gaze upon your new wells of free time and ask yourself: what next?