14 min readJul 1, 2026

How JSX Works Under the Hood -Compiler pipeline, ASTs, XSS security

How does React turn HTML-like syntax into JavaScript? Take a deep dive into the JSX compiler pipeline, ASTs, Babel, and built-in XSS security.

How JSX Works Under the Hood -Compiler pipeline, ASTs, XSS security

How does React turn HTML-like syntax into JavaScript? Take a deep dive into the JSX compiler pipeline, ASTs, Babel, and built-in XSS security.

JSX is a syntax extension that allows you to write HTML-like code directly inside JavaScript. However, browsers cannot natively understand JSX, so it must be translated into standard JavaScript before it can run. In this article, we will look under the hood of that build process. We will cover:

  • The transpilation: How the compiler transforms raw JSX into standard JavaScript.
  • The architecture: Why the compiler enforces strict structural rules (like TitleCase components and single parent elements).
  • The security: How this compilation process acts as a filter to automatically protect your application from Cross-Site Scripting (XSS) attacks.

What are syntax extensions?

A syntax extension is an addition to a programming language that introduces new custom syntax that is not part of the language's original rules. It acts as a "custom upgrade" to the language. The language itself doesn't understand the new syntax, so it must be converted back into standard code that the computer can actually run. The new syntax is converted to the standard syntax using a compiler or transpiler.

Compiler

A compiler is a piece of software that translates code written in a high-level programming language into another language. The target language can be at a similar or lower level of abstraction. For example, the compiler can translate TypeScript to JavaScript or TypeScript to binary.

Compilation is the process of translating code written in a high-level language into a different any-level language.

Transpiler (short for source-to-source compiler)

A transpiler is a specific type of compiler. While a general compiler can translate high-level code to any level, a transpiler strictly translates the code between languages at the same level of abstraction. For example, JSX to JavaScript or TypeScript to JavaScript - no JavaScript to binary.

Transpilation is the process of translating source code written in one high-level language into another high-level language.

So, translating JSX to JavaScript is transpilation; tools like Babel handle the conversion.

Babel

Babel is a JavaScript compiler. It acts as a transpiler for JSX, translating it into JavaScript. The transpilation process follows a step-by-step engineering pipeline to safely translate the code without breaking its meaning.

Transpiling JSX into JavaScript - journey of a syntax extension moving through a compiler pipeline to become standard JavaScript:

  1. Lexical Analysis (Tokenization)
  2. Parsing (Building the AST)
  3. Semantic Analysis (Logical Validation)
  4. Optimization (Tree Refinement)
  5. Transformation (The Translation)
  6. Code Generation (Outputting Text)

lexical analysis

When a piece of code reaches the compiler, it represents a giant string of characters. The first job of the compiler is to break this string down into a flat list of meaningful (to the language) pieces called tokens.

The process of converting a raw sequence of characters into a structured sequence of tokens is called lexical analysis. Lexical analysis is done by the part of the compiler called the lexer, short for lexical analyzer.

First, the lexer identifies lexemes—the actual, raw strings of text written in code, such as const.

Once a lexeme is identified, the lexer immediately categorizes it by assigning a label, such as Keyword. This resulting data object—the raw string paired with its label, like [Keyword: const]—is what the lexer returns. This finalized, categorized piece of data is called a token.

So, the lexer’s job is to take a string of characters and transform it into a flat list of tokens.

Building the AST via parsing

The tokens list is flat - they do not have a connection with one another. So the next step is to restore the tokens' structural relationships to understand the code's intent.

The compiler takes that flat list of tokens and organizes them into a deeply nested tree structure called an Abstract Syntax Tree (AST). The AST represents the exact grammatical relationship of the code.

In AST, JSX elements are labeled as an unusual, non-standard JavaScript feature that requires special attention.

// Simplified look at an AST for JSX:
VariableDeclaration
 └── VariableDeclarator
      ├── Identifier: element
      └── JSXElement
           ├── JSXOpeningElement: h1
           └── JSXText: "Hello"

