Stan’s Signed Snippets

My positive/negative coding experiences

Using Express.js and Dust.js on Node.js

Express.js is the most full-featured and popular Node.js open source framework. However, its default template library of choice – Jade, is far from impressive. Not only does it use it’s own syntax(you cannot use plain HTML), but it is limited to server-side rendering. In this post, we will setup Dust.js with Express.js for server-side rendering, as well as discuss a possible implementation with client-side rendering.

Server-side rendering

Dust.js can easily be integrated into the latest Express.js version using the Consolidate.js module. This integration could easily be completed without this module, however, Consolidate.js performs the exact functionality we need, including support for caching.

For this example, we are using the latest Express 3.0 alpha source from Github.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Module dependencies.
var express = require('express')
    , routes = require('./routes')
    , http = require('http')
    , fs = require('fs')
    , path = require('path')
    , cons = require('consolidate')

var app = express();

// assign the dust engine to .dust files
app.engine('dust', cons.dust);

app.configure(function(){
    app.set('view engine', 'dust');
    app.set('views', __dirname + '/views');
    app.use(express.static(__dirname + '/public', {redirect: false}));
    app.use(express.bodyParser());
    app.use(express.session({ secret: 'very_unique_secret_string',
        cookie: { maxAge: 1800000 }}));
    app.use(app.router);
 });

app.get('/', function(req, res){
  res.render('index', {
    title: 'Testing out dust.js server-side rendering'
  });
});

Note: Make sure to install the latest Consolidate.js from the Github source, since Dust.js support has been added only recently, and is not available in the node package manager just yet.

This is my code for app.js for my sample Node.js application. First we include all the required modules. We set our default template engine with the app.engine command. We pass in the default extension of all the template file-names, as well as the default engine that will be used to render them.

We can set the default file-name extension by setting the ‘view engine’ property. This is a very convenient feature. Let’s say our file-names end with .dust. Instead of specifying ‘filename.dust’, we can just pass in ‘filename’.

The index page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>{+title}7th-Degree{/title}</title>
    <script type="text/javascript" src='/javascripts/utils.js'></script>
    <link rel="stylesheet" type="text/css" href="/stylesheets/style.css">
    {+http_header/}
</head>
<body>
<div id="wrapper">
    <div id="header">
        <div class="logo"></div>
        <div class="header_login">
            {?username}
              {username}
            {:else}
            <form method="POST" action="/signin">
            <label>Email <input type="text" name="user[username]" id="s-user"></label>
            <label>Password<input type="text" name="user[password]" id="s-pass"></label>
            <input type="submit" class="submit" value="Login">
            </form>
            {/username}
        </div>
        {+header/}
    </div>
    <div id="content">
        <div id="right">
          {+right/}
        </div>
        <div id="left">
          {+left/}
        </div>
    </div>
    <div id="footer">
        <ul>
            <li>About</li>
            <li>Contact/Bug Report</li>
            <li>Copyright © 2011-2012 Stanislav Palatnik. All rights reserved.</li>
        </ul>
    </div>
</div>
</body>
</html>

After this index page gets rendered through dust.render(), it will replace all the keys and conditional statements with plain HTML code. For example, let’s say I added username: ‘Stan’ to the response object. Dust.js would detect that the username token is present, and would show my username stead of the login form!

Dust.js Crash Course

You might not recognize some symbols or tags in that HTML code. That’s because they are used by the Dust.js parser to add additional functionality(conditionals, includes). Let’s start with the title. {+title}7th-Degree{/title}. First let’s ask ourselves what problem we are trying to solve. We want to have a standardized header and footer. We want to dynamically change the title in each page, without having to change it manually in the layout.

By surrounding the title with {+title} tags, we can dynamically change the title by passing the variable ‘title’ during the request. If you look back at the previous Gist, we are calling res.render, and passing in title: ‘Testing out dust.js server-side rendering’. Dust.js will see this and dynamically replace the title within those tags on the server. When the client(or crawler) loads this page, they will only see the ladder title. Similarly, the {+http_header/} tag serves the same purpose. But this case, we don’t have a default value, so we open and close the tag to create a placeholder to add additional more information in the header, like additional JavaScripts of stylesheets.

Conditional tags
1
2
3
4
{?username}
  {username}
{:else}
  Please Log-In!

Another great feature of Dust.js is support for conditionals. Putting ? in front of a tag is similar to checking if the variable the tag represents has been set. If not, we render the content in after the {:else}.

Client-side rendering:

A recent case study by LinkedIn shows the true benefits of client-side rendering: How LinkedIn leverages Dust.js.They make a strong case in support of Dust.js. One of the major advantages is removing load from your servers. Client-side templates can be served from your CDN network, while the data that populates them can come straight from your server. In this scenario, you get the best of both worlds. Offloading bandwidth to CDN servers while constantly serving the most updated content. Additionally, you are offloading the computational power to parse and render the templates on the client’s browser. This results in reduced server latency. The major downside is that you have to include the entire Dust.js framework inside the webpage, which adds considerable overhead for slow internet connections and under-powered devices.

Let’s take our previous example and port it to client-side rendering. In this case, we do not need to load the consolidate module on the server. We do not need to parse any variables on the server. So what’s the catch? In order to enable client-side rendering, we need to compile the dust templates into JavaScript files before we serve them to the client.That means that every time we make a change to the template, we need to compile it into JavaScript. Compiling can be done with the dust.compile() command. The alternative would be to compile the HTML source each time on the client. This is most efficient approach for the server, it does not have to worry about tracking changes to the template. However, compiling a complicated template on the client will create substantial overhead for the browser.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<script src="dust-full-0.3.0.min.js"></script>
<script type="text/javascript">
//example showing client-side compiling and rendering
var compiled = dust.compile("Hello {name}!", "index");
dust.loadSource(compiled);

dust.render("index", {name: "David"}, function(err, out) {
  if(err != null)
    alert("Error loading page");
  //assume we have jquery
  $("#pageContainer").html(out);
});
</script>
</head>

<body>
<div id="pageContainer"></div>
</body>
</html>

In this simple example, we are compiling and rendering the template all inside the client. First we compile the template into JavaScript that Dust.js can understand. Then we render the template using Dust.js. In this case, the engine performs a variable substitution to replace {name} with David. In this case, we maximized the amount of work the client does, which minimizes the work for us on the server. However, there are some issues to pure client-side rendering:

Increased Bandwidth - The entire Dust.js framework is 26 kb. If we remove client-side rendering functionality, the size is drastically reduced to 6 kb. That extra 20 kb will substantially increase bandwidth usage. Search Engine Optimization - Search engine crawlers will not be able to use the template because they cannot execute JavaScript. However, in the LinkedIn article mentioned earlier in the article, they address this issue by falling back to server-side rendering if the client does not support JavaScript. This solves the crawler issue, as well as client that does not support JavaScript these days. Click here to check out more information about the Dust.js API

The major takeaways from this post is that Dust.js is a great templating engine. It’s powerful, versatile, and efficient. And with Express 3.0, it’s a breeze to setup.