July 17, 2017 — programming, reactive and xus

This post will introduce you to xus, the templating language I’ve been working on recently which brings the simplicity of mustache and the expressiveness of mobx-state-tree together, and makes laying out reactive user interfaces incredibly easy.

Recently I came up with an idea I think could save the world. It’s a regular mustache-y template engine which has a lexer, parser and all of that (streaming though) but instead of transforming HTML into regular old HTML again, it compiles them into an observer React component tree. That is to say, given a state object (constructed by mobx or mobx-state-tree or whatever state container implementation that you have with similar characteristics) it will re-render the tree efficiently when something has changed.

Not the most original idea, I know, but it was so tempting to implement something like this on top of mobx, and I am kind of curious about how this could change the way I write UIs.

Here’s an example of a partially finished todo app:

It starts with a template written in xus/mustache which shows a list of todo items and the number of completed ones:

<div>
  <p>You have completed <b>{completedCount}</b> of your tasks.</p>
  <p><b>Click on more tasks to finish them!</b></p>
  <ul>
    {#todos}
        <li class="{#done}finished{/done}" onclick="toggle">{title}</li>
    {/todos}
  </ul>
<div>

If you have worked with mustache or any other templating language before, this should be really straightforward.

xus is so simple, there aren’t many original features yet :) It supports mustache spec with the exception of lambdas and partials.

In mustache, this is a section: {#todos} … {/todos}. The behaviour of the section is determined by the current context. According to the spec, if todos value is of type boolean, then the HTML between the pound and slash will either be displayed or not. If it is a non-empty Array, then it is rendered one or more times. And finally, when it is an object, the value of it is used as the context for a single rendering of the block.

Nested inside the todos section, there is a li tag with title variable in it, and there is also the done section on class attribute which wraps a class name; and then another string, toggle on onclick.

Furthermore, we have completedCount on top where a string will be expanded (just like title) if the key name is found in the current context.

Looking at this template, you can more or less tell what will happen. For example, you can say, finished class will only be assigned if done is set to true. But there is no information here that tells our compiler the type of done, whether it is a boolean or not. So this will only get you so far if you are not running inside a very smart VM, many VMs shouldn’t be that smart anyway. And as for toggle, it could be anything. But you know that it’s just a reference to something else. A reference to an event, or maybe an action name.

So, we need something else that tells us about the data, how to render it and interact with it as well. We need a context.

What is missing? Types of course! Let’s start with them. Let’s focus on a single Todo item first, it should have a title and a done value. So when typed, it looks something like this:

{
  title: string
  done : boolean
}

Next, we need that toggle action which will negate the value of done:

toggle: function() {
    this.done = !this.done
}

And, congratulations! You have just created a mobx-state-tree model:

import { types } from "mobx-state-tree"

const Todo = types.model("Todo", {
  title: types.string,
  done: types.boolean
}, {
  toggle: function() {
    this.done = !this.done
  }
})

Let’s create another one.

This time, call it State with a single value of type Array which will hold a list of Todos. Ah, and before I forget, we are creating this model for the purpose of getting the number of completed tasks; so, we also need an accessor for computing that value:

const State = types.model("State", {
  todos: types.array(Todo),
  get completedCount() {
    return this.todos.reduce(function(count, todo) {
      return todo.done ? count + 1 : count
    }, 0)
  }
})

Finally, let’s create a State instance with an initial value:

const state = State.create({
  todos: [
    { title: "Get coffee", done: false },
    { title: "Wake up", done: true }
  ]
})

That’s it! We now have enough information for rendering the layout. With xus, it is super easy!

xus exposes a default function which expects a template string and a state object. Using xusify transform you can just require() your template files and they will be returned as pre-compiled render functions with this signature: function render(state, options)

Using xusify is faster because you don’t need to parse your templates again. xusify already does it for you during compilation. This is also the reason why it’s possible to emit very tiny bundles with it, because you don’t have to include parser in the final build. (In fact, it is currently mostly optimized for that!)

See xus and xusify readmes for more information about the usage.

Rendering with xus

Assuming you have saved your layout as layout.html, this is how you require() it as a stand-alone render function:

const render = require("./layout.html")

This looks very similar to a functional React.Component, and actually it is. It expects a state and an options argument which are both required.

Since we don’t use setState anymore, you should translate the state argument to props in your head.

Ok, let’s finish this up!

Given our state, following code block shows how you can create a React-ive component tree and render it into DOM using ReactDOM.render. xus does not ship with React or mobx, that’s why you need to provide React.createElement and mobx.observer methods in the options object as well:

import { createElement } from "react"
import { observer } from "mobx-react"

const tree = render(state, {
  createElement: createElement,
  observer: observer
})

ReactDOM.render(tree, document.getElementById("main"))

And, that’s it! Your partial todo application is ready.

Furthermore, it’s not magic! There is so much information there.

See the full example code here.

See the example on CodePen.

See API documentation.

Status

I’d say, at this point, xus is somewhat web-turing complete. Of course, there is still so much else to do! Optimizations, more tests, adding new language constructs and such. One new addition could be a simple range operator for example, e.g. {#todos:10–20}. Have a look at the Issues page for the planned features.

So, yes, please go ahead and create cool things with it. This is not a competitor to JSX or whatever, but it is improving :)

If you are curious why I’ve started this project and how this could play a role within a larger framework, take a look at “Templates, state trees, a school of ‘whatever’ and a state definition language”.