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?

More Posts by Robert Prehn:

Kubernetes, Delivered

Deploy your first app within 24 hours. Book a demo to get started.