
JSX is Useful for More Than React
Introduction: How JSX Works
If you’ve been using JSX and React, you know that the JSX transform converts from XML-like code to a nested block of JavaScript function calls. For example:
/** @jsx */
<BlogPost>
<BlogPostHeader author={myAuthor}>
Header content
</BlogPostHeader>
<BlogPostBody>
Here is my post body for this example.
</BlogPostBody>
</BlogPost>
becomes:
React.createElement(BlogPost, {author: myAuthor},
React.createElement(BlogPostHeader, null, "Header content"),
React.createElement(BlogPostBody, null, "Here is my post body for this example.")
)
You probably didn’t know that recent versions of babel can use a a customizable JSX transform that can use any JavaScript function. In other words, you can replace React.createElement
with anything you want!
You do this by changing the @jsx pragma (/** @jsx */
) and adding your function name after the @jsx bit like: /** @jsx foobar */
.
Now, the code from above would transform into:
foobar(BlogPost, {author: myAuthor},
foobar(BlogPostHeader, null, "Header content"),
foobar(BlogPostBody, null, "Here is my post body for this example.")
)
Using JSX with other frameworks or functions
The function takes three kinds of arguments:
componentClass
: If the word after the <
in the JSX starts with a capital, it passes right through the transform (Example
becomes Example
). If it is lower case, the transform converts it to a string literal (example
becomes "example"
). Think of how React treats div
vs FooBar
.
props
: The “attributes” of your “tag” are combined into a hash and passed as the second argument. <... height={100} width={100} />
becomes fn(..., {height: 100, width: 100})
children
: The rest of the arguments are the “children” of your tag and the JSX transform processes them in the same way as the parent. <Parent><Child /><Child /></Parent>
becomes: fn(Parent, null, fn(Child), fn(Child))
Uses
This is useful to support JSX with web frameworks other than React (see Preact). That is probably the most useful thing to do with this configuration. You could use this to do anything that lends itself to functional composition. Some ideas:
- You could create DSLs with an XML-like syntax within your JavaScript.
- You could abuse the JSX transform to compile XML configuration or seed data into JavaScript objects (please don’t).
- You could even make a whole XML-syntax functional programming language that compiles to JS (just stop).
A simple, but silly example: Boolean Logic in JSX
Here’s a simple boolean logic calculator using JSX:
/** @jsx Bool.exec */
class Bool {
static exec(operation, _opts, ...children) {
return this[operation](children)
}
static and(children) {
let child
let retval = true
for(child of children) {
retval = retval && child
}
return retval
}
static or(children) {
let child
let retval = false
for(child of children) {
retval = retval || child
}
return retval
}
static not(child) {
return !child
}
}
alert(<and>
<or>
<not>
{true}
</not>
{true}
</or>
{true}
<or>
{false}
{true}
</or>
{false}
</and>)
The JSX bit at the end compiles to:
alert(Bool.exec(
"and",
null,
Bool.exec(
"or",
null,
Bool.exec(
"not",
null,
true
),
true
),
true,
Bool.exec(
"or",
null,
false,
true
),
false
));
where each call to Bool.exec
delegates to an operator method and returns the result. <and>{a} {b}</and>
is the same as a && b
(with more overhead).
What do you think: is using JSX for DSLs a good idea? How would you use it?