It's just text
Here's the line we'll follow: a let and an arrow function, two things very old browsers don't know. To Babel, at first, this is only a string of
characters.
A scroll story · 2026
Babel turns new JavaScript into old JavaScript. It looks smart, but it doesn't understand code. It builds a tree, edits the tree and prints it back as text, following rules that people wrote, step by step. Scroll down and watch it happen to one line of code.
Input
Output
Text
Here's the line we'll follow: a let and an arrow function, two things very old browsers don't know. To Babel, at first, this is only a string of
characters.
Step 1 · Parse
First the parser cuts the text into tokens, the words of the language: let, greet, =, (, name,
), => and so on. No meaning yet, just pieces.
Then it groups the tokens into a tree, the AST. Each piece of the program becomes a node, and a node keeps its parts in named fields: a declaration has an
id and an init, a + has a left and a right.
Hover or tap a node to see its piece of the code. Babel has its own parser; this page uses acorn. Same idea, same kind of tree.
Nothing clever lives in the tree. This is the VariableDeclaration node, as it is in memory. The word let is just a string in a field
called kind.
Step 2 · Traverse
The walker visits every node. It enters, goes down into the children, then leaves. It doesn't know what any node means. It carries a rulebook with two rules and checks whether a rule wants the node it's standing on.
let → vararrow → functionThe rule wants VariableDeclaration nodes. It has one step: if kind is "let", write "var".
VariableDeclaration(node, { step }) {
if (node.kind !== "let") return;
step("Change kind from let to var", () => {
node.kind = "var";
});
}
This rule wants ArrowFunctionExpression nodes, in two steps. One: change the node's type. Two: a function needs { } and
return, so wrap the old body in two new nodes. Then the walker walks into the new nodes too.
No rule wants the other nodes, so the walker only enters and leaves them, all the way back up to the top. Babel calls these two moments “enter” and “exit”.
Step 3 · Generate
The printer walks the edited tree once more. For each node type it has a few lines that write text: a VariableDeclaration writes its
kind, then its parts, then ;. It doesn't care that the tree was edited.
Parse, traverse, generate. Real Babel knows many more node types and has hundreds of rules (it calls them plugins), but each works like these two: someone wrote down the steps for one piece of new JavaScript.
Bonus level
A rule does exactly what its steps say, nothing more. Rule 2 has two steps and no idea what this means. Inside an arrow function, this is
the this of the function around it. Inside a function, it isn't. So this code breaks:
read() returns 1
read() returns undefined. Broken.
read() returns 1 again
Babel's rule isn't smarter. It has more steps, written by people who hit this bug before:
this inside the arrowvar _this = this; to the function around it, and use _this insteadFunctionExpression{ return … }
The last of the three outputs is exactly what Babel prints for this code. And it's still not everything: arguments, super and
new.target behave like this, and each needs steps of its own.
It's not an LLM
Babel doesn't read your code and decide what you meant, and it doesn't try something and check if it worked. Same input, same rules, same output, every time. Code that no rule wants passes through unchanged. Code the printer has no lines for stops mine with an error:
Check it yourself: Babel's arrow function steps · Babel plugin handbook · AST Explorer
Inventory
Everything on this page ran in your browser just now, from these files: a few hundred lines of TypeScript, without comments and blank lines. Parsing is acorn's work.
Continue?
The same steps, printed in your terminal. You need Node.js 22.18 or newer.
Get the code
git clone https://github.com/slimcandy/no-magic-babel
cd no-magic-babel
npm install
Watch the three steps
npm start
Run the tests: the output must parse as old JavaScript and do the same thing as the input
npm test