1

Background

  • I'm using Handlebars templates in JavaScript that's part of a SpringMVC 3.x web application.
  • Project builds are done using ant.
  • Neither Node.js nor Ruby are available on the Linux server.

Problem

  • I want to precompile Handlebars templates into JavaScript as part of the build process.
  • That is, I don't want to precompile the templates locally and then have to check generated code into source control.
  • Without Node.js or Ruby, the server doesn't support the Handlebars npm package nor the Ruby gem, so I can't go the straight-forward route.

Attempts at resolution

Gems-in-a-jar worked well for Compass, but the Handlebars gem relies on native extensions and JRuby doesn't play nice with native extensions and wouldn't build them. I tried a couple of proposed solutions, but had no luck.

Other notes

I looked at Handlebars.java but that seems to be geared more toward using Handlebars templates application-wide, which feels like overkill for my purposes.

I'm looking for a light-weight solution that doesn't create a lot of dependencies and would like to avoid potential maintenance headaches as much as possible.

Community
  • 1
  • 1
Kenny Linsky
  • 1,726
  • 3
  • 17
  • 41
  • 1
    Better buy admin a drink and ask to install node.js on build server :) – raidendev Aug 13 '14 at 13:50
  • 1
    There is also a [Rhino](https://developer.mozilla.org/en-US/docs/Rhino_documentation) JS engine, which is written on Java and can be [embedded](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Embedding_tutorial). – raidendev Aug 13 '14 at 13:53

2 Answers2

2

I put together a solution based on a blog post about precompiling Handlebars with Rhino. It consists of:

  • A bit of JavaScript that precompiles the Handlebars templates.
  • An ant task using Rhino to execute the JavaScript.

The original JavaScript expects all templates to be in the same directory and concatenates all the generated JavaScript into a single file. I have templates across multiple directories and wanted to precompile each of them into an individual file, so my JavaScript is a little different, as is the ant task:

<target name="handlebars">
  <apply executable="java" dest="${javascript.dir}">
    <arg value="-jar"/>
    <arg path="${lib-ext.dir}/rhino.jar"/>
    <arg value="${lib-ext.dir}/rhino-handlebars-precompiler.js"/>
    <arg value="--handlebars"/>
    <arg value="${javascript.dir}/handlebars/handlebars.js"/>
    <arg value="--template"/>
    <srcfile/>
    <arg value="--output"/>
    <targetfile/>
    <fileset id="fileset" dir="${javascript.dir}">
      <include name="**/*.tmpl" />
    </fileset>
    <mapper type="glob" from="*.tmpl" to="*.tmpl.js"/>
  </apply>
</target>
Kenny Linsky
  • 1,726
  • 3
  • 17
  • 41
1

I've just come across this problem myself (minus the ANT integration), and solved it using Phantom JS (which as a small standalone executable can easily be checked in with your code and executed from whatever build script you choose) and a small javascript runner - see this gist.

This will take a source HTML file containing one or more handlebars templates or partials wrapped in tags with an arbitrary class name to identify which scripts to pick up, e.g.:

<script type="text/x-handlebars-template" class="hbTemplate" id="MyFabulousTemplate">
    <p>This template is FABULOUS - hello {{username}}!!!</p>
</script>

From the command line, it would be executed with something like:

phantomjs hbcompiler.js filewithtemplatesin.html hbTemplate hbPartial MyTemplates

Which would pre-compile all the templates in the source HTML and output them to stdout as something like:

if (MyTemplates === undefined) var MyTemplates = {};
if (MyTemplates.pcTemplates === undefined) MyTemplates.pcTemplates = {};
if (MyTemplates.pcPartials === undefined) MyTemplates.pcPartials = {};

MyTemplates.pcTemplates["MyFabulousTemplate"] = (pre-compiled-template-definition)
MyTemplates.pcTemplates["MySecondTemplate"] = (pre-compiled-template-definition)
...

Which you can then use in your JS with something like:

var template = Handlebars.template(MyTemplates.pcTemplates["MyFabulousTemplate"]);
// Then for e.g. execute the template and put the resulting output in the element with
// ID "target":
$("#target").html(template({ username: "CheeseMonger" }));

It's all a bit hacked together but it works for my use-case. As it stands, it needs jquery and (of course) the handlebars library in known locations (see lines 50-51 of the gist for the paths it's looking for them on), but that could easily be factored out to more parameters or whatever. You could easily re-write it to remove the need for jquery, I'm just lazy!

Mark Hughes
  • 7,264
  • 1
  • 33
  • 37