Thomas McDevitt

precompilation of templates with handlebars

For those who missed my last blog post, our choice of JavaScript templating library is Handlebars.js (have a re-read for reasons why!). Going from a template to an HTML output with handlebars has approximately this workflow:

  1. Write out the template as HTML with mustaches.
  2. Grab the template text with JavaScript.
  3. Compile it into the handlebars function.
  4. Pass the object to the function and have it return the desired HTML as a string.

There are two broad choices with respect to where and how this is done. With the process I described previously, this is mostly done by the client — but we can also delegate some of the work elsewhere.

about precompilation and how it’s done

By precompiling templates, steps 1-3 of the above workflow are essentially done in advance. The compilation is by far the most intensive part of handlebars usage so it’s worth alleviating this work from the client’s browser. This also means that a more lightweight version of handlebars can be deployed since compilation is no longer needed on the front end, resulting in a considerable reduction in bandwidth usage (handlebars.min.js is ~65KB; handlebars.runtime.min.js is ~10KB).

You’ll need node.js installed to start with precompilation. Once it’s up and running, simply install handlebars like this:

npm install -g handlebars

Here, the ‘-g’ implies global, meaning that handlebars can be accessed from the terminal on its own. We can thus use it to precompile templates in a given directory like this. Check out the documentation for a full list of options.

handlebars -m templates/* -f ./script/templates.js

Remember to include the output script file in your markup. Finally, invoking any template can now be done in a single step. The extra line caches the function for clarity.

// will end up with the file name
var template = Handlebars.templates['myTemplate.hbs'];
var pageHTML = template(data-object);

how else does this differ from client-side compilation?

Other than the performance benefits described in the previous section, there are a few more advantages to consider…

  • The templates become easier to handle — templates no longer need to be wrapped in <script> or <template> tags since they’re not needed in the markup. They can also be divided into individual *.hbs or *.handlebars files, making it easier to use syntax highlighters.
  • The markup becomes neater — before, adding templates meant either new HTML imports or bulky script tags with template content. Now we can inject it directly into the script.

…but there are some drawbacks…

  • Extra time is needed to set up the tasks.
  • The precompiler needs to be run every time the template is changed.
  • Precompiler and runtime library version matching — typically not a problem with this approach but can cause issues down the line. More on this shortly.

If these aren’t a problem for you then you’re golden. Enjoy precompilation to your heart’s content. For everyone else, there is something else I can suggest that will make this process even slicker

automating precompilation with gulp

At MetaBroadcast we use gulp as our JavaScript build tool and task runner. If this isn’t your choice, then the same approach may still work but will need to be adapted.

A rudimentary gulp task for our precompilation might look like this:

var handlebars = require('gulp-handlebars');
var wrap = require('gulp-wrap');
var declare = require('gulp-declare');
var concat = require('gulp-concat');

gulp.task('compile-templates', function() {
  gulp.src('templates/*.hbs')
    .pipe(handlebars())
    .pipe(wrap('Handlebars.template(<%= contents %>)'))
    .pipe(declare({
      namespace: 'HBTemplates',
      noRedeclare: true
    }))
    .pipe(concat('templates.js'))
    .pipe(gulp.dest('script'));
});

Using this, we can simply tell gulp to run the precompilation for us.

gulp compile-templates

Easier yet, just add it to your watch function. Every time you make a change to a template, gulp will compile it for you!

gulp.task('watch', function() {
  gulp.watch('src/**/*.scss', ['run-sass']);
  gulp.watch('src/**/*.js', ['compile-js']);
  gulp.watch('src/**/*.hbs', ['compile-templates']);
});

Invoking the template is the same as before, except now we’ve got a different global variable for our templates. In the example above, we’ve given it the name ‘HBTemplates’ so I’d find the templates at HBTemplates[‘myTemplate’] (no need for file extension this time). However, there are disadvantages with this approach.

The version of the precompiler needs to match the Handlebars runtime library being used. This isn’t a problem when using a bundled library for client-side compilation — nor should it be an issue if simply using the latest versions for precompilation and runtime alike. However, the npm module used in this example for precompilation with gulp has an out-of-date compiler meaning that the runtime library needs to be downgraded accordingly (as of the time of this posting, gulp-handlebars works with version 2.0.0 of Handlebars runtime).

Whether or not it’s worth automating precompilation anyway is down to the user.

concluding

Personally, I haven’t had any issues with using an older compiler for Handlebars templates, but other precompilers (for gulp or otherwise) may be more up to date so do check them out. As for the extra setup, particularly if your organisation uses standardised frameworks for front end development it’s well worth setting up a precompiler that can be used across multiple projects. Please do share your opinions and, if this article has helped you at all, please get in touch and let us know!

If you enjoyed the read, drop us a comment below or share the article, follow us on Twitter or subscribe to our #MetaBeers newsletter. Before you go, grab a PDF of the article, and let us know if it’s time we worked together.

blog comments powered by Disqus