It's been over 10 years since React was first released in 2013. Ten years ago, Jord Walke had a crazy idea: "Whenever any state changes, re-render the entire page." This idea led to the creation of React, and since then, the wheels of fate began to turn. Perhaps at that time, Walke never imagined that React would become one of Facebook’s most successful open-source projects. Not only that, but it now powers thousands of web applications across the globe and has become the most popular frontend framework in the world for data-driven views.
The image above is from Jord Walke's first presentation of React at JSConf.
The image above shows download statistics of mainstream frameworks from npmtrends.
However, anyone familiar with React’s syntax knows that React doesn’t use the actual DOM to describe the UI. Instead, it uses a syntax called JSX to describe it, which tightly integrates with logic, making it convenient to build complex, interactive, and frequently changing UIs.
This article will explain, through storytelling, how JSX gradually transforms into JS. By the end, you’ll have answers to the following questions:
Why was JSX created?
How does React syntax transform from source code to an abstract syntax tree (AST)?
What role does Babel play?
Other related knowledge...
This article is relatively straightforward. If you’re already familiar with these questions, feel free to explore other articles in this series.
1.JSX to JS
Let’s start from the Web 1.0 era, where the primary role of websites was to present content with minimal interactivity. People would simply browse information on web pages. Therefore, browsers were designed to separate the UI from the logic: HTML described the UI, and JavaScript handled simple logic and events. This setup worked well for the time, allowing websites to be easily maintained, with each component functioning harmoniously.
However, with the arrival of Web 2.0, the web began to handle increasingly complex scenarios, such as various portals, e-commerce, instant messaging, and web games. Users could now do a lot more on the web, making human-computer interactions more complex and diverse. Developers began to realize that UI changes and logic should be more coupled. For the next generation of development frameworks, they desired a declarative, component-based approach.
In response, the React team started exploring this and invented JSX. It uses a syntax almost identical to HTML, allowing frontend developers to learn and use it seamlessly. Moreover, it allows developers to create and modify the UI within logic as if they were creating an object. This approach matches the natural thinking process of developers when writing complex applications.
JSX Code Example:
const App = <div>I am JSX</div>;
Summary
In summary, JSX was created to meet the demands of modern web development for efficient, intuitive, and maintainable UI descriptions, especially in the context of component-based frameworks like React. By combining HTML-like syntax with JavaScript, JSX simplifies writing and managing interface logic, promotes component reuse and modularity, and ensures cross-browser compatibility and performance optimization through the compilation process. These advantages make JSX a powerful tool for building complex single-page applications (SPAs), rich internet applications (RIAs), and other types of frontend projects.
Babel
However, browsers’ JavaScript engines (we’ll use V8 as an example) cannot recognize JSX syntax. V8 only understands ECMAScript-compliant syntax, which is where Babel comes in.
On its official website, Babel is described as a toolchain primarily used to convert ECMAScript 2015+ syntax into backward-compatible JavaScript so it can run in both current and older browsers or other environments. The main functions of Babel include:
Syntax transformation
Adding missing features to the target environment via polyfills (by introducing third-party polyfill modules like core-js)
Source code transformation (codemods)
As we can see, Babel’s most important role is syntax transformation. Clever React engineers took advantage of this to allow users to write JSX in their IDEs for an optimal development experience, then use Babel to transform it into syntax that V8 can recognize. Essentially, this is a compilation process.
Quick Side Note:
In the early days of programming, developers found assembly language difficult to write, so they invented higher-level languages like C and C++, followed by compilers like GCC. What Babel does is very similar to what GCC does.
How Babel Works
Babel transforms syntax in the following steps:
Parsing:
Lexical Analysis: Babel uses a lexer to break the source code string into a series of meaningful symbols, called tokens. These tokens include identifiers, keywords, operators, strings, numbers, and comments. For example,
const x = 5;
would be broken into tokens likeconst
,x
,=
,5
, and;
.Syntax Analysis: Babel’s parser organizes these tokens into an Abstract Syntax Tree (AST) according to syntax rules. An AST is a tree-like data structure that represents the logical structure of the source code in a structured way. Each node in the AST represents a syntax element in the source code, such as variable declarations, function calls, or conditional statements.
Transformation:
Plugin Application: Babel’s core does not include specific transformation rules. Instead, it relies on a plugin system to support different features. When Babel encounters a syntax feature that needs to be transformed (like JSX, arrow functions, or decorators), the corresponding plugin is activated. These plugins usually define a set of visitor functions, which traverse the AST and perform the appropriate transformations based on node types. For JSX, the plugin @babel/plugin-transform-react-jsx converts JSX elements into
React.createElement()
calls and transforms attributes and embedded expressions into function arguments.Transformation Logic: During this phase, plugins may replace new or experimental syntax structures with equivalent traditional or widely supported syntax. For example, arrow functions might be transformed into regular function declarations.
Generation:
AST Traversal and Code Generation: In the final stage, Babel uses a code generator to traverse the transformed AST and generate a JavaScript code string that adheres to the target syntax. The generator follows syntax rules to ensure that the generated code preserves the original semantics and can be parsed and executed correctly by the target environment.
Output: After these steps, Babel outputs the transformed JavaScript code. This code no longer contains new or experimental syntax and can be understood and executed by widely supported JavaScript engines.
Is Babel Naturally Capable of Compiling JSX?
The answer is no. To enable Babel to parse JSX, specific plugins must be provided. Only when Babel has the corresponding recognition and transformation rules can it consider JSX a valid syntax during parsing. The plugin that provides JSX parsing rules is mainly @babel/plugin-transform-react-jsx.
Thus, JSX is essentially just syntactic sugar. If you wanted, you could define your own custom syntax with specific functionality, but don’t forget to provide Babel with the appropriate plugins.
2. Practical Application
Next, let’s demonstrate how to parse JSX using Babel in both Node and web environments.
Node.js
Prepare a Project
// Only two dependencies are needed npm i @babel/core @babel/plugin-transform-react-jsx -S
JS Code Explanation
// code.js function App(){ return ( <div onClick={ ()=> null}> <h1>Hello World</h1> <p>This is a paragraph</p> </div> ) } // index.js const fs = require("fs"); const babel = require("@babel/core"); fs.readFile("./code.js", (e, data) => { const code = data.toString(); // This step includes parsing, transforming, and generating // All completed in memory const result = babel.transform(code, { plugins:["@babel/plugin-transform-react-jsx"] }); fs.writeFile("./jsx.js", result.code, function(){}) });
Run the program and check the result:
function App() { return /*#__PURE__*/React.createElement("div", { onClick: () => null }, /*#__PURE__*/React.createElement("h1", null, "Hello World"), /*#__PURE__*/React.createElement("p", null, "This is a paragraph")); }
You’ll see that everything has been converted into syntax that the browser can recognize. As long as the execution environment provides the React.createElement
method, it will work.
Web Environment
In the web environment, it’s much simpler. One HTML file will suffice:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>babel in html</title> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> const React = { createElement: (type , props , ...children) => { return { type, props, children }; } }; function App(){ return ( <div onClick={ ()=> null}> <h1>Hello World</h1> <p>This is a paragraph</p> </div> ); } console.log(App()); </script> </body> </html>
Check the result:
Summary
With the above, we’ve learned how to transform JSX into JavaScript. Next, we’ll explore how React implements the React.createElement
method.
3. ReactElement
React only needs to define a function to implement ReactElement
, which can handle three parameters: type
, props
, and children
. Let’s look at React’s implementation:
function createElement(type, config, children) { var propName; var props = {}; // ReactElement's properties var key = null; // Unique key for ReactElement var ref = null; // Reference for ReactElement var self = null; // Special property var source = null; // Special property if (config != null) { if (hasValidRef(config)) { ref = config.ref; ... } if (hasValidKey(config)) { { checkKeyStringCoercion(config.key); } key = "" + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { // Non-special properties are stored in props props[propName] = config[propName]; } } } // If there is only one child, no need to store it in an array var childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } { if (Object.freeze) { Object.freeze(childArray); } } props.children = childArray; } ... return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props ); } // ReactElement implementation var ReactElement = function (type, key, ref, self, source, owner, props) { var element = { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: ref, props: props, _owner: owner, }; ... if (Object.freeze) { Object.freeze(element.props); // Freeze props Object.freeze(element); // Freeze element } return element; };
After filtering out non-essential code, the overall logic is quite simple. The core is to extract special attributes such as key
, ref
, __store
, __self
, etc., from the config parsed by Babel.
In the end, the result from JSX to JS is simply a JavaScript object that describes the UI.
4. Conclusion
In this article, we have learned the origin of JSX, understood the process of converting JSX to JS, and finally, I want to say that JSX is truly a great invention. It redefined the frontend development paradigm, allowing UI and logic to be written together. Though this was achieved via compilation, it greatly improves the user development experience. Many other frameworks have also borrowed ideas from JSX to make improvements.