# recast reprints only the syntax tree you actually changed

> recast is a JavaScript syntax tree transformer that hands back a shadow copy of the parse tree, with every node pointing at the original text it came from, and reuses that text on reprint. The catch is that every parser and every transform in your pipeline has to go through that one function.

**benjamn/recast** — JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator

- Repository: https://github.com/benjamn/recast
- Stars: 5,252 · Forks: 366
- Language: TypeScript
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/benjamn-recast

## The shadow copy that keeps your formatting intact

recast exists to make one identity true:

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

Parse a file and print it straight back, and the string comes out unchanged, including the comment sitting on its own line and the indentation around it. What makes that possible is that `recast.parse` does not hand you the parser's tree. It makes a shadow copy first, and every copied node gets a back-reference to the node it came from through a special `.original` property. When `recast.print` walks the tree, any node it can trace back to untouched source has that original text pasted back in. Only the parts you changed get regenerated.

Two interfaces carry the whole design: `parse` to read code, `print` to write a modified tree. The AST you get back is the one from `ast-types`, reached through `recast.types`, and `def/core.ts` in that module is the reference for the node types themselves.

## Where the generic pretty printer takes over, and what it costs

When a modified node can no longer be described by its original text, recast stops trying and falls back to a generic pretty printer for that piece. The docs set the ceiling plainly: the worst that can happen is that your change triggers some harmless reformatting of your code.

That ceiling is a design boundary rather than a bug, and it is worth planning around. You get no say in how much of a subtree gets reformatted when you touch it. In a codemod that runs across a repository, the diff starts carrying whitespace the transformation never intended, and reviewers stop reading it line by line.

If formatting is irrelevant to your output, stop paying for preservation and call the printer directly:

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

The example the docs walk through makes the difference visible. A function declaration with a line-broken return and an interleaved comment comes back through `print` with all of that intact, and through `prettyPrint` as `var add = function(b, a) { return a + b; }`. Same abstract structure, different bytes.

Esprima, the default parser, is the other side of this comparison. Parse with it and regenerate from the tree yourself, and every formatting decision in the file is gone. The copy-and-reference step is the part recast adds.

## Parsing outside recast.parse throws the formatting away

The `.original` chain is a contract, and it breaks without an error message.

The docs are explicit that if you want the conservative printing you must keep calling `recast.parse` and hand it a different parser, rather than running that parser yourself:

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

Any object exposing `parse(source)` qualifies, so a parser that needs extra options gets wrapped rather than skipped:

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

Now the failure mode worth caring about. Parse somewhere else, hand that tree to `recast.print`, and the `.original` references are absent. Nothing throws. Every node looks new, so the whole file goes through the fallback printer, and the surgical edit you asked for arrives reformatted from the first line to the last.

Babel users meet the same edge from another direction. Calling `transformFromAST` means passing `cloneInputAst: false`, because the copy Babel makes by default is exactly what strips the references recast relies on.

## Builders from ast-types, checked against the Mozilla Parser API

Constructing nodes goes through `recast.types`, which re-exports `ast-types`.

```js
// 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);
```

`namedTypes` is the assertion surface. `n.FunctionDeclaration.assert(add)` refuses to continue when the node is the wrong type, which matters in a codemod walking a tree where the shape of node three is an assumption.

`builders` is the construction surface, and every argument you pass is checked against the Mozilla Parser API signature at runtime. A wrong argument list fails at build time instead of producing a tree that prints into nonsense later. For anyone who has written transformations against Esprima or the Mozilla Parser API, this API shape is deliberately the same one they know.

Imports are named by design, which keeps the surface obvious:

```js
import { parse, print } from "recast";
```

## Source maps come out of the same tracking pass

Because the printer already tracks which character sequences came from the input file, mapping generated code back to it costs you two option names.

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

`sourceFileName` names the input, `sourceMapName` names the map, and `result.map` is a source map object built from the same bookkeeping that formatting preservation uses. Every slice, every join and every re-indentation is recorded as it happens, which is why the mapping is high resolution rather than a single statement that the file was rewritten.

The case for having it is a transform whose output reaches a debugger or a browser. Without a map, every frame points into the generated file and the trace tells you nothing about your source. With it, the stack lands on the line you wrote. The repository description lists automatic source map generation as one of the project's three jobs, and here it rides along with the reprinting rather than being bolted on afterwards.

## Two installs, and the one parser that arrives by default

From npm:

```bash
npm install recast
```

From GitHub, the documented route is a clone into your `node_modules`:

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

The package layout is small and legible. `main.ts` at the root compiles to the `main.js` entry point with `main.d.ts` beside it for types, `lib/` holds the implementation, `parsers/` the preconfigured parser entry points, `test/` the suite, and `example/` four worked examples named `add-braces`, `generic-identity`, `identity` and `to-while`. The npm scripts chain lint, build and mocha into `npm test`, and `package.json` sets `"browser": { "fs": false }` so the filesystem module stays out of browser bundles.

One install fact catches people out. Only Esprima is installed by default. Reaching for `recast/parsers/typescript`, or the `flow` or `babel` parsers, means running `npm install @babel/parser` first, and the `acorn` parser means `npm install acorn`.

## Version 0.24.0 in package.json, release tags last cut in 2022

The version numbers disagree, and that is the upgrade story.

`package.json` declares version 0.24.0. The two most recent GitHub releases are v0.21.1, published on 2022-04-27, and v0.7.0, published on 2014-08-15. The tags stopped tracking the package years before the version number did, and there is no changelog file at the root of the repository to fill that gap. Any upgrade has to be read against the dependency list yourself: `ast-types` sits at `^0.16.1`, `esprima` at `~4.0.0`, `source-map` at `~0.6.1`, `tiny-invariant` at `^1.3.3`. The Babel, Flow and esprima-fb parsers are devDependencies, so they exist for development of recast and are not installed for the people who use it.

The last push was on 2026-08-21, so the tree is still being worked on even though the release tags are not. Licensing is the simple part: MIT, with the LICENSE file at the repository root.

## Conclusion

recast earns its place when a pipeline has to change JavaScript or TypeScript in place without rewriting the file, because the `.original` chain is what keeps a codemod diff readable. It is the wrong choice when you want formatted output anyway, since the generic printer gives the same result with less bookkeeping. Before depending on it, check that every parser and transform in the pipeline calls `recast.parse` instead of parsing on its own, since one stray parse call is enough to lose every formatting reference.

## FAQ

### How do I use recast?

Call `parse` on your source, change the tree, then call `print` and take `.code` from the result: `recast.print(recast.parse(source)).code`. Unmodified parts come back byte for byte, because `parse` returns a shadow copy whose nodes point back at the original text.

### Does recast need another parser installed for TypeScript?

Only Esprima is installed by default. Using the preconfigured `typescript`, `flow` or `babel` parsers means running `npm install @babel/parser` first, and the `acorn` parser means `npm install acorn`.

### Will recast rewrite my whole file's formatting when I change one line?

Not by design. `recast.print(recast.parse(source)).code === source` is the guarantee the docs lead with, and unmodified nodes are reprinted from the original source. Nodes a modification changes fall back to a generic pretty printer, so a reformatted region is still possible, and it gets more likely if you parse the file yourself instead of calling `recast.parse`.

## Sources

- [benjamn/recast on GitHub](https://github.com/benjamn/recast)
- [Issues](https://github.com/benjamn/recast/issues)
- [License: MIT](https://github.com/benjamn/recast/blob/master/LICENSE)
- [README](https://github.com/benjamn/recast/blob/master/README.md)
- [Releases](https://github.com/benjamn/recast/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/benjamn-recast
