Last week I started playing with Node.js and LinkedIn’s fork of Dust.js for server side templating. I think I am beginning to see the appeal that made LinkedIn choose Dust.js over a number of alternatives. Back when LinkedIn had a templating throwdown and chose Dust.js, they classified it in the ‘logic-less’ group, in contrast to the ’embedded JavaScript’ group that allows you to write arbitrary JavaScript in your templates. Here is the list of reasons I like Dust.js over the alternatives:
- Less logic instead of logic-less. Unlike Mustache, it allows you to have some sensible view logic in your templates (and add more via helpers).
- Single instead of double braces. For some reason Mustache and Handlebars decided that if one set of curly braces is good, two sets must be better. It is puzzling that people insisting on DRY see no irony in needing two sets of delimiters for template commands and variables. Typical Dust.js templates look cleaner as a result.
- DRY but not to the extreme (I am looking at you, Jade). I can fully see the HTML markup that will be rendered. Life is too short to have to constantly map the shorthand notation of Jade to what it will eventually produce (and bang your head on the table when it barfs at you or spits out wrong things).
- Partials and partial injection. Like Mustache or Handlebars, Dust.js has partials. Like Jade, Dust.js has injection of partials, where the partial reserves a slot for the content that the caller will define. This makes it insanely easy to create ‘skeleton’ pages with common areas, then inject payload. One thing I did was set up three injection areas: in HEAD, in BODY where the content should go and at the end of BODY for injections of scripts.
- Helpers. In addition to very minimal logic of the core syntax, helpers add more view logic if needed (‘dustjs-helpers’ module provides some useful helpers that I took advantage of; you can write your own helpers easily).
- Stuff I didn’t try yet. I didn’t try streaming and asynchronous rendering, as well as complex paths to the data objects, but intend to study how I can take advantage of them. Seems like something that can come in handy once I get to more advanced use cases. That and the fact that JSPs support streaming so we feel we are not giving up anything by moving to Node/Dust.
- Client side templating. This also falls under ‘stuff I didn’t try yet’, but I am singling it out because it requires a different approach. So far our goal was to replace our servlet/JSP server side with Node/Dust pair. Having the alternative to render on the client opens up a whole new avenue of use cases. We have been burnt in the past with too much JavaScript on the client, so I want to approach this carefully, but a lot of people made a case for client side rendering. One thing I would like to try out is adding logic in the controller to sniff the user agent and render the same template on the server or on the client (say, render on the server for lesser browsers or search engine bots). We will definitely try this out.
In our current project we use a number of cooperating Java apps using plain servlets for controllers and JSPs for views. We were very disciplined to not use direct Java expressions in JSPs, only JSTL/EL (Tag Library and Expression Language). JSPs took a lot of flack in the dark days of spaghetti JSPs with a lot of Java code embedded in views, essentially driving current aversion to logic in templates to absurd levels in Mustache. It is somewhat ironic that you can easily create similar spaghetti Jade templates with liberal use of embedded JavaScript, so that monster is alive and well.
Because of our discipline, porting our example app to Node.js with Dust.js for views, Express.js for middleware and routing was easy. Our usual client stack (jQuery, Require.js, Bootstrap) was perfectly usable – we just copied the client code over.
Here is the shared ‘layout.dust’ file that is used by each page in the example app:
<!DOCTYPE html> <html> <head> <title>{title}</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> <link rel="stylesheet" href="/stylesheets/base.css"> {+head/} </head> <body class="jp-page"> {>navbar/} <div class="jp-page-content"> {+content/} </div> <script src="/js/base.min.js"></script> <script> requirejs.config({ baseUrl: "/js" }); </script> {+script/} </body> </html>
Note the {+head/}, {+content/} and {+script/} sections – they are placeholders for content that will be injected from templates that include this partial. This ensures the styles, meta properties, content and script are injected in proper places in the template. One thing to note is that you don’t have to define empty placeholders – you can place content between the opening and closing tag of the section, but we didn’t have any default content to provide here. You can view an empty tag as an ‘injection point’ (this is where the stuff will go), whereas a placeholder with some default content will be more like ‘overriding point’ (the stuff in the caller template will override this).
The header is a partial pulled into the shared template. It has been put together quickly – I can easily see the links for the header being passed in as an array of objects (it would make the partial even cleaner). Note the use of the helper for controlling the selected highlight in the header. It is simply comparing the value of the active link to the static values and adds a CSS class ‘active’ if true:
<div class="navbar navbar-inverse navbar-fixed-top jp-navbar" role="navigation"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse"> <span class="sr-only">Toggle Navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">Examples<div class="jp-jazz-logo"></div></a> </div> <div class="navbar-collapse navbar-ex1-collapse collapse"> <ul class="nav navbar-nav"> <li {@eq key=active value="simple"}class="active"{/eq}><a href="/simple">Simple</a></li> <li {@eq key=active value="i18n"}class="active"{/eq}><a href="/i18n">I18N</a></li> <li {@eq key=active value="paging"}class="active"{/eq}><a href="/paging">Paging</a></li> <li {@eq key=active value="tags"}class="active"{/eq}><a href="/tags">Tags</a></li> <li {@eq key=active value="widgets"}class="active"{/eq}><a href="/widgets">Widgets</a></li> <li {@eq key=active value="opensocial"}class="active"{/eq}><a href="/opensocial">OpenSocial</a></li> </ul> <div class="navbar-right"> <ul class="nav navbar-nav"> <li><a href="#">Not logged in</a></li> </ul> </div> </div> </div>
Finally, here is the sample page using these partials:
{>layout/} {<content} <h2>Simple Page</h2> <p> This is a simple HTML page generated using Node.js and Dust.js, which loads CSS and JavaScript.<br/> <a id="myLink" href="#">Click here to say hello</a> </p> {/content} {<script} <script src="/js/simple/simple-page.js"></script> {/script}
In this page, we include shared partial ‘layout.dust’, then inject content area into the ‘content’ placeholder and also some script into the ‘script’ placeholder.
The Express router for this page is very short – all it does is render the template. Note how we are passing the title of the page and also the value of the ‘active’ property to ensure the proper link is highlighted in the header partial:
exports.simple = function(req, res){ res.render('simple', { title: 'Simple', active: 'simple' }); };
Running the Node app gives you the following in the browser:
Since we are using Bootstrap, we also get responsive design thrown in for free:
There you go. Sometimes it pays to follow in the footsteps of those who did the hard work before you – LinkedIn’s fork of Dust.js is definitely a very comfortable and capable templating engine and a great companion to Node.js and Express.js. We feel confident using it in our own projects. In fact, we have decided to write one of the apps in our project using this exact stack. As usual, you will be the first to know what we learned as we are going through it.
© Dejan Glozic, 2014
I experimented with dynamic content injection using a rules engine a few years ago (JRules). The goal was to explore how much of the page could be rendered on the fly with dynamic business logic. All aspects of the page were rendered in rules that emitted template snippets. It was fun and I learned a lot. It’s a great way to think about safe dynamic content but not entirely practical. Simple goes a long way.
In context of the article, I would assume that you would put a lot of the dynamic business logic computation in the controller (the C of MVC, or ‘route’ JS in Node/Express world). Dust.js is almost logic-less and would force you to decide what to put together earlier (in the route). However I can see how you could make snippets available as Dust.js partials and decide which one to combine together in the controller using dynamic business logic.
I think you’re on to something Dejan. Aside from Dust, most templating libraries are restrictive by intent. I have a theory that is less to do with the philosophical goal of separation, than that it’s easier to create a small edgy library in new fangy technology than something practical and robust. Just as a side, Mobify.js is actually using Dust.js on the client side. They are using the Capturing technique (which uses the PLAINTEXT tag to grab the HTML and manipulate it with JS). I was thinking an ideal situation would be a weightless page with a Desktop version entirely in Dust, a Mobile version entirely in Dust, both consuming an API for payload, and they can share Dust partials. It would be super fast, pretty lean, adapt to differently sized devices, but retain full control of the features (oppose to entirely responsive). The downside would be search engine’s wouldn’t pick up on HTML. I’m not sure that’s such a big deal in this day and age. Thoughts?
One thing I am always concerned about is the hoops we need to jump through to capture HTML fragments – Web Components promise HTML fragments but they surely took their time to arrive.
Aside from that, I don’t see too much issue with you proposal but since you are already using Dust, why don’t you reuse the templates on the server to render the initial page version? It will be fast, it will works flawlessly with web crawlers, and you can share partials with your desktop and mobile version. The ‘run on server and client’ of Node/Dust combo is what intrigues me most – kind of allows you to have your Kate and Edith too :-).
are you have that source code? i wanna learn about partial js templating with dust
can you push that to github? here my git http://github.com/zulfinjuliant/
thank you
I am wondering if in 2017, after 3 years of this article, if you are still using dust. Do you use the adaro package for the dust view engine in nodejs?
We do actually. Of course, you can see from my other posts that we are using React for all non-trivial rendering, but we still use dust to render the envelope of the page. In fact there is an article on that (using Dust as a ‘React Delivery Vehicle’ :-).
After I wrote my comment, I found and printed the other articles out and am going over them. They are really well written. Well done
Thanks!
Dejan,
have you had a chance to look at markojs? It looks like a very interesting templating/framework. I looks like dustjs on steroids.
Yes, some friends from PayPal mentioned it to me, but as we have all the steroids we need from React, we are happy with what dust.js is giving us for our now limited needs. Essentially we reach for dust.js for less demanding (mostly static pages that are rendered on the server but don’t change on the client much). We use React for anything that requires client-side behaviour, which is most of our pages these days :-).