Inline template diffing vanilla JS

Hey guys,
Just curious, does anybody know of a better way of diffing an inline template?
I have tried using the HTML template instead of script type = ‘text/template’ but the innerHTML.toString() causes it to be 30-40% slower. I am not sure if using HTML template is better for subsequent diffs as the browser parses the HTML.

//HTML
<body>
    <script class = 'template' type = 'text/template' data-target = 'one'>

(template literals)
    <h1>${this.obj1}</h1>
    <h1>${this.obj2}</h1>
    <h1>${this.obj3}</h1>
    <h1>${this.obj4}</h1>
    <h1>${this.obj5}</h1>
    </script>
    <div></div>
</body>

// JS
const render = (x) => {
    // maybe JSON
    let obj = {
        obj1: 'One object',
        obj2: 'Two object',
        obj3: 'Three object',
        obj4: 'Four object',
        obj5: 'Five object' }
var text = document.querySelector('.template').firstChild.nodeValue;
let  div = document.querySelector('div');

// Alternate to eval()
const templator  = new Function("return `" + text + "`;");
let markup = templator.call(obj);

let frag = document.createElement('documentFragment')
frag.innerHTML = markup;

let app = () => div.insertAdjacentElement('afterend' ,frag);
requestAnimationFrame(app);
}

Thanks for your help/ opinions :grinning:

HTML templates are better for when you’re going from HTML to HTML. You’re going from string to HTML, and in using a template that would mean HTML to string to HTML which would be a bit more work. Using a script makes it easier to get all the text as a string rather than having it parsed as HTML first and having it go back to string.

As far as diffing, I’m not sure what you mean. You seem to be appending with new renders rather than updating what’s already there, so even if you wanted to diff, it wouldn’t matter too much. Otherwise, if you were updating, you can save yourself from render updates by diffing obj. If obj doesn’t change between renders, then you can bail out of the next render because it would mean the HTML wouldn’t change.

Ideally, you also wouldn’t be re-parsing the HTML again on each render. While there are a number of templating engines that work this way, the best thing to do would be to update the existing HTML rather than recreating it again from scratch, especially if the shape of the HTML doesn’t change. In your particular case, all you’re doing is filling in some h1 content so it would be easy to just target those nodes directly and replace their text whenever you need to render. Similar to something mentioned in another thread not too long ago, this could be done using something like:

HTML:

<h1 data-val="obj1"></h1>
<h1 data-val="obj2"></h1>
<h1 data-val="obj3"></h1>
<h1 data-val="obj4"></h1>
<h1 data-val="obj5"></h1>

JS:

let obj = {
  obj1: 'One object',
  obj2: 'Two object',
  obj3: 'Three object',
  obj4: 'Four object',
  obj5: 'Five object' }

//  render(div, obj)
div.querySelectorAll('[data-val]')
  .forEach(el => el.textContent = obj[el.dataset.val])

If you need to duplicate, throw it in a <template> and clone its content every time you need to make a copy.

Granted this only works for specific use cases, but it is simple and efficient in terms of not requiring the browser to constantly re-parse HTML and replace existing elements with new ones.

Beyond that, you start to get into more complicated cases where libraries like react use things like virtual doms to figure out how to update HTML through state changes.

But as far as text to HTML goes, the best you can do is to reduce renders when necessary by checking for changes in your data. Don’t bother rendering when the data doesn’t change.

Hey mate, thanks heaps for the reply.

I should have clarified, I’m doing both dynamic page changes and diffing (multi page website pulling data from other sources)
I was hoping for a magic bullet :grin:

I’ve tried a few things since:
1- cache all pages with service worker and swap pages using Range.ContextualFragment(html) then fetch data-> apply your qsa -> text Content method to the docFrag before appending to the body.
2. Save the HTML template as a template literal inside an es6 module then fetch data -> and dynamically import the template, then parse with Range.ContextualFragment(string) and append.

The pro with no1 is that the template is only stored in the cache and not on the page or in memory(as var).

The pro with no2 is that the js template is heaps more flexible and it can dynamically create x number of specific elements e.g.
obj1 = {1:‘ONE’, 2: ‘TWO’, 3: ‘THREE’}
let template = (backtick)

${obj1.map(i => (backtick)<h1${i}</h1(backtick)).join(’’)}
(backtick);
will produce:

ONE

TWO

THREE

Thanks again
.