Semantic Analysis

Semantic Analysis goes beyond syntax to verify the code's logical accuracy. It takes the raw AST from the parser and makes sure that:

  • Types are used correctly – Type Checking
  • Variables are declared before use and in the correct scope – Scope Checking
  • Functions receive their intended arguments – Function Signatures
  • Constants are not reassigned – Immutability
  • Functions return the type they are supposed to return – Return Consistency
  • Special commands are used in the correct places (e.g., using break inside a loop) – Control Flow Validation

If everything passes, the compiler decorates the AST with this new data, creating a "Validated AST." 

If not, the compiler throws an error and halts the entire build process.

Optimization

Once the compiler knows the code makes logical sense, it hands it off to the Optimizer. The optimizer's entire job is to rewrite parts of the tree to make the final program run faster or use less memory, without changing what the code actually does.

  • Math is pre-calculated ahead of time so the user's device doesn't have to do it – Constant Folding
  • Code that will never actually run is deleted to keep the file size small – Dead Code Elimination
  • Function calls are changed with the actual function code to save processing time – Function Inlining
  • Repeated calculations are solved and the answer is reused – Redundancy Reduction
  • Simple loops are unrolled into a straight list of commands to run faster – Iteration Flattening
  • Imported files or libraries that are never actually used are thrown away – Tree Shaking

Transformation (The Translation)

Remember that when building AST, the compiler labels JSX elements as non-standard JavaScript. During transformation, the compiler identifies these nodes containing JSX-specific elements and translates them into standard, universally known JavaScript structures.

To perform this translation, the compiler utilizes a plugin specifically designed to handle syntax extensions (such as @babel/plugin-transform-react-jsx).

The plugin traverses the AST, targets nodes labeled JSXElement, and replaces them with standard JavaScript nodes (such as CallExpression).

It replaces each JSXElement node (and its associated props and children) with a CallExpression node. For instance, the parser identifies <h1>Hello</h1> as a JSXElement and physically rewrites it within the tree into a function call for React.createElement('h1', null, 'Hello').

React.createElement() returns a JavaScript object. React uses that object to render output to the browser.

Code Generation

Finally, the compiler takes the newly transformed, standard JavaScript AST and turns it back into a readable string of code. To ensure compatibility with browsers, the code is output according to the strict ECMAScript specification.

When you look at the Sources tab in your browser's developer tools, you are looking at the result of that final compilation - the source code.

example output: const element = React.createElement("h1", null, "Hello");

All this tokenization, parsing, and swapping happen on the developer's environment or a build server before the website is ever launched.

Runtime

When those functions execute on the user’s device, they dynamically build and connect their objects, resulting in one giant nested tree of objects that represents the entire user interface in memory: the Virtual Dom (vDOM).

How compilation applies to practice - Key differences with HTML syntax:

Understanding JSX Security: How It Prevents XSS Attacks

JSX is highly secure compared to traditional HTML because it protects applications from Cross-Site Scripting (XSS) attacks using two core mechanisms: compilation and escaping.

What is an XSS Attack?

A Cross-Site Scripting (XSS) attack occurs when a malicious user inputs a JavaScript snippet into a form or input field. If the website displays this typed information using raw HTML, the script becomes embedded in the webpage. When another user visits the page and views this content, the malicious code executes automatically in their browser, potentially stealing their cookies, redirecting them to a dangerous site, or causing other harm.

How JSX Prevents XSS (Cross-Site Scripting)

XSS attacks occur when an application accidentally renders user-supplied input as active HTML or JavaScript, allowing a malicious script to run in the browser. A naive templating engine might simply inject a string directly into the DOM, making it easy for an attacker to execute malicious code (e.g., <img src=x onerror=alert(1)>). JSX prevents this by ensuring that the browser treats user-provided data only as text, never as raw HTML instructions.

