The JSX Series (Part 2): What Happens Under the Hood When Babel Transpiles JSX?
Understanding how Babel transpiles HTML-like syntax into React.createElement calls and plain JavaScript UI objects.
The JSX Series (Part 2): What Happens Under the Hood When Babel Transpiles JSX?
JSX Is Not Magic
In Part 1, we looked at how JSX rescued us from the nightmare of string soup and verbose DOM methods. But how does browser-executable code actually come out of HTML-like syntax inside a JavaScript file?
Browsers don't natively understand JSX. If you feed raw JSX to Chrome or Firefox, you'll get a syntax error. Before your code hits the browser, a build tool (like Babel or SWC) transpiles your JSX into plain, standard JavaScript functions.
Step 1: Transpilation to Function Calls
When you write a JSX element, the compiler translates it into a call to React.createElement() (or the newer _jsx() runtime function).
The Input (What You Write in JSX):
TypeScript
const todoList = ["Learn React", "Learn TypeScript", "Build a React App"];
export const TodoList = () => {
const name = "Edward Phillips";
return (
<>
<h1>{`${name}'s`} Todo List</h1>
<ul style={{ listStyleType: "none", padding: 0 }}>
{todoList.map((item, index) => (
<TodoItem key={index} item={item} />
))}
</ul>
</>
);
};
const TodoItem = ({ item }: { item: string }) => {
return <li style={{ textDecoration: "none" }}>{item}</li>;
};
The Output (What the Compiler Generates):
JavaScript
import React from "react";
const todoList = ["Learn React", "Learn TypeScript", "Build a React App"];
export const TodoList = () => {
const name = "Edward Phillips";
return React.createElement(
React.Fragment,
null,
// <h1> Element with dynamic text children
React.createElement("h1", null, `${name}'s`, " Todo List"),
// <ul> Element containing mapped TodoItem components
React.createElement(
"ul",
{ style: { listStyleType: "none", padding: 0 } },
todoList.map((item, index) =>
React.createElement(TodoItem, { key: index, item: item })
)
)
);
};
const TodoItem = ({ item }) => {
return React.createElement("li", { style: { textDecoration: "none" } }, item);
};
Understanding React.createElement()
To see what’s happening, look at the signature of React.createElement:
React.createElement(type, props, ...children)
Type: The element to create. This can be a string representing an HTML tag (like
"h1"or"ul") or a reference to a custom component function (likeTodoItem).Props: an object containing attributes passed to the element (like
{ style: { padding: 0 } }or{ key: 0, item: "Learn React" }).Props: Anything nested inside the element—text strings, other React elements, or an array of mapped elements.
Why Component Naming Matters: Capitalization Rules
Have you ever wondered why React component names must start with a capital letter (<TodoItem /> instead of <todoItem />)?
It comes down to how the compiler handles the type argument:
Lowercase Tags (
<div />or<li />): The compiler treats these as standard HTML tags and passes them as string literals (React.createElement("div")).Capitalized Tags (
<TodoItem />): The compiler treats these as custom React components and passes them as direct variable references (React.createElement(TodoItem)).
The Pitfall: If you name your component
todoItemin lowercase, the compiler generatesReact.createElement("todoItem"). The browser will search for a native HTML element called<todoItem>, won't find one, and your app will fail to render as expected.
Step 2: From Function Calls to JavaScript Objects
Compiling JSX into React.createElement() is only half the journey. What happens when those functions actually execute in the browser?
They return plain, light-weight JavaScript objects that describe the UI structure.
For example, a simple expression like:
JavaScript
const element = <div className="greeting">Hello World</div>;
Transpiles to:
JavaScript
const element = React.createElement("div", { className: "greeting" }, "Hello World");
When executed, it outputs an object structure in memory that looks like this:
JavaScript
{
$$typeof: Symbol.for('react.element'),
type: 'div',
props: {
className: 'greeting',
children: 'Hello World'
},
key: null,
ref: null
}
Key Takeaway
JSX is just a cleaner, visual shortcut. It allows us to write familiar HTML-like code to build nested trees of JavaScript objects, so we don't have to manually type out dozens of repetitive React.createElement() function calls.
Now that we know JSX turns into a tree of plain JavaScript objects in memory, a crucial question arises: What does React actually do with this object tree?
That brings us to Part 3, where we'll explore how these JSX objects power the Virtual DOM and allow React to perform lighting-fast, targeted UI updates.