Understanding how JSX is transformed into JavaScript is crucial for any React developer aiming to write efficient and maintainable code. Yet, misconceptions abound, leading to confusion about what happens under the hood.
As a developer mentor, I was recently approached by some junior team members who were puzzled after reading a blog post that claimed:
"When Babel encounters a name starting with a capital letter, it knows it’s dealing with a React component and converts it into a React Fiber object."
Their confusion worried me because this statement contains several inaccuracies about how Babel and React work together. Misunderstandings like these can hinder a developer's growth and lead to misconceptions about fundamental concepts.
In this post, I aim to clarify how JSX is actually transformed under the hood by Babel and explain why the claim about creating React Fiber objects during transformation is misleading. More importantly, I want to highlight the importance of critically evaluating the information we come across, especially in rapidly evolving fields like web development. By understanding the true mechanics behind JSX transformation, we can write better code and guide others more effectively.
Before diving into the theoretical aspects of JSX transformation, let's put the assumption to the test. The blog post in question claimed:
"When Babel encounters a name starting with a capital letter, it knows it's dealing with a React component and converts it into a React Fiber object."
To verify this, we'll experiment with different JSX elements using the Babel REPL (Read-Eval-Print Loop), an online tool that allows us to input JSX code and see how Babel transforms it.
Testing Babel’s JSX Rules in Action
Step 1: Prepare the Test Cases
We'll use a variety of JSX elements to cover different scenarios:
const foo = { bar: () => null };
const Baz = () => null;
const tests = {
builtIn: <div>Hello</div>,
notHtmlElement: <Div>Hello</Div>,
memberExpr: <foo.bar>World</foo.bar>,
capitalRef: <Baz>Test</Baz>,
nested: <foo.bar.baz>Nested</foo.bar.baz>,
};
Explanation of Test Cases:
builtIn: <div>Hello</div> — A standard HTML element.
notHtmlElement: <Div>Hello</Div> — An element that looks like an HTML element but is capitalised.
memberExpr: <foo.bar>World</foo.bar> — A component accessed via a member expression starting with a lowercase letter.
capitalRef: <Baz>Test</Baz> — A component starting with a capital letter.
nested: <foo.bar.baz>Nested</foo.bar.baz> — A nested member expression.
To see how Babel transforms JSX in action, you can try the code directly in the Babel REPL or view a preconfigured example here.
If you’d like to try it yourself, copy the test cases below and paste them into the Babel REPL. This setup will transform the JSX code into JavaScript, allowing you to observe the results first-hand:
const tests = {
builtIn: <div>Hello</div>,
notHtmlElement: <Div>Hello</Div>,
memberExpr: <foo.bar>World</foo.bar>,
capitalRef: <Baz>Test</Baz>,
nested: <foo.bar.baz>Nested</foo.bar.baz>,
};
When you run the code, Babel will produce output like this:
const tests = {
builtIn: _jsx("div", { children: "Hello" }),
notHtmlElement: _jsx(Div, { children: "Hello" }),
memberExpr: _jsx(foo.bar, { children: "World" }),
capitalRef: _jsx(Baz, { children: "Test" }),
nested: _jsx(foo.bar.baz, { children: "Nested" }),
};
Note: The _jsx function is a helper used by Babel when the "automatic" JSX runtime is enabled. It serves a similar purpose to React.createElement.
Let's dissect each transformed element:
builtIn (<div>Hello</div>):
notHtmlElement (<Div>Hello</Div>):
memberExpr (<foo.bar>World</foo.bar>):
capitalRef (<Baz>Test</Baz>):
nested (<foo.bar.baz>Nested</foo.bar.baz>):
Capitalisation and Syntax Determine Treatment:
For simple identifiers (like Div or Baz), Babel relies on the capitalisation of the tag name to decide whether it's a built-in element or a custom component.
For member expressions (like foo.bar or foo.bar.baz), Babel treats them as component references regardless of the capitalisation of the initial identifier.
- The structure of the expression takes precedence over the capitalisation of individual parts.
Therefore, capitalisation is not the sole factor; the syntax of the tag (whether it's an identifier or a member expression) also influences how Babel transforms it.
No Internal HTML Tag List:
Babel does not check the tag name against a list of valid HTML elements.
The differentiation is based on syntactic rules rather than knowledge of HTML specifications.
This is evident in the notHtmlElement example, where <Div> is treated as a component simply because it's capitalised.
No React Fiber Objects Are Created:
The transformation results in calls to helper functions like _jsx, which generate JavaScript objects representing elements.
There is no evidence of React Fiber objects being created during this process.
React Fiber is part of React's runtime internals, handling rendering and reconciliation, and is not involved in Babel's compile-time transformations.
This analysis further refutes the blog's claim:
How Babel Really Handles JSX: A Source Code Dive
To drive the point home, let's look at the actual Babel source code responsible for transforming JSX. By doing so, we can see precisely how Babel differentiates between built-in HTML elements and custom React components, and confirm that it does not create React Fiber objects during this process.
Locating the Relevant Code
The JSX transformation is handled by Babel's @babel/plugin-transform-react-jsx plugin. Specifically, the transformation logic is found in the file transform-automatic.js.
The isCompatTag Function
In the transformation process, Babel uses the isCompatTag function to determine whether a JSX tag should be treated as a built-in element (like an HTML tag) or as a component reference. Here's the relevant part of the code:
const args = state.args;
if (t.react.isCompatTag(tagName)) {
args.push(t.stringLiteral(tagName));
} else {
args.push(state.tagExpr);
}
The isCompatTag function is defined as follows:
export default function isCompatTag(tagName?: string): boolean {
return !!tagName && /^[a-z]/.test(tagName);
}
Understanding isCompatTag
Let's break down what this function does:
Logic:
Return Value:
true: If tagName starts with a lowercase letter, indicating a built-in element.
false: If tagName does not start with a lowercase letter, indicating a custom component or a member expression.
Implications of isCompatTag
Built-in Elements:
Tags like <div>, <span>, <button>, which start with lowercase letters.
Babel transforms them into React.createElement('div', ...), using the tag name as a string.
Custom Components:
Tags like <MyComponent>, <App>, which start with uppercase letters.
Babel transforms them into React.createElement(MyComponent, ...), using the component reference.
Member Expressions:
Tags like <foo.bar>, <this.props.component>.
Since they are not simple identifiers, isCompatTag does not apply, and Babel treats them as component references regardless of the capitalisation of individual parts.
Demonstrating with Code Examples
Let's revisit the test cases from our earlier experiment to see how Babel uses isCompatTag:
const foo = { bar: () => null };
const Baz = () => null;
const tests = {
builtIn: <div>Hello</div>,
notHtmlElement: <Div>Hello</Div>,
memberExpr: <foo.bar>World</foo.bar>,
capitalRef: <Baz>Test</Baz>,
nested: <foo.bar.baz>Nested</foo.bar.baz>,
};
Transformed Output:
const tests = {
builtIn: _jsx("div", { children: "Hello" }),
notHtmlElement: _jsx(Div, { children: "Hello" }),
memberExpr: _jsx(foo.bar, { children: "World" }),
capitalRef: _jsx(Baz, { children: "Test" }),
nested: _jsx(foo.bar.baz, { children: "Nested" }),
};
Let's analyse each case in the context of isCompatTag:
builtIn:
Tag: <div>
Tag Name: 'div'
isCompatTag('div') returns true.
Transformation: _jsx("div", { ... }), with the tag name as a string.
Explanation: Since the tag name starts with a lowercase letter, Babel treats it as a built-in element.
notHtmlElement:
Tag: <Div>
Tag Name: 'Div'
isCompatTag('Div') returns false.
Transformation: _jsx(Div, { ... }), with the component reference.
Explanation: Although "Div" resembles an HTML element, the capitalisation leads Babel to treat it as a custom component.
memberExpr:
Tag: <foo.bar>
Tag Name: null (since it's not a simple identifier)
isCompatTag is not applicable.
Transformation: _jsx(foo.bar, { ... }), with the member expression.
Explanation: Member expressions are treated as component references, and isCompatTag is not used.
capitalRef:
Tag: <Baz>
Tag Name: 'Baz'
isCompatTag('Baz') returns false.
Transformation: _jsx(Baz, { ... }), with the component reference.
Explanation: Starts with an uppercase letter, so Babel treats it as a custom component.
nested:
Tag: <foo.bar.baz>
Tag Name: null (it's a nested member expression)
isCompatTag is not applicable.
Transformation: _jsx(foo.bar.baz, { ... }), with the nested member expression.
Explanation: Babel handles nested member expressions as component references.
By examining the isCompatTag function and the transformed output, we can conclude:
Babel Uses Simple Syntax Rules:
No Creation of React Fiber Objects:
The transformation results in calls to helper functions like _jsx, which generate JavaScript objects or function calls.
React Fiber is a runtime implementation detail within React and is not involved in Babel's compile-time transformation.
Member Expressions Are Treated as Component References:
Babel treats member expressions as component references, regardless of the capitalisation of the individual identifiers within them.
The isCompatTag function does not apply to member expressions since they are not simple tag names.
Linking Back to the Misconception
The claim that "Babel converts capitalised names into React Fiber objects" is clearly refuted by the source code analysis:
Babel's Role:
Syntax Transformation: Babel's responsibility is to convert JSX syntax into standard JavaScript code, following syntactic rules.
No Runtime Behaviour: Babel operates at compile-time and does not create or manipulate runtime objects like React Fiber nodes.
React Fiber:
Runtime Concern: React Fiber is part of React's internal rendering mechanism, dealing with reconciliation and rendering.
Not Involved in Compilation: Babel has no awareness of React's runtime implementations and does not generate Fiber objects during transformation.
Key Takeaways
Capitalisation Matters for Simple Identifiers:
For tags that are simple identifiers, Babel uses capitalisation to decide whether it's a built-in element or a custom component.
Lowercase starts indicate built-in elements, while uppercase starts indicate custom components.
Member Expressions Are Handled Differently:
Member expressions and other complex tag expressions are treated as component references, bypassing the isCompatTag check.
This allows for the use of components accessed via objects or props, regardless of capitalisation.
Babel's Transformation Is Syntax-Based:
By examining Babel's source code and understanding the isCompatTag function, we've confirmed that Babel's transformation logic is based on simple syntactic rules, primarily the capitalisation of simple tag names. This analysis dispels the misconception that Babel creates React Fiber objects during JSX transformation.
Now that we've delved into both practical examples and the underlying source code, we have a comprehensive understanding of how Babel transforms JSX and how it distinguishes between built-in elements and custom components. This clarity reinforces the importance of syntax in JSX and the distinct roles that Babel and React play in the development process.
Addressing Misconceptions and Concluding Insights
Through experiments and code analysis, we've clarified the key misconceptions presented in the original blog post. Understanding these nuances helps illuminate the distinct roles of Babel and React in JSX transformation and rendering processes.
Misconception 1: Babel Creates React Fiber Objects
Claim: "When Babel encounters a name starting with a capital letter, it knows it's dealing with a React component and converts it into a React Fiber object."
Clarification:
Babel's Role: Babel is a compile-time tool that transforms JSX into JavaScript syntax using function calls (e.g., _jsx, React.createElement). It doesn’t execute code or manage runtime objects like React Fiber.
React Fiber: Fiber is part of React’s internal runtime system for efficient reconciliation and rendering, created only when React processes the component tree at runtime—not during Babel’s compile-time transformations.
Conclusion: This clear separation of compile-time and runtime responsibilities shows that Babel merely transforms code without affecting runtime data structures, such as React Fiber objects, which are managed internally by React during rendering.
Claim: "When Babel encounters a name starting with a capital letter, it knows it's dealing with a React component..."
Clarification:
Syntax-Based Transformation: Babel follows capitalisation conventions for simple tags, treating lowercase tags as HTML elements and uppercase tags as components. For member expressions (e.g., foo.bar), Babel handles them as component references, regardless of capitalisation.
Example:Transformation:Explanation: Babel recognizes foo.bar as a component reference, bypassing capitalisation checks for member expressions.
const foo = { bar: () => <div>Bar</div> };
const element = <foo.bar />;
const element = _jsx(foo.bar, {});
Conclusion: The capitalisation rule applies only to simple identifiers, not member expressions. This distinction prevents misinterpretation of Babel’s JSX transformation behaviour.
Final Thoughts and Best Practices
By clarifying these misconceptions, we’ve highlighted the following key distinctions:
Babel’s Responsibility:
Syntax Transformation: Converts JSX into JavaScript function calls based on syntactic rules.
No Runtime Data Structures: Babel does not create runtime objects like React Fiber nodes; it transforms syntax only.
React’s Responsibility:
- Runtime Rendering: Manages component lifecycle, state, and rendering using the Fiber architecture, instantiated at runtime.
Compile-Time vs. Runtime:
Compile-Time (Babel): Focused on transforming syntax with no side effects.
Runtime (React): Handles component reconciliation and rendering tasks, creating and managing Fiber nodes internally.
Syntactic Conventions in JSX:
Capitalisation Rules: Capitalisation helps differentiate between HTML elements and custom components but isn’t an enforced rule for member expressions.
Member Expressions: Treated as component references regardless of capitalisation, enhancing component organisation and flexibility.
In our exploration of JSX transformation, we clarified that Babel’s role is purely syntactic and does not involve creating React Fiber objects. This distinction not only dispels common misconceptions but also underscores the importance of carefully verifying technical claims.
In a field where tools and best practices evolve rapidly, and where AI-generated content becomes increasingly common, the ability to verify and validate becomes as crucial as technical knowledge itself.
Here’s a practical approach to navigating and understanding technical claims:
Check the Source: Dive into implementations, like we did with Babel’s isCompatTag, consult official documentation, and explore commit histories.
Test Assumptions: Use tools like Babel REPL, create test cases, and validate claims hands-on.
Question the Narrative: Consider whether the claim accurately reflects how the technology functions in practice and aligns with its intended purpose, rather than assuming explanations are complete or universally applicable.
Final Thoughts
With these practices, the next time you encounter a technical explanation, you’ll be equipped to:
Test it yourself,
Check the source code,
Question assumptions,
Share your findings.
In the end, true understanding doesn’t come from accepting explanations; it comes from verifying them. This approach strengthens your skills and helps you confidently guide others in a complex, ever-evolving field.
About the Author
If you’d like to explore more of my approach and connect with me, here's a bit about who I am:
I'm Daniel Philip Johnson, a senior frontend engineer specializing in frontend development and architecture. I’m passionate about simplifying complex challenges and building scalable, innovative solutions. To explore more of my work, visit my personal website, or connect with me on LinkedIn for insights on tech leadership and the future of frontend development.