In Chapter 2, Working with Views, we learned the advantages of using precompiled templates in your application. In addition, we saw a number of options to store your templates as inline in the index.html
file or as separate template files. We also saw how we can use a template manager to precompile and cache templates to avoid compilation overhead every time. However, this precompilation process will run anyway when you start your application, which will surely take a certain period of time. Wait! Aren't these templates static resources? Then the compiled versions of the templates without data will also be static resources. Right? Then why not keep a separate file with all of the precompiled templates ready and use it as soon as your application starts? If you get a file with all of your templates already precompiled and minified, it will certainly boost your application's performance. This is what we will try here—we will develop a script to precompile the templates on the server side, which will traverse all of the template files and create a single template manager file. We use Node.js here, but you can use any server-side technology to get the same result. The complete working code is given in our code samples.
To precompile, we need a template engine with precompilation support. We will use Underscore.js here, but you are free to use your desired template engine to achieve the result. The following Node.js example shows you how to achieve this functionality:
// load the file system node module var fs = require('fs'), // load the underscore.js _ = require('../../../lib/underscore.js'); var templateDir = './templates/', template, tplName, // create a string which when evaluated will create the // template object with cachedTemplates compiledTemplateStr = 'var Templates = {cachedTemplates : {}}; \n\n'; // Iterate through all the templates in templates directory fs.readdirSync(templateDir).forEach(function (tplFile) { // Read the template and store the string in a variable template = fs.readFileSync(templateDir + tplFile, 'utf8'); // Get the template name tplName = tplFile.substr(0, tplFile.lastIndexOf('.')); // Add template function's source to cachedTemplate compiledTemplateStr += 'Templates.cachedTemplates["' + tplName + '"] = '; compiledTemplateStr += _.template(template).source + '\n\n'; }); // Write all the compiled code in another file fs.writeFile('compiled.js', compiledTemplateStr, 'utf8');
The preceding code is pretty self-explanatory. We created a complete JavaScript snippet as a string that will be returned to the frontend. Here are the steps to do so:
First, we browse through each template file in the
templates
directory and retrieve their contents.We already have an object
Templates.cachedTemplates
defined and we need to store each template file's contents in this object with the template filename as a property and the template string as its value.Underscore's
_.template()
method, in general, returns a function. It also provides a property calledsource
that gives the textual representation of that particular function. The following line will give you the function source code:_.template(template).source
We place all of the function strings inside
Templates.cachedTemplates
one by one, and once the loop is over, we write the entire contents to another JavaScript file.
Now assume that the client side is asking for the templates.js
file that contains the complete template content of the project. On the server side, we can write the following code that will send the compiled.js
file content to the browser:
// While templates.js file is loaded, it will // send the compiled.js file's content app.get('/templates.js', function (req, res) { res .type('application/javascript') .send(fs.readFileSync('compiled.js', 'utf8')); });
So, a request to the template.js
file on the client side will display content similar to the following code:
var Templates = { cachedTemplates: {} }; Templates.cachedTemplates["userLogin"] = function (obj) { var __t, __p = '', __j = Array.prototype.join, print = function () { __p += __j.call(arguments, ''); }; with(obj || {}) { __p += '<ul>\r\n <li>Username:\r\n <input type="text" value="' + ((__t = (username)) == null ? '' : __t) + '" />\r\n </li>\r\n <li>Password:\r\n <input type="password" value="' + ((__t = (password)) == null ? '' : __t) + '" />\r\n </li>\r\n</ul>\r\n'; } return __p; }
The final output is the TemplateManager
object with the template's filename as its property and the compiled version of the template as the value of that property. This way, all of your template files will get added to the TemplateManager
object. However, for this piece of code, you need to make sure that each template's filename is different. Otherwise, the template of the files with the same name will get overwritten by another.
You do not need to understand this compiled template function definition, as this will be used internally by the library. Be assured that once you call this function with the data object, you will get the proper HTML output:
var user = new Backbone.Model({ username: 'hello', password: 'world' }); // Get the html var html = Templates.cachedTemplates.userLogin(user.toJSON());
This solution for precompiling JavaScript templates is very effective and you can use the same concept freely in your projects. We have used this concept in multiple projects successfully.