67

I am struggling somewhat with pre-compilation of templates in Handlebars. My jQuery Mobile project is getting pretty big template-wise and I wish to pre-compile the templates I use.

However I can't seem to find a good explanation (like a step by step tutorial) of how to do this with Handlebars.

I still have my templates all inline using the script tags. I have handlebars installed using NPM. But now I am kinda lost in how to proceed.

I am guessing doing something like

handlebars -s event.handlebars > event.compiled

and somehow including the event.compiled contents? But then, how to call it.

I am calling my templates like so

var source = $('#tmpl_profile').html(),
template = Handlebars.compile(source),
context = user.profile()),
html    = template(context);

Hope someone can shed some light on this for me.

user4035
  • 19,332
  • 7
  • 51
  • 78
Marco
  • 2,403
  • 3
  • 22
  • 19

5 Answers5

117

So after much trial and error (which is the best way to learn it) here's the way that works for me.

First- externalize all your templates, say you have a template inside script tags like

<script id="tmpl_ownevents" type="text/templates">
    {{#unless event}}
        <div class="notfoundevents"><p>No events for you</p></div>    
    {{/unless}}
</script>

Create a new file called events.tmpl and copy/paste the template into that. Be sure to remove the script elements themselves, this gotcha bit me in the a..

Install the Handlebars commandline script like so

npm install -g handlebars

go to the folder you have saved events.tmpl into and run

handlebars events.tmpl -f events.tmpl.js

Include the compiled javascript into your HTML after you included Handlebars.js

<script src="events.tmpl.js"></script>

Now all that is left is change your normal template code into something resembling the following

var template = Handlebars.templates['events.tmpl'], // your template minus the .js
    context = events.all(), // your data
    html    = template(context);

And there you have it, super fast pre-compiled Handlebars templates.

zok
  • 3,053
  • 7
  • 33
  • 55
Marco
  • 2,403
  • 3
  • 22
  • 19
  • 29
    Semantically, I find it better not to give the template files "js" extension as these files are not "pure" javascript files. The handlebars compiler understands "handlebars" extension and will remove it from the final template name, so for events.handlebars instead of Handlebars.templates['events.tmpl']; you get Handlebars.templates['events']; which I find a bit cleaner. – Peter Pajchl May 10 '12 at 15:25
  • 7
    For those who use RequireJS, there is an option (`-a` or `--amd`) in handlebars command line utility which tells it to create an AMD style function which you can later load with RequireJS. – Eye Sep 28 '12 at 02:35
  • @PeterPajchl your answer is a bit confusing, and did not work for me at face value, so I want to clarify it for others. Yes, naming your template file "name.handlebars" _before_ you compile it, and choosing to output it to "name.js" (which is then referenced in your html file) will result in Handlebars.templates['name']. However, this _does not_ mean that you should output the file to "name.handlebars" and reference that in the html. – rvr_jon Dec 03 '12 at 18:07
  • 6
    Do you have to have multiple files for multiple templates? Can someone explain how you would externalize 10+ templates? Ideally they're all in one file, and by removing the script tags, you've removed the only identification that specific templates could have. – Rey Dec 05 '12 at 18:02
  • 3
    @xckpd7 you dont need to externalize templates, see [fiddle](http://jsfiddle.net/aKwhJ/1/); so you can compile any count of handlebars and just concatenate them to single file, compilation time name will determine _Handlebars.templates_ property – ertrzyiks Jan 30 '13 at 15:47
  • There's an AMD compatible grunt task coming soon too - https://github.com/gruntjs/grunt-contrib-handlebars – Adam Waite Feb 03 '13 at 21:10
  • the final template call throws me an error: "Uncaught TypeError: undefined is not a function" (=> var result = templateSpec.call(...) ) am i missing something? any tips? thanks – DoubleU23 May 20 '14 at 09:58
  • found the problem... the compiler is @2.0.0.alpha and is incompatible with the actual handlebarsjs version (1.3.0) see: chttp://stackoverflow.com/questions/21964023/typeerror-applying-precompiled-handlebars-templates-with-a-context – DoubleU23 May 20 '14 at 10:41
  • I get `Uncaught ReferenceError: routes is not defined` on the line `context = routes.all()`. "routes" is the name of my template. The Handlebars version I used to compile (checked using `npm info handlebars`) and the one I import on the html document are the same (2.0.0) – zok Oct 10 '14 at 21:03
  • where does `events` come from in `events.all()`? whom the `all()` function belongs to? – zok Oct 13 '14 at 21:36
15

Another great option for this is to use GruntJS. Once installed:

npm install grunt-contrib-handlebars --save-dev

Then inside your gruntfile.js

grunt.initConfig({
    dirs: {
      handlebars: 'app/handlebars'
    },
    watch: {
      handlebars: {
        files: ['<%= handlebars.compile.src %>'],
        tasks: ['handlebars:compile']
      }
    },
    handlebars: {
      compile: {
        src: '<%= dirs.handlebars %>/*.handlebars',
        dest: '<%= dirs.handlebars %>/handlebars-templates.js'
      }
    }
});


grunt.loadNpmTasks('grunt-contrib-handlebars');

Then you simply type grunt watch from your console, and grunt will automatically compile all *.handlebars files into your handlebars-templates.js file.

Really rad way to work with handlebars.

Scott Silvi
  • 2,979
  • 5
  • 37
  • 63
10

Here is the way I do it:

Preparation

We will just assume all your template variables are in a variable called context:

var context = {
    name: "Test Person",
    ...
};

Where to put your templates

Create a directory containing all your templates (we'll call it templates/) Add your templates here, called filename.handlebars.

Your directory structure:

.
└── templates
    ├── another_template.handlebars
    └── my_template.handelbars

Compiling your templates

First go to your root directory, then compile your templates with the npm CLI program:

handlebars templates/*.handlebars -f compiled.js

New directory structure:

.
├── compiled.js
└── templates
    ├── another_template.handlebars
    └── my_template.handlebars

Usage

Include the compiled.js in your HTML page after you include the runtime:

<script src="handlebars.runtime.js"></script>
<script src="compiled.js"></script>

Render your templates using the global Handlebars object:

// If you used JavaScript object property conform file names
// Original filename: "my_template.handlebars"
Handlebars.templates.my_template(context)

// if you used special characters which are not allowed in JavaScript properties
// Original filename: "my-template.handlebars"
Handlebars.templates["my-template"](context)

Remarks

Note the file extension .handlebars. It is automatically stripped when compiling the templates.

Extra: if you use one of the Jetbrains IDEs together with the Handlebars/Mustache plugin you even get quite a good editor support.

apfelbox
  • 2,565
  • 2
  • 21
  • 26
  • I'm considering to use this method to load my precompiled templates , biut I'd like to know one thing before Istart to implement this. Is it possible to load both the `handlebars.runtime` and `compiled.js` through the require.js? .. – Wracker Jan 06 '15 at 11:01
  • I have no deep knowledge of requirejs, but you should define `handlebars.runtime` as a dependency of `compiled.js` (and your script has `compiled.js` as dependency). You can do that manually (by manually wrapping the compiled code in a define) or look for an existing library that does this for you. – apfelbox Jan 07 '15 at 12:50
  • @apfelbox what is npm CLI and how to add it to VS 2015? – Tomas Mar 24 '16 at 16:33
6

Precompiling Handlebars Templates with Grunt

Assuming you have Node.js installed. If you don't, go do that.

1) Setting up Node dependencies:

In your applications root directory add a file named package.json. Paste the following into that file:

{
  "devDependencies": {
   "grunt-contrib-handlebars": "~0.6.0",
    "grunt-contrib-watch": "~0.5.3",
    "handlebars": "~1.3.0"
  },
}

This JSON file tells Node what packages it needs to install. This helps to get other developers get up-and-running very quickly, as you'll see in the next step.

2) Installing Node Dependencies:

In your terminal/command prompt/powershell, cd into your projects root directory and run the following commands:

npm install grunt -g
npm install grunt-cli -g

And to install the dependencies listed in your package.json:

npm install

Now you've installed all of the node dependencies that you need. In your projects root directory, you'll see a node_modules folder.

3) Configuring Grunt:

In your projects root directory, create a file named Gruntfile.js. Paste the following into that file:

module.exports = function(grunt) {

    var TEMPLATES_LOCATION        = "./src/templates/",       // don't forget the trailing /
        TEMPLATES_EXTENSION       = ".hbs",
        TEMPLATES_OUTPUT_LOCATION = TEMPLATES_LOCATION,       // don't forget the trailing /
        TEMPLATES_OUTPUT_FILENAME = "compiled_templates.js";  // don't forget the .js

    grunt.initConfig({
        watch: {
            handlebars: {
                files: [TEMPLATES_LOCATION + '**/*' + TEMPLATES_EXTENSION],
                tasks: ['handlebars:compile']
            }
        },
        handlebars: {
            compile: {
                src: TEMPLATES_LOCATION + '**/*' + TEMPLATES_EXTENSION,
                dest: TEMPLATES_OUTPUT_LOCATION + TEMPLATES_OUTPUT_FILENAME,
                options: {
                    amd: true,
                    namespace: "templates"
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-handlebars');
    grunt.loadNpmTasks('grunt-contrib-watch');

}

4) Configuring to Your Liking:

If you are not using Require.js, you'll want to change handlebars.compile.options.amd to false. You may also want to tweak the namespace option to your liking. If you're using require/amd modules, the namespace property is unimportant (it's default is "JST", if you remove it).

Because all project structures are a little bit different, you'll need to configure the Gruntfile just a little bit. Modify the constants TEMPLATES_LOCATION, TEMPLATES_EXTENSION, TEMPLATES_OUTPUT_LOCATION, TEMPLATES_OUTPUT_FILENAME to fit your project.

If your templates are scattered throughout your application, you'll want to change TEMPLATES_LOCATION to the lowest directory possible. Make sure your templates are isolated from your node_modules, bower_components or any other 3rd party directories, because you don't want Grunt to compile 3rd Party templates into your applications compiled templates. If you include all of your own code in a ./src, ./js, ./app directory, you'll be okay.

5) Compiling templates with Grunt:

To run grunt in the background, open your terminal and cd into your projects root directory and run the command: grunt watch:handlebars (just grunt watch works as well). With grunt running in the background, all changes to your template files will be automatically compiled and the output file specified handlebars.compile.dest will be rewritten as needed. The output will look something like this:

Running "watch" task
Waiting...

To run the compile task alone, open your terminal and cd into your projects root directory and run the command: grunt handlebars:compile (just grunt:handlebars works as well). The output will look something like:

Running "handlebars:compile" <handlebars> task
File "./src/templates/compiled_templates.js" created.

Done, without errors.
Cory Danielson
  • 13,192
  • 3
  • 42
  • 49
3

For Handlebars 2.0.0 alpha Update:

@Macro has explained quite clearly how it works with 1 piece of template, please see this answer for how to make handlebars.js works

If you see "TypeError: 'undefined' is not a function" when using precompiled templates:

This error happened at "handlebar.runtime.js" line 436 when evaluting templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data),

because the compiler npm installed is not matching the one used by the browser. The latest stable version downloaded is v1.3.0 while if you use npm install handlebars, it will install 2.0.0-alpha4 for you.

Please check it out using

handlebars any_your_template_before_compile.handlebars | grep "compiler"

which will give you like, if you indeed installed handlebar 2.0.0-alpha4:

this.compiler = [5, '>=2.0.0']

With the first number stands for the version for your handlebar compiler. Type in the following code in browser console, see if the result match the version.

Handlebars.COMPILER_REVISION

If you have compiler 5 with browser revison 4, then you will have the above problem.

To fix it, install handlebar 1.3.0 with the following command

npm install handlebars@1.3.0 -g

Then try to compile it again, you will see this time, it gives you matching version info and you are good to go with the precompiled templates.

this.compilerInfo = [4, '>=1.0.0']


Just explain if you have tons of templates:

Firstly externalize each piece of your braced templates: event.handlebars, item.handlebars, etc... You can put them all in one folder, say "/templates"

Compile all the files and concatenate it into 1 file with the following command:

handlebars *.handlebars -f templates.js

And include handlebars.runtime, this file in your HTML

<script src="/lib/handlebars.runtime.js"></script>
<script src="templates.js"></script>

The templates will be injected into Handlebars.templates by their name. For example, event.handlebars can be accessed by Handlebars.tempaltes['event'].

Community
  • 1
  • 1
yeelan
  • 1,243
  • 1
  • 14
  • 20
  • Using v2.0.0-alpha.4 I had to call `Handlebars.template(Handlebars.templates.MYTEMPLATE)(CONTEXT)` to use my precompiled templates. Doesn't seem that elegant so either I'm missing something or it's just alpha. Either way, glad I figured it out. – Raine Revere Aug 15 '14 at 19:46