JSX is a syntax extension for JavaScript that allows us to write HTML-like code within our JavaScript code. In this post, we will not only see what is it but will also deep dive into how it works. To understand JSX, let's first ask some questions.
We know JS is JavaScript, so what is JSX? Is it JavaScript version 10? like JS version X? Or JavaScript Xtra? Well no, the X in JSX stands for JavaScript Syntax eXtension. It is also sometimes called JavaScript XML.
If you have been coding for a while, you would have come across the term AJAX (Asynchronous JavaScript and XML). AJAX was responsible for creating highly interactive web pages that are updated asynchronously instead of a single queue which rather consumes more time. The XMLHttpRequest object of AJAX was able to make async HTTP requests and receive responses from the server without reloading the whole page. The responses were initially in XML format but as the modern implementation evolved, the responses tended to be in JSON instead. This is why fetch has overtaken XMLHttpRequest.
JSX which you may have heard of while learning React, is a syntax extension for JavaScript that allows developers to write HTML-like code within their JavaScript code. It was originally developed by Meta for React but it has been adopted by a lot of frameworks and libraries as well. JSX is not a separate language rather it's just an extension that is transformed into native JavaScript later. But what does it look like? Is it similar to JavaScript or HTML? Why do we even need it?
JSX will help us to write the JavaScript within your HTML. To do this, you will need the curly braces { } to embed any JavaScript code you need.
This is typical HTML, you are aware of. But with JSX, you can use curly braces to introduce JS.
Moreover, all the HTML attributes are written as camelCase. So, onclick
will become onClick
, tabindex
becomes
tabIndex
, and so on. Some attributes like for
becomes htmlFor
, class
becomes className
.
Normal HTML
The same in JSX will be
One thing to note is, that all the HTML elements must be lowercase. JSX
will treat <div>
as an HTML element but <Div>
or <DIV>
will be treated as a React Component.
That's why all React components start with a Capital Letter so JSX &
React can differentiate between HTML Elements and React Components.
You will see almost all the react components are written in JSX. So you might think, that JSX is mandatory to write React, and without JSX React will not be able to parse the code. This is wrong. You can write React without JSX too but the problem is it will be hard to read and maintain. Let's say you want to render li unordered list items.
Here is what you can do in JSX:
But the same without JSX would be:
You can see, that writing code with JSX is much simpler and easy to read and maintain than writing in vanilla JS. Writing code in JSX will in the end converted into vanilla JS only.
Using JSX can have several pros and cons. Understanding them will give a ore clarity on JSX. Some of the notable benefits are:
There are some drawbacks too which needs attention:
Under the hood all the JSX code are converted into vanilla JavaScript only before it is rendered to browsers. But you may ask, how does it work? Let's deep dive!
See the following code snippet
This is a human-readable text. You can understand that two variables are initialized, one with const and the other with let. Then we are consoling (or printing) their sum. How is this interpreted by a computer and then executed? The codes are usually compiled and converted into machine code using a compiler. A compiler is a piece of software that translates source code written in a high-level programming language into a syntax tree (literally, a tree data structure like a JavaScript object) according to specific rules. The process of compiling code involves several steps, including lexical analysis, parsing, semantic analysis, optimization, and code generation. If we talk about compilers (at least for JavaScript), there are three steps that come into play.
In this step, the whole code is broken down into smaller meaningful tokens. When a token has a state about its parent or children, it is known as a lexer. Lexers have rules that detect pre-defined key tokens such as variable names, function names, object keys and values, and more. The lexer then maps these keywords to some type of enumerable value, depending on its implementation. For example, const becomes 0, let becomes 1, function becomes 2, etc.
The process of taking the tokens and converting them into the tree-like data structure that represents that whole code structure is known as parsing. The above code can be represented in the tree structure like this:
With a parser, the string is converted into a JSON object.
This is where the compiler generates machine code from the abstract syntax tree (AST). This involves translating the code in the AST into a series of instructions that can be executed directly by the computer’s processor. The process of converting an AST into machine code is complex and involves many different steps. However, modern compilers are highly sophisticated and can produce highly optimized code that runs efficiently on a wide range of hardware architectures.
There are several types of compilers that you have heard of:
To execute JavaScript code efficiently, many modern environments, including web browsers, utilize JIT compilers. These compilers are part of engines, which first translate code into an intermediate representation, such as bytecode. Then they are dynamically compiled into machine code on the go. This on-the-fly compilation allows the engine to make optimizations based on real-time information, such as variable types and frequently executed code paths. The most popular JavaScript runtime, by far, is the common web browser, such as Google Chrome: it ships the Chromium runtime that interfaces with the engine. Similarly, on the server side, we use the Node.js runtime that still uses the v8 engine.
Runtimes give JavaScript engines context, like the window object and the document object that browser runtimes ship with. If you’ve worked with both browsers and Node.js before, you may have noticed that Node.js does not have a global window object. This is because it’s a different runtime and, as such, provides a different context. Cloudflare created a similar runtime called Workers whose sole responsibility is executing JavaScript on globally distributed machines called edge servers.
But what all this is related to JSX?
Now that you have a clear understanding, of how a compiler works, how codes are converted into machine code, and how to write a JSX syntax. You may ask how JSX works? How it is extended? To extend JavaScript syntax, we’d need to either have a different engine that can understand our new syntax, or deal with our new syntax before it reaches the engine.
Former is nearly impossible. We can't create a new engine, because it will be expensive and time-consuming that works on all the devices. Even if we ignore the time-consuming factor, it will still be not a good option as who will adopt our new engines? How would we convince browser vendors and other stakeholders to switch to our unpopular engines? This wouldn’t work.
The latter is quicker. Before sending JSX to the browser, we need to convert it to native JavaScript. To do this, we need to create our lexer and parser that can understand our extended language: that is, take a text string of code and understand it. Then, instead of generating machine code as is traditional, we can take this syntax tree and instead generate plain old regular vanilla JavaScript that all current engines can understand. This is precisely what Babel in the JavaScript ecosystem does, along with other tools like TypeScript.
You now clearly know that JSX cannot be sent to browsers directly, it first needs to be transformed and converted to a JS file which then gets compiled. That is why the process is known as Transpilation.
〝Transpilation: Transform (trans) + Compile (pile)
〞
It all starts with <
which is not recognised in
JavaScript other than compariosn operator. When JavaScript encounters
this, it will of course throw an error: SyntaxError: Unexpected token '<'
. But when JSX transpiles this, it will perform a function call.
Because of JSX transpilation, the compiler will know how it should
handle some contents of a file.
Even native JavaScript has this type of prgama. You have encountered
“use strict” pragmas that we sometimes see on top of older modules, and
the recent “use client” pragma in the context of React Server Components
(RSCs). The signature of the function call that is made when JSX
encounters <
is something like this:
The function received, tag (or label), props and the children as arguments. For example the following JSX code:
will become the following JavaScript code:
One of the most powerful features of JSX is the ability to execute code inside a tree of elements. You can use dynamic content using variables, even iterate over lists, or perform expression evaluation.
This will print 3 on the screen as the code in the curly brackets is treated as expressions that are evaluated and displayed. Here is another example with a conditional check using a ternary operation:
This will render Is number even? YES
since the comparison
is an evaluated expression. However, it's not possible to execute
statements inside of a JSX element tree. This will not work:
It doesn’t work because statements do not return anything and are
considered side effects: they set the state without yielding a value.
When JSX encounters this, it will compute and evaluate, it will even go
to the if statement, but how does it know that we have to print EVEN
? Notice that in the example, we just put the string EVEN
.
How is our renderer supposed to know we intend to print EVEN
? This is why expressions are evaluated, but statements are not.
If you've enjoyed reading this blog and have learnt at least one new thing, do subscribe to recieve updates whenever I post a new article directly to your inbox and do share on Twitter with your friends.