The Compilation & Runtime Process: This defense is a two-part architectural strategy:

  1. Parsing Phase (Structural Separation): When the compiler parses JSX, it strictly separates the structure(the tags) from the data (the expressions inside {}). It builds an AST that treats these as two distinct branches. Because the structure is already "locked in" during compilation, user input cannot "break out" of its data branch to modify the structural tree.
  2. Runtime Phase (Automatic Escaping): React receives the output of this compilation. Before it ever touches the browser's DOM, it performs automatic encoding. If you pass a string variable containing malicious HTML into a JSX expression {userInput}, React treats that variable as a literal string. It converts dangerous characters like < and >into safe HTML entities (such as &lt; and &gt;), ensuring the browser renders the tags as harmless text rather than executing them as DOM elements.

Handling Rich Text Safely: dangerouslySetInnerHTML

If you want to allow users to format their text using rich text elements (such as <b> or <i>), you can use the specialized attribute dangerouslySetInnerHTML. However, using this attribute completely bypasses React's built-in safety nets.

Whenever you use dangerouslySetInnerHTML, you must pair it with a dedicated sanitization library like DOMPurify.

Sanitization is the process of scrubbing a raw string of HTML to strip out dangerous tags (like <script>) and malicious attributes (like onerror), while safely preserving harmless formatting tags.

Because React does not automatically sanitize data when dangerouslySetInnerHTML is used, utilizing a third-party library is required to keep your application secure.

Summary

JSX is a syntax extension for JavaScript that allows for HTML-like structure within code. Because browsers do not natively understand JSX, it must be processed through a compiler pipeline (like Babel) before execution.

1. The Compiler Pipeline

The conversion of JSX into standard JavaScript follows a rigorous engineering process:

  • Lexical Analysis: A lexer breaks raw code into a flat list of categorized tokens.
  • Parsing: The parser organizes tokens into an Abstract Syntax Tree (AST), establishing grammatical relationships. It identifies JSX nodes as non-standard, labeling them for special handling.
  • Semantic Analysis & Optimization: The compiler verifies the logic (types, scope, constants) and rewrites the tree to improve performance (e.g., dead code elimination).
  • Transformation: A plugin (e.g., @babel/plugin-transform-react-jsx) traverses the AST, finds JSXElement nodes, and replaces them with CallExpression nodes—specifically React.createElement().
  • Code Generation: The final, standard JavaScript is output for the browser.

2. Structural Rules of JSX

To maintain the integrity of the AST and ensure compatibility with JavaScript's function-based nature, the compiler enforces strict rules:

  • TitleCase vs. lowercase: Used by the parser to distinguish between Custom Components (Identifiers) and native HTML tags (String Literals).
  • className & camelCase: Required because class is a reserved JavaScript keyword, and DOM properties must match the native JavaScript API (e.g., onClick vs onclick).
  • Context Switching ({}): The curly brace acts as a portal, switching the parser from "HTML Mode" to "JavaScript Expression Mode," allowing for arbitrary expressions (math, ternary operators, etc.) to be passed as function arguments.
  • Structural Integrity: Every tag must be explicitly closed (or self-closed), and all elements must be wrapped in a single parent or Fragment to ensure the compiler can generate one root object for the return statement.

3. Security and XSS Prevention

JSX provides security by design through a two-part defense against Cross-Site Scripting (XSS):

  • Compilation (Structural Separation): The parser separates the structure (tags) from the data (user input), preventing input from breaking out of its data branch.
  • Runtime (Automatic Escaping): React automatically encodes dangerous characters (e.g., < becomes &lt;), ensuring user input is rendered as literal text rather than executable HTML/scripts.
  • Exceptions: Using dangerouslySetInnerHTML bypasses these protections; it requires manual sanitization (e.g., using DOMPurify) to remove malicious code.

4. Trade-offs

  • Benefits: Highly readable, modular component-based architecture, and proactive security (automatic escaping).
  • Drawbacks: Requires a compilation step (toolchain overhead), mixes concerns (logic and presentation), and offers partial JS compatibility (e.g., limited support for inline statements like if/switch within expressions).