Handlebars Templates with Browserify

This post documents how to use the hbsfy precompiler plugin for Browserify to compile Handlebars templates into javascript. With this design, templates can be required as javascript modules in the views that use them.

Requirements

Global Packages

List the global packages that are installed with the npm list –global command. Then install these if they are not already installed or need to be updated.

Install Browserify

After installing Node.js, using a Command shell, install Browserify globally.

# global browserify install
npm install -g browserify

If you have an older version of global Browserify, run npm rm –global browserify to remove the old version before installing the newer one.

Install Gulp

Install gulp globally with the npm install -g command as follows: *

# global gulp install
npm install -g gulp

If you have an older version of global Gulp, run npm rm –global gulp to remove the old version before installing the newer one.

Project Packages

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm.

# create package.json
npm init

Install Packages

These are the project node module packages needed to browserify and bundle the Handlebars template modules. Since all of the modules are bundled into a single minified javascript file, source maps are included in the bundle task to make it easier to debug in the browser’s developer tools. For convenience, a local web server module has been included.

$ npm install browserify --save-dev
$ npm install handlebars --save-dev
$ npm install hbsfy --save-dev
$ npm install gulp --save-dev
$ npm install gulp-sourcemaps --save-dev
$ npm install gulp-uglify --save-dev
$ npm install gulp-util --save-dev
$ npm install gulp-webserver --save-dev
$ npm install vinyl-source-stream --save-dev
$ npm install vinyl-buffer --save-dev

Create a gulpfile.js in the project root to define and configure the Gulp tasks. The default task is simply gulp. When gulp is executed, it will run the browserify bundle task, start a local web server and open the default web page in a browser.

gulpfile.js
'use strict';

var gulp        = require('gulp'),
    browserify  = require('browserify'),
    buffer      = require('vinyl-buffer'),
    gutil       = require('gulp-util'),
    source      = require('vinyl-source-stream'),
    sourcemaps  = require('gulp-sourcemaps'),
    uglify      = require('gulp-uglify'),
    webserver   = require('gulp-webserver');

var hbsfy = require('hbsfy');

gulp.task('bundle', function () {
    var b = browserify({
    entries: './src/js/index.js',
    debug: true
    });

    return b.transform(hbsfy).bundle()
    .pipe(source('bundle.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(uglify())
        .on('error', gutil.log)
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./js/'));
});

// static web server
gulp.task('server', function() {
    gulp.src('./')
        .pipe(webserver({
            port: 3000,
            directoryListing: false,
            open: true
        }));
});

gulp.task('default', ['bundle', 'server']);

Source

Create a Handlebars template in /src/hbs/

people.hbs
{{#each people}}
<li>{{firstName}} {{lastName}}</li>
{{/each}}

Create a data module in /src/js/modules/ for this template demo since we are not calling a rest API to get data.

data.js
module.exports = {
    people: [
        {firstName: "Yehuda", lastName: "Katz"},
        {firstName: "Carl", lastName: "Lerche"},
        {firstName: "Alan", lastName: "Johnson"}
    ]
}

In the /src/js folder, create the main node, index.js.

index.js
var data = require('./modules/data');
var list = require('../hbs/people.hbs');

document.querySelector('#people').innerHTML = list(data);

Web Page

Create this html file to load the javascript and render the template output.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Handlebars with Browserify</title>
</head>
<body>
    <ul id="people"></ul>
    <script src="js/bundle.js"></script>
</body>
</html>

Project Folders and Files

Here is a tree view of the projects folders and files. Browse, clone or download the source code if you prefer.

    • js
    • src
      • hbs
        • people.hbs
      • js
        • modules
          • data.js
        • index.js
    • gulpfile.js
    • package.json
    • index.html

Custom Helper Registration

To register custom helpers, require the hbsfy/runtime and run registerHelper to create the helper. This example creates a compare helper courtesy of the Comparison block helper for handlebars templates post by doginthehat.

Edit index.js, before the document.querySelector, require the hbsfy/runtime and register the compare custom helper.

index.js
var data = require('./modules/data');
var list = require('../hbs/people.hbs');

var Handlebars = require('hbsfy/runtime');

Handlebars.registerHelper('compare', function (lvalue, operator, rvalue, options) {

    var operators, result;

    if (arguments.length < 3) {
        throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
    }

    if (options === undefined) {
        options = rvalue;
        rvalue = operator;
        operator = "===";
    }

    operators = {
        '==': function (l, r) { return l == r; },
        '===': function (l, r) { return l === r; },
        '!=': function (l, r) { return l != r; },
        '!==': function (l, r) { return l !== r; },
        '<': function (l, r) { return l < r; },
        '>': function (l, r) { return l > r; },
        '<=': function (l, r) { return l <= r; },
        '>=': function (l, r) { return l >= r; },
        'typeof': function (l, r) { return typeof l == r; }
    };

    if (!operators[operator]) {
        throw new Error("Handlerbars Helper 'compare' doesn't know the operator " + operator);
    }

    result = operators[operator](lvalue, rvalue);

    if (result) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }

});

document.querySelector('#people').innerHTML = list(data);

Edit the template to utilize the compare helper.

people.hbs
{{#each people}}
<li>
{{#compare lastName "==" 'Katz'}}Mr. {{/compare}}
{{firstName}} {{lastName}}
</li>
{{/each}}

Helper Module

After testing that everything works by running gulp, this would be a good time to break out the helper function in index.js into its own module.

In the modules folder, create a hb-helpers folder and in that folder create the compare.js helper. Take the anonymous function parameter from the Handlebars.registerHelper call in index.js and assign it to the module.exports in compare.js.

compare.js
module.exports = function(lvalue, operator, rvalue, options) {

    var operators, result;

    if (arguments.length < 3) {
        throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
    }

    if (options === undefined) {
        options = rvalue;
        rvalue = operator;
        operator = "===";
    }

    operators = {
        '==': function (l, r) { return l == r; },
        '===': function (l, r) { return l === r; },
        '!=': function (l, r) { return l != r; },
        '!==': function (l, r) { return l !== r; },
        '<': function (l, r) { return l < r; },
        '>': function (l, r) { return l > r; },
        '<=': function (l, r) { return l <= r; },
        '>=': function (l, r) { return l >= r; },
        'typeof': function (l, r) { return typeof l == r; }
    };

    if (!operators[operator]) {
        throw new Error("Handlerbars Helper 'compare' doesn't know the operator " + operator);
    }

    result = operators[operator](lvalue, rvalue);

    if (result) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }

};

Update index.js: Create a compare variable and assign the exported function from the new compare module with require; Replace the anonymous function parameter in the Handlebars.registerHelper call with the compare variable.

index.js
var data = require('./modules/data');
var list = require('../hbs/people.hbs');

var Handlebars = require('hbsfy/runtime');

var compare = require('./modules/hb-helpers/compare');

Handlebars.registerHelper('compare', compare);

document.querySelector('#people').innerHTML = list(data);
Source Code
comments powered by Disqus