Assembling a frontend stack part 3: Handlebars and JSHint
Edit: Some of the material in the Assembling a Frontend Stack posts is outdated. Specifically, I no longer use Bower in any of my projects.
Handlebars is a good templating solution
Handlebars seems to have won the day. I don't have many complaints. And someday soon we'll have HTMLBars and everything will be amazing.
Adding Handlebars to our build
Let's add Handlebars to our build.
First create a new Backbone view that will make use of a Handlebars template at src/js/views/CountriesTable.js
like this:
(function () {
"use strict";
var TableView = require('./Table'),
template = require('../templates/countries-table');
module.exports = TableView.extend({
render: function () {
var rows = [ {
name: 'Austria',
capital: 'Vienna',
region: 'Europe'
}, {
name: 'Belarus',
capital: 'Minsk',
region: 'Europe'
}, {
name: 'Barbados',
capital: 'Bridgetown',
region: 'North America'
}, {
name: 'Micronesia',
capital: 'Palikir',
region: 'Oceania'
}];
this.$el.html(template({rows: rows}));
return this;
}
});
}());
You can see that we are extending our basic table view and using a Handlebars template to render the view.
Let's update src/js/app.js
to append a CountriesTable view instead of a simple Table view.
(function () {
"use strict";
var $ = window.$,
CountriesTableView = require('./views/CountriesTable');
$(function () {
$('body').append(new CountriesTableView().render().el);
});
}());
Now let's create that Handlebars template. Create
src/hbs/countries-table.hbs
:
<tr>
<th>Country</th>
<th>Capital</th>
<th>Region</th>
</tr>
{{#each rows}}
<tr>
<td>{{name}}</td>
<td>{{capital}}</td>
<td>{{region}}</td>
</tr>
{{/each}}
Notice that we require
d this template in our CountriesTable view like this: require('../templates/countries-table')
. This is possible by using gulp to precompile our Handlebars templates and then stash them at src/js/templates/
, where they will be accessible to our JS modules.
Setting up the Handlebars precompilation in gulp
First, install the gulp-handlebars plugin.
npm install --save-dev gulp-handlebars
This plugin precompiles Handlebars templates. It also provides options to transform the compiled templates in various ways, but none do exactly what we want: a single compiled template per file that is simply wrapped in module.exports = ;
For that, we'll use a simple gulp plugin: gulp-wrap
npm install --save-dev gulp-wrap
Now update your gulpfile.js to look like this:
"use strict";
var gulp = require('gulp'),
mbf = require('main-bower-files'),
concat = require('gulp-concat'),
handlebars = require('gulp-handlebars'),
wrap = require('gulp-wrap'),
browserify = require('gulp-browserify');
gulp.task('bower', function () {
gulp.src(mbf({includeDev: true}).filter(function (f) { return f.substr(-2) === 'js'; }))
.pipe(concat('vendor.js'))
.pipe(gulp.dest('public/js/'));
});
gulp.task('browserify', ['handlebars'], function () {
gulp.src(['src/js/app.js'])
.pipe(browserify())
.pipe(gulp.dest('public/js/'));
});
gulp.task('handlebars', function () {
return gulp.src('src/hbs/**/*.hbs')
.pipe(handlebars())
.pipe(wrap('module.exports = Handlebars.template(<%= contents %>);'))
.pipe(gulp.dest('src/js/templates/'));
});
Notice that we have a new task called handlebars.
In addition, the browserify task now has a second parameter: ['handlebars']
. This means that the browserify task now depends on the handlebars task and will run it before starting. Run the browserify task to both precompile the handlebars templates and assemble the Javascript files.
gulp browserify
public/js/app.js
now contains the code from the following files
src/js/app.js
src/js/views/CountriesTable.js
src/js/Table.js
src/js/templates/countries-table.js
(which was compiled fromsrc/hbs/countries-table.hbs
)
JSHint
And now for some Javascript linting. Linting catches lots of bugs before you even run your code; very handy.
Create a .jshintrc file
You need a .jshintrc file in the root directory of your project. You can pick whatever options you want. Here's one that I like; It's pretty strict.
{
"maxerr" : 50,
"bitwise" : true,
"camelcase" : false,
"curly" : true,
"eqeqeq" : true,
"forin" : true,
"immed" : true,
"indent" : 2,
"latedef" : true,
"newcap" : true,
"noarg" : true,
"noempty" : true,
"nonew" : true,
"plusplus" : true,
"quotmark" : false,
"undef" : true,
"unused" : true,
"strict" : true,
"maxparams" : false,
"maxdepth" : false,
"maxstatements" : false,
"maxcomplexity" : false,
"maxlen" : false,
"asi" : false,
"boss" : false,
"debug" : false,
"eqnull" : false,
"es5" : false,
"esnext" : false,
"moz" : false,
"evil" : false,
"expr" : false,
"funcscope" : false,
"globalstrict" : false,
"iterator" : false,
"lastsemic" : false,
"laxbreak" : false,
"laxcomma" : false,
"loopfunc" : false,
"multistr" : false,
"proto" : false,
"scripturl" : false,
"shadow" : false,
"sub" : false,
"supernew" : false,
"validthis" : false,
"browser" : true,
"couch" : false,
"devel" : false,
"dojo" : false,
"jquery" : false,
"mootools" : false,
"node" : false,
"nonstandard" : false,
"prototypejs" : false,
"rhino" : false,
"worker" : false,
"wsh" : false,
"yui" : false,
"globals" : {
"require": true,
"module": true
}
}
Add JSHint to the build
First install the gulp plugin (and a nice formatter):
npm install --save-dev gulp-jshint jshint-stylish
Next update your gulpfile:
"use strict";
var gulp = require('gulp'),
mbf = require('main-bower-files'),
concat = require('gulp-concat'),
handlebars = require('gulp-handlebars'),
wrap = require('gulp-wrap'),
browserify = require('gulp-browserify'),
jshint = require('gulp-jshint');
gulp.task('handlebars', function () {
return gulp.src('src/hbs/**/*.hbs')
.pipe(handlebars())
.pipe(wrap('module.exports = Handlebars.template(<%= contents %>);'))
.pipe(gulp.dest('src/js/templates/'));
});
gulp.task('browserify', ['handlebars'], function () {
gulp.src(['src/js/app.js'])
.pipe(browserify())
.pipe(gulp.dest('public/js/'));
});
gulp.task('bower', function () {
gulp.src(mbf({includeDev: true}).filter(function (f) { return f.substr(-2) === 'js'; }))
.pipe(concat('vendor.js'))
.pipe(gulp.dest('public/js/'));
});
gulp.task('jshint', function () {
return gulp.src(['src/js/**/*.js', '!src/js/templates/**/*.js'])
.pipe(jshint(process.env.NODE_ENV === 'development' ? {devel: true} : {}))
.pipe(jshint.reporter('jshint-stylish'))
.pipe(jshint.reporter('fail'));
});
The added jshint
task makes use of a few plugin options, which you can read about here.
We also add the devel: true
option when the NODE_ENV environment variable is set to "development". This allows you to leave stuff like console.log()
statements in your code during development.
We're ready to move on to Bootstrap, Less, and livereload.