When developing single page web applications, patterns of structure, organization and reuse become ever more important. This especially holds true when there is a need to maintain mulitiple web applications, each of which targeting a specific form factor, while also sharing many of the same underlying core APIs.
In the context of client-side templating, such patterns begin to emerge, quite naturally so, when leveraging RequireJS modules and the RequireJS text plugin.
Template Modules
One specific pattern I have found myself implementing is that of a single Templates Module which provides a centralized location from which all compiled templates within an application can be referenced. A rather simple pattern, Template Modules are only concerned with loading, compiling and providing a public API to access compiled templates; that is, a Templates Module simply requires all external templates, and provides named methods for retrieving the compiled template functions of each.
A basic implementation of a Templates module is as follows (while Handlebars may be used in this example, any template engine would suffice):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | define( function( require ) { var Handlebars = require('Handlebars') , _template1 = require('text!app/templates/template-1.tpl') , _template2 = require('text!app/templates/template-2.tpl') , _template3 = require('text!app/templates/template-3.tpl') , _template4 = require('text!app/templates/template-4.tpl'); return { template1: function() { return Handlebars.compile( _template1 ); }, template2: function() { return Handlebars.compile( _template2 ); }, template3: function() { return Handlebars.compile( _template3 ); }, template4: function() { return Handlebars.compile( _template4 ); } } }); |
The main benefit of implementing a Templates Module is reuse, as different modules can use the same templates without a need for redundantly requiring and compiling the templates themselves. Additionally, Template Modules provide a convenient means of abstracting away the underlying template engine from client code, thus reducing the amount of refactoring needed should the template engine itself ever need to change.
When using the RequireJS Optimizer, each external template will be included in the optomized build and loaded synchronously, and so there is no additional overhead in terms of HTTP requests when requiring each template in a single location.
You can check out a basic example implementation of a Templates Module (in the context of Backbone) here.
Leveraging the text plugin I made a new plugin where I added a single line to compile te template before it’s returned by requirejs, what do you think? explained here:
http://stackoverflow.com/questions/9887512/precompiled-handlebars-templates-in-backbone-with-requirejs/11061975#11061975
Hi! I have just recently been looking into possible solutions for compiling templates using RequireJS, so this hit the nail directly.
Two points that I wanted to point out:
1. If I recall correctly (it is certainly true of Ember.JS, which uses a modified Handlebars), there is some sort of global template cache, like ‘Handlebars.TEMPLATES’, which could mean that a Template Module would not even need to return an export.
2. While this is a great method for accessing templates while still using a Resource Manager like RequireJS, this still doesn’t take advantage of template “Precompiling”. I.e. you are still downloading an HTML string that is then compiled client-side into a JS function. Precompiling would allow to download only JS. On the other hand, I haven’t been able to find enough explaining the advantage of precompiling, so maybe your solution is good enough too.
Thanks, Schmulik.
P.s Is there a specific reason you use the ‘define(function(require) { … })’ syntax?
Extremely useful. Nice work on this. I have already noticed some performance increases.
Hi Schmulik,
Yes, when pre-compiling templates in Handlebars, a].
templates
property is provided from which each compiled template can be referenced via Handlebars.templates[I actually plan to branch the github project containing the example to demonstrate how to leverage Template Modules using both runtime compilation of templates (during development) and pre-compilation of templates (in production). Ideally, the difference would remain transparent to client implementations, which is another reason why leveraging Template Modules is nice as it helps to ensure that abstraction from client code.
As for the use of:
define(function(require){...})
:– as oppossed to the more common:
define(['ModuleA','ModuleB',...], function(ModuleA, ModuleB){...})
:– this is typically just a matter of personal preference when there are more than three or so dependencies. See http://www.ericfeminella.com/blog/2012/05/17/organizing-require-js-dependencies/ for more thoughts on the “sugar syntax”.