Vue tree renderer

 from Red Blob Games
3 Nov 2018

On the Vue discord someone mentioned wanting to display sanitized html. I was wondering if this could be done with a recursive render function. Let's say we have a parse tree like this:

let parseTree = [
    'span',
    ['b', 'Fred'],
    ' and ',
    ['b', ['i', 'Ginger'], 'ella']
];

and we want to render <span><b>Fred</b> and <b><i>Ginger</i>ella</b></span>.

One way to do this would be to generate HTML, then use Vue's v-html directive. But another way would be to use a render function[1] to inspect that parse tree and output the right type of node. The advantage of the render function is that it could also output Vue components, not only plain HTML.

// This is Vue 2 but the same idea works in Vue 3; details are different
Vue.component('v-render-tree', {
    functional: true,
    props: ['tree'],
    render(h, context) {
        function traverse(tree) {
            if (typeof tree === 'string') {
                return tree;
            } else {
                let [type, ...children] = tree;
                return h(type, children.map(traverse));
            }
        }
        return traverse(context.props.tree);
    }
});

Let's make an interactive demo:

new Vue({
    el: "#vue",
    data: {tree: JSON.stringify(parseTree, null, 4)},
    template: `
      <div class="side-by-side">
        <textarea cols="40" rows="15" v-model="tree"/>
        <p id="output">
          <v-render-tree v-bind:tree="parsed"/>
        </p>
      </div>`,
    computed: {
      parsed() {
        try {
          return JSON.parse(this.tree);
        } catch (e) {
          return ['b', 'parse error'];
        }
      },
    },
});

Try it out. Edit this parse tree:

More things you could add:

This page grew out of an earlier experiment with a non-nested example[2] that highlights urls using a render function. I wrote this in 2018 with Vue 2 but I think the concept should work just as well in Vue 3.

Using a render function also preserves existing dom nodes, whereas v-html overwrites them. Try this vue3 playground[3] that shows the advantage of using the render function.

Email me , or tweet @redblobgames, or comment: