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
- Node.js which includes NPM.
- Browserify
- Gulp
- Command shell such as terminal, Cygwin or Bash on Ubuntu on Windows.
- Code Editor.
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
- modules
- hbs
- 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);