Sneaker Dev Logo
Back to Blog

Babel Traverse - Part 2 - @babel/traverse's traverseFast

February 12, 2023

Nero

enter: (node: t.Node, opts?: any) => void,
Babel Traverse - Part 2 - @babel/traverse's traverseFastThis article may have been republished from another source and might not have been originally written for this site.

⚠️ Some information, tools, or techniques discussed may have changed or evolved since the publishing of this article.

Originally published at https://nerodesu017.github.io/posts/2023-02-13-babel-traverse-part-2

Babel Traverse - Part 2 - @babel/traverse's traverseFast

Nero - Feb 12 2023

Last time we left off at how traversal actually works for babel, today we'll do just that. We'll analyze the traverseFast

function.

Let's check its implementation

1export default function traverseFast(
2  node: t.Node | null | undefined,
3  enter: (node: t.Node, opts?: any) => void,
4  opts?: any,
5): void {
6  if (!node) return;
7
8  const keys = VISITOR_KEYS[node.type];
9  if (!keys) return;
10
11  opts = opts || {};
12  enter(node, opts);
13
14  for (const key of keys) {
15    const subNode = node[key];
16
17    if (Array.isArray(subNode)) {
18      for (const node of subNode) {
19        traverseFast(node, enter, opts);
20      }
21    } else {
22      traverseFast(subNode, enter, opts);
23    }
24  }
25}
26

As we can see, the traverseFast

function takes 3 arguments:

Node - which is the main node to be traversed and modified

Enter - a function we pass that shows how the nodes are going to be modified

Opts - an object containing anything we'd pass it, and that gets passed down recursively. For example, we could keep states here.

Enter function

this is what we are most interested in, this function shows how the nodes are going to be modified

this function, in turn, gets passed 2 arguments:

node

, which is the current node we are atopts

, which is our opts that we passed (or an empty object if we didn't pass any)

Going back to the traverseFast

function, let's quickly summarize what is happening:

if the passed node isn't there, we'll just return (e.g. the function was called with no arguments)

get the subnodes' keys that need to be checked (

VISITOR_KEYS[node.type]

) - in case the node.type

is not valid, return - modify the current node using the passed enter

function - traverse subNodes recursively and go to step 1.

Remarks

There's no way of removing safely a said node, only by detaching it while we are still at its parent; (we could still replace its type to the noop type)

There's no way of going to the parent node from a said node, we can only go one way, and that is down the tree

There's only one time transformations are applied: when we enter the node. This is mostly the case, but sometimes you'd want to do transformations when exiting the node. Let's see where:

1"abc" + "def" + "ghi" + "jkl"

This code, when parsed, gives us the next tree view:

1ExpressionStatement {
2  expression: BinaryExpression {
3    left: BinaryExpression {
4      left: BinaryExpression {
5        left: StringLiteral {
6          value: "abc"
7        }
8        operator: "+"
9        right: StringLiteral {
10          value: "def"
11        }
12      }
13      operator: "+"
14      right: StringLiteral {
15        value: "ghi"
16      }
17    }
18    operator: "+"
19    right: StringLiteral {
20      value: "jkl"
21    }
22  }
23}
24

As we can see, we got nested BinaryExpressions that, in the end, mean concatenation of strings Doing transformations when we enter a BinaryExpression node would look something like:

1// visitor
2
3traverseFast(node, node => {
4  if(node.type !== "BinaryExpression")return;
5  const left = node.left;
6  const right = node.right;
7  if(left.type !== "StringLiteral" || right.type !== "StringLiteral")return;
8  node.type = "StringLiteral"
9  node.value = left.value + right.value;
10  delete node.left;
11  delete node.right;
12  delete node.operator;
13})
14
15// result
16
17"abcdef" + "ghi" + "jkl"
18

Of course, we could rewrite or enter-transform visitor to make it work, but the easiest way is to do the transformation before exiting the node. Example:

1// visitor
2// I'll put just the before-exit visitor when using the normal `traverse` function here
3
4BinaryExpression: {
5  exit(path) {
6    const {node} = path;
7    const left = node.left;
8    const right = node.right;
9    if(left.type !== "StringLiteral" || right.type !== "StringLiteral")return;
10    path.replaceWith(t.StringLiteral(left.value + right.value));
11  }
12}
13
14// result
15
16"abcdefghijkl"
17

Nero

Blog: https://nerodesu017.github.io

GitHub: https://github.com/nerodesu017