We saw on the previous part how to make it much easier to create content from JavaScript, however we are still left with a rather tedious way to express our DOM nodes. Let's see how we can make this a bit better.
JSX, or as it should be called, XML-in-JS
The current h
function has lot lot going for it. It creates a tree of DOM elements on the fly, in a way that's concise and not so different visually than the tree it generates.
However, all that "function-calling" will quickly become an eyesore and a keyboard-sore - navigating complex elements quickly becomes mentally straining. What if I told you we can bring the HTML back into the JavaScript?
This is exactly what JSX is. It allows developers to write the h
function calls in an HTML-esque (or rather, XML-esque) way. Not only is there less to write, but JSX code emphasizes data and structure more clearly than the mess of function calls any regularly-sized piece of code would be.
What does it look like? Let's revisit our project from the last part:
const root = (
<div id="app">
<header>
<h1>My custom-generated web page</h1>
<h2>How neat is that?</h2>
</header>
<section>
<div class="container">
<p class="content">That's pretty neat.</p>
</div>
</section>
</div>
);
// Prepending here because scripts should ideally be at the end of the body
document.body.prepend(root);
We can clearly see how this "simple syntax sugar" trick immediately improved the readability of our function.
If you try that using our custom-defined h
function (more on that later), however, you'll see the following error:
TypeError: Can't convert null to object
This is because when a tag doesn't have any attribute in JSX, instead of passing an empty object as argument, the transformed JS uses null
instead. We need to account for that and check for null before applying attributes:
/**
* Creates an element and returns it
* @param tag Tag name, like the HTML tag
* @param attrs Object of attributes to add to the node
* @param children Children nodes to append into the created element
*/
function h(
tag: string,
attrs: object | null,
...children: (HTMLElement | string)[]
) {
const el = document.createElement(tag);
// **ADDED**: Append given attributes (if defined)
if (attrs !== null)
for (const key of Object.keys(attrs)) {
el.setAttribute(key, attrs[key]);
}
// Insert children
for (const child of children) {
// Text is special in the DOM, we must treat it accordingly
if (typeof child === "string") {
el.appendChild(document.createTextNode(child));
} else {
el.appendChild(child);
}
}
return el;
}
Using JSX in your project
The problem with JSX, is that you can't use it directly. If you do, your JavaScript engine of choice will complain - because JSX isn't part of the JavaScript language - it's a trick placed on top of JavaScript to help with writing nested h
functions. This is where Babel comes in.
Babel has plugins to turn your JSX into h function calls, which in turns makes the JavaScript valid again. A proper introduction to Babel isn't in the scope here, and if you don't know about Babel, you should definitely check it out as it has become part of the everyday tooling of the JavaScript developer.
Another solution to enable using JSX is with TypeScript. The TS transpiler has support for JSX, and you can convert it using it. Again, configuring TypeScript is outside the scope, and the documentation for the project is awesome, so do check it out if you need it.
In the next part
We're going to step outside of the boring but necessary theory and create our first project, using what we learned on top of Preact!
Top comments (0)