Step 1 of 5 · Reading · ~3 min
Read
Reactive Foundations
JSX to createElement Compilation
JSX is not a runtime feature. It is syntax that a build tool removes before the browser sees the code. After this lesson your program does that job for real: it reads JSX source, possibly several lines of nested elements, and prints the factory calls it compiles to.
The mapping
<div className="card" id={nextId}>Hello</div>
becomes
h("div", {className: "card", id: nextId}, "Hello")
The element name is the first argument, the attributes become one props object, and every child is a further argument. h is just the function name the compiler is configured to emit (the pragma: React.createElement, h in Preact). React 17 added an automatic mode that emits calls into a runtime module, so files no longer need to import React, but the shape is the same. Nothing parses JSX at runtime; the transform is purely lexical.
The rules
| Source | Output |
|---|---|
<div> (lowercase) | the string "div" |
<Button> (capital) | the bare identifier Button, a reference to your component |
<>...</> | the identifier Fragment, a wrapper with no element of its own |
| no attributes | null as the props argument, not {} |
a="x" or a='x' | a: "x" |
a={expr} | a: expr, the expression copied as written |
bare a | a: true |
data-id="7" | "data-id": "7", quoted because it is not an identifier |
<br /> | h("br", null), no children |
{expr} as a child | expr, copied as written |
nested <b>..</b> | a nested call |
An expression is copied between its balanced braces without being understood, so a stray > inside {() => go()} must not end the tag, and style={{color: "red"}} works because the outer braces are the JSX container and the inner pair is an object literal. A real compiler parses the expression as JavaScript and transforms JSX inside it too; yours may treat the text as opaque and may assume no brace appears inside a string literal.
The whitespace rule
Text between tags is not copied verbatim. Pretty-printing JSX over several lines must not add stray spaces, but Hello <b>x</b> must keep the space. The rule, as Babel implements it:
- Split the text into lines.
- Every line except the first loses its leading spaces; every line except the last loses its trailing spaces.
- Lines that are now empty are dropped.
- The remaining lines are joined with a single space.
A text on one line is untouched, so Hello keeps its trailing space and a lone space between two elements survives as the child " ". Indented source:
<p>
Hello
world
</p>
gives the single child "Hello world", and the blank-looking runs between the <li> lines of a list give no child at all. Text is emitted as a string literal, so " and \ inside it are escaped.
How a parser reads it
One function handles one element, entered at <. It reads the name, then attributes until > or />, then children until </, and each child is one of three things: another element (call the same function), an expression container (read to the matching brace), or text (read to the next < or {). Nesting is handled by the call stack, which is the stack of open elements.
Common mistakes
- Printing
{}for no attributes. The convention isnull. - Quoting a component name (
"Button"), which makes the compiler look up an HTML tag. - Trimming single-line text, losing the space in
Hello <b>x</b>. - Ending an expression at the first
>, which() =>contains. - Forgetting to escape
"in text or in an attribute string.
Your exercise
The starter reads the source and provides quote and prop_key. You write clean_text, which applies the whitespace rule to one run of text, and transform, the recursive reader that produces the call. Expressions are opaque and never contain <.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…