Today we had a discussion at work about which templating library we should use for our backbone applications. Since Dust is the library most teams in the company use, we thought it may be the direction we want to take. I have never used Dust, but some people in the room mentioned that the fact that it was asynchronous made it a little painful to do some tasks. It was very weird for me to hear that the rendering of templates happened asynchronously, mostly because I don’t know a way to make JS execute asynchronously other than using setTimeout. So I decided to dive into the code and figure out how they are doing it.

I started my journey by getting dust from github. The only file I really needed was dist/dust-full-0.3.0.js, so I got the file and built a simple example in an HTML file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
  <script src='dust-full-0.3.0.js'></script>
  <script>
    var compiled = dust.compile("Hello {name}!", "intro");
    dust.loadSource(compiled);
    dust.render("intro", {name: "Fred"}, function(err, out) {
      console.log(out);
    });
  </script>
</head>
<body>
</body>
</html>

We can see that dust.render doesn’t return the rendered template, but it passes it to a callback function, so lets see what is happening. This is how dust.render looks like:

1
2
3
4
dust.render = function(name, context, callback) {
  var chunk = new Stub(callback).head;
  dust.load(name, chunk, Context.wrap(context)).end();
};

The three arguments we are passing are the name of the template, the object containing the data we will use in our template and the callback to which we’ll pass the final result. This function by itself doesn’t tell us what is happening. A mysterious chunk variable is created from the Stub constructor, so lets take a look:

1
2
3
4
5
function Stub(callback) {
  this.head = new Chunk(this);
  this.callback = callback;
  this.out = '';
}

It seems like the callback is saved to use later, so lets continue with dust.load:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dust.load = function(name, chunk, context) {
  var tmpl = dust.cache[name];
  if (tmpl) {
    return tmpl(chunk, context);
  } else {
    if (dust.onLoad) {
      return chunk.map(function(chunk) {
        dust.onLoad(name, function(err, src) {
          if (err) return chunk.setError(err);
          if (!dust.cache[name]) dust.loadSource(dust.compile(src, name));
          dust.cache[name](chunk, context).end();
        });
      });
    }
    return chunk.setError(new Error("Template Not Found: " + name));
  }
};

dust.cache is a hash table with the templates we have already compiled. Since we already compiled our template, it will be there and tmpl will have this value (I am formatting it to make it more readable):

1
2
3
4
5
function body_0(chk, ctx) {
  return chk.write("Hello ")
      .reference(ctx.get("name"),ctx,"h")
      .write("!");
}

Since chk is the the chunk variable we created previously we will have to take a look at what the write method does:

1
2
3
4
5
6
7
8
9
Chunk.prototype.write = function(data) {
  var taps  = this.taps;

  if (taps) {
    data = taps.go(data);
  }
  this.data += data;
  return this;
}

Not much going on here either, it only saves the data for later use, we haven’t executed the callback. Do you remember: dust.load(name, chunk, Context.wrap(context)).end(); ?. Lets look at this last step:

1
2
3
4
5
6
7
8
Chunk.prototype.end = function(data) {
  if (data) {
    this.write(data);
  }
  this.flushable = true;
  this.root.flush();
  return this;
}

The important part here is the call to this.root.flush(), which essentially executes this function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Stub.prototype.flush = function() {
  var chunk = this.head;

  while (chunk) {
    if (chunk.flushable) {
      this.out += chunk.data;
    } else if (chunk.error) {
      this.callback(chunk.error);
      this.flush = function() {};
      return;
    } else {
      return;
    }
    chunk = chunk.next;
    this.head = chunk;
  }
  this.callback(null, this.out);
}

You can see in the last line that the callback gets executed and the output is passed to it in the second argument. So, how did dust achieve to make the rendering asynchronous? Well, from what I can see in the code I would say, it didn’t. It seems like it is not doing anything asynchronously, it just chose to use a callback to pass the rendered template. This decision seems a little weird to me, but if I had to guess I would say that it has something to do with dust also running on node, and most node libraries working asynchronously.

[ debugging  javascript  programming  projects  ]
Introduction to HTML Canvas
React refs
Introduction to Next JS - React framework
Introduction to React JS
Publishing a PWA to the Play Store with PWA builder