node package manager

grunt-hb

grunt-hb

NPM version Downloads Build Status Coverage Status Chat Tip

A sane static Handlebars Grunt plugin. Think Assemble, but with a lot less Jekyll baggage.

For Gulp, see gulp-hb. To precompile templates into JavaScript, see grunt-contrib-handlebars.

Install

$ npm install --save-dev grunt-hb

Example

require('jit-grunt')(grunt); // npm install --save-dev jit-grunt 
 
grunt.initConfig({
    hb: {
        target: {
            options: {
                data: './src/assets/data/**/*.{js,json}',
                helpers: './src/assets/helpers/*.js',
                partials: './src/assets/partials/**/*.hbs'
            },
            files: [{
                expand: true,
                cwd: './src/',
                src: ['{,posts/}*.html'],
                dest: './web/'
            }]
        }
    }
});
 
grunt.registerTask('default', ['hb']);

Options

Internally, this plugin uses gulp-hb and gulp-front-matter to process files. The options object will be passed directly to both plugins, so any of those modules' options may be specified. Here are some of the common ones:

data {String|Array.<String>|Object|Function}

A glob string matching data files, an array of glob strings, an object literal, or a function returning any of these. Globbed data files are merged into an object structure which mirrors the directory structure and file names.

data: './src/assets/data/**/*.{js,json}'
data: [
    './package.json',
    './src/assets/data/**/*.{js,json}'
]
data: {
    pkg: require('./package.json'),
    foo: 'bar'
}

parseDataName {Function(Object):String}

By default, globbed data files are merged into a object structure according to the shortest unique file path without the extension, where path separators determine object nesting. So if you have two data files with the paths site/meta.js and posts/nav.js, the data object will be equivelent to the following:

{
    site: {
        meta: require('site/meta.js')
    },
    posts: {
        nav: require('posts/nav.js')
    }
}

You may optionally provide your own name parser. This is helpful in cases where you may wish to exclude the directory names.

parseDataName: function (file) {
    // this.handlebars <- current handlebars instance 
    // file.path       <- full system path with extension 
    // file.shortPath  <- shortest unique path without extension 
    // file.exports    <- result of requiring the helper 
 
    // Ignore directory names 
    return path.basename(file.path);
}

helpers {String|Array.<String>|Object|Function}

A glob string matching helper files, an array of glob strings, an object of helpers, or a function returning any of these. Globbed helper files are JavaScript files that define one or more helpers.

helpers: './src/assets/helpers/**/*.js'
helpers: [
    './node_modules/handlebars-layouts/index.js',
    './src/assets/helpers/**/*.js'
]
helpers: {
    lower: function (text) {
        return String(text).toLowerCase();
    },
 
    upper: function (text) {
        return String(text).toUpperCase();
    }
}

When including helpers using globs, modules may export a single helper function. These helpers will be named by calling parseHelperName.

// lower.js 
module.exports = function (text) {
    return String(text).toLowerCase();
};

Helpers may also export an object of named functions.

// helpers.js 
module.exports = {
    lower: function (text) {
        return String(text).toLowerCase();
    },
 
    upper: function (text) {
        return String(text).toUpperCase();
    }
};

If you need a reference to the handlebars instance inside of a helper, you may expose a factory register method.

// helpers.js 
module.exports.register = function (handlebars) {
    handlebars.registerHelper('link', function(text, url) {
        text = handlebars.Utils.escapeExpression(text);
        url  = handlebars.Utils.escapeExpression(url);
 
        var result = '<a href="' + url + '">' + text + '</a>';
 
        return new handlebars.SafeString(result);
    });
};

parseHelperName {Function(Object):String}

By default, standalone helpers will be named according to the shortest unique file path without the extension. So a helper with a path of string/upper.js will be named string-upper. Note that path separators are replaced with hyphens to avoid having to use square brackets. You may optionally provide your own name parser. This is helpful in cases where you may wish to exclude the directory names.

parseHelperName: function (file) {
    // this.handlebars <- current handlebars instance 
    // file.path       <- full system path with extension 
    // file.shortPath  <- shortest unique path without extension 
    // file.exports    <- result of requiring the helper 
 
    // Ignore directory names 
    return path.basename(file.path);
}

partials {String|Array.<String>|Object|Function}

A glob string matching partial files, an array of glob strings, an object of partials, or a function returning any of these. Globbed partial files are either standalone Handlebars files, or JavaScript files that define one or more helpers.

partials: './src/assets/partials/**/*.{hbs,js}'
partials: [
    './src/assets/vendor/some-theme/partials/**/*.hbs',
    './src/assets/partials/**/*.hbs'
]
partials: {
    link: '<a href="{{url}}">{{text}}</a>',
    people: '<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>'
}

When including paritals using globs, partials may be standalone handlebars files. Each partial will be named by calling parsePartialName.

{{!-- link.hbs --}}
<a href="{{url}}">{{text}}</a>

Partials may also be modules that export an object of named partials.

// partials.js 
module.exports = {
    link: '<a href="{{url}}">{{text}}</a>',
    people: '<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>'
};

If you need a reference to the handlebars instance when defining a partial, you may expose a factory register method.

// partials.js 
module.exports.register = function (handlebars) {
    handlebars.registerPartial({
        item: '<li>{{label}}</li>',
        link: '<a href="{{url}}">{{label}}</a>'
    });
};

parsePartialName {Function(Object):String}

By default, standalone partials will be named according to the shortest unique file path without the extension. So a partial with a path of component/link.hbs will be named component/link. You may optionally provide your own name parser. This is helpful in cases where you may wish to exclude the directory names.

parsePartialName: function (file) {
    // this.handlebars <- current handlebars instance 
    // file.path       <- full system path with extension 
    // file.shortPath  <- shortest unique path without extension 
    // file.exports    <- result of requiring the helper 
 
    // Ignore directory names 
    return path.basename(file.shortPath);
}

bustCache {Boolean} (default: false)

Whether to force a reload of data, helpers, and partials by deleting them from the cache. Useful inside watch tasks.

cwd {String}

Current working directory. Defaults to process.cwd().

dataEach {Function(Object,Vinyl):Object}

A pre-render hook to modify the context object being passed to the handlebars template on a per-file basis. May be used to load additional file-specific data.

dataEach: function (context, file) {
    context.foo = 'bar';
    context.meta = require(file.path.replace('.html', '.json'));
    return context;
}

debug {Boolean} (default: false)

Whether to log the helper names, partial names, and root property names for each file as they are rendered.

file {Boolean} (default: true)

Whether to include the file object in the data passed to the template, which includes the file's front matter.

---
title: Hello World
---
<h1>{{file.frontMatter.title}}</h1>

Contribute

Tasks

Standards for this project, including tests, code coverage, and semantics are enforced with a build tool. Pull requests must include passing tests with 100% code coverage and no linting errors.

Test

$ npm test

© 2015 Shannon Moeller me@shannonmoeller.com

Licensed under MIT