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:
- Generalization: you might want the parse tree to support attributes in addition to children.
- Sanitization: you might want to limit the render function to only render certain tags and attributes, if the parse tree is user supplied. The render function as is could generate unsafe html.
- Vue components: as mentioned earlier, the advantage of this approach is that it can include vue components in the tree, not only HTML elements.
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.