├── .gitignore
├── .gitattributes
├── test
├── partials
│ ├── header-test.hbs
│ ├── desktop
│ │ └── header-test.hbs
│ └── mobile
│ │ └── header-test.hbs
└── test.js
├── changelog
├── package.json
├── readme.md
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/test/partials/header-test.hbs:
--------------------------------------------------------------------------------
1 | Header Goes Here
--------------------------------------------------------------------------------
/test/partials/desktop/header-test.hbs:
--------------------------------------------------------------------------------
1 | Desktop Header Goes Here
--------------------------------------------------------------------------------
/test/partials/mobile/header-test.hbs:
--------------------------------------------------------------------------------
1 | Mobile Header Goes Here
--------------------------------------------------------------------------------
/changelog:
--------------------------------------------------------------------------------
1 | v0.3.3
2 | ======
3 | Fixed the partials to just use a blank string
4 |
5 |
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gulp-compile-handlebars",
3 | "version": "0.6.0",
4 | "description": "Compile Handlebars templates to file - gulp plugin",
5 | "license": "MIT",
6 | "repository": "kaanon/gulp-compile-handlebars",
7 | "author": {
8 | "name": "Kaanon MacFarlane",
9 | "email": "kaanonm@gmail.com",
10 | "url": "http://kaanon.com"
11 | },
12 | "engines": {
13 | "node": ">=0.10.0"
14 | },
15 | "scripts": {
16 | "test": "mocha"
17 | },
18 | "files": [
19 | "index.js"
20 | ],
21 | "keywords": [
22 | "gulp",
23 | "handlebars",
24 | "gulp-plugin",
25 | "template",
26 | "compile",
27 | "html",
28 | "rendering"
29 | ],
30 | "dependencies": {
31 | "gulp-util": "^3.0.3",
32 | "through2": "^0.6.3",
33 | "handlebars": ">=3.0.0"
34 | },
35 | "devDependencies": {
36 | "mocha": "*"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # gulp-compile-handlebars
2 | Forked from [gulp-template](https://github.com/sindresorhus/gulp-template)
3 | Inspired by [grunt-compile-handlebars](https://github.com/patrickkettner/grunt-compile-handlebars)
4 |
5 | > Compile [Handlebars templates](http://www.handlebarsjs.com/)
6 |
7 | ## Install
8 |
9 | Install with [npm](https://npmjs.org/package/gulp-compile-handlebars)
10 |
11 | ```
12 | npm install --save-dev gulp-compile-handlebars
13 | ```
14 |
15 |
16 | ## Example
17 |
18 | ### `src/hello.handlebars`
19 |
20 | ```handlebars
21 | {{> header}}
22 |
Hello {{firstName}}
23 | HELLO! {{capitals firstName}}
24 | {{> footer}}
25 | ```
26 |
27 | ### `src/partials/header.handlebars`
28 |
29 | ```handlebars
30 | Header
31 | ```
32 |
33 | ### `gulpfile.js`
34 |
35 | ```js
36 | var gulp = require('gulp');
37 | var handlebars = require('gulp-compile-handlebars');
38 | var rename = require('gulp-rename');
39 |
40 | gulp.task('default', function () {
41 | var templateData = {
42 | firstName: 'Kaanon'
43 | },
44 | options = {
45 | ignorePartials: true, //ignores the unknown footer2 partial in the handlebars template, defaults to false
46 | partials : {
47 | footer : ''
48 | },
49 | batch : ['./src/partials'],
50 | helpers : {
51 | capitals : function(str){
52 | return str.toUpperCase();
53 | }
54 | }
55 | }
56 |
57 | return gulp.src('src/hello.handlebars')
58 | .pipe(handlebars(templateData, options))
59 | .pipe(rename('hello.html'))
60 | .pipe(gulp.dest('dist'));
61 | });
62 | ```
63 |
64 | ### `dist/hello.html`
65 |
66 | ```html
67 | Header
68 | Hello Kaanon
69 | HELLO! KAANON
70 |
71 | ```
72 |
73 | ## Options
74 |
75 | - __ignorePartials__ : ignores any unknown partials. Useful if you only want to handle part of the file
76 | - __partials__ : Javascript object that will fill in partials using strings
77 | - __batch__ : Javascript array of filepaths to use as partials
78 | - __helpers__: javascript functions to stand in for helpers used in the handlebars files
79 | - __compile__: compile options. See [handlebars reference](http://handlebarsjs.com/reference.html#base-compile) for possible values
80 |
81 | ## handlebars.Handlebars
82 |
83 | You can access the Handlebars library from the `handlebars.Handlebars` property.
84 |
85 | ```js
86 | var handlebars = require('gulp-compile-handlebars');
87 | var safestring = new handlebars.Handlebars.SafeString('HELLO! KAANON');
88 | ```
89 |
90 | ## Works with gulp-data
91 |
92 | Use gulp-data to pass a data object to the template based on the handlebars file being processed.
93 | If you pass in template data this will be extended with the object from gulp-data.
94 |
95 | See [gulp-data](https://www.npmjs.org/package/gulp-data) for usage examples.
96 |
97 | ## License
98 |
99 | [MIT](http://opensource.org/licenses/MIT) © [Kaanon MacFarlane](http://kaanon.com)
100 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var assert = require('assert');
3 | var gutil = require('gulp-util');
4 | var template = require('../index');
5 |
6 | it('should compile Handlebars templates', function (cb) {
7 | var stream = template(
8 | {
9 | people: ['foo', 'bar'],
10 | message: 'BAZ'
11 | },
12 | {
13 | partials : { header : '' },
14 | helpers : { toLower : function(str) { return str.toLowerCase(); } }
15 | });
16 |
17 | stream.on('data', function (data) {
18 | assert.equal(data.contents.toString(), 'foobar baz');
19 | cb();
20 | });
21 |
22 | stream.write(new gutil.File({
23 | contents: new Buffer('{{> header}}{{#each people}}{{.}}{{/each}} {{toLower message}}')
24 | }));
25 |
26 | stream.end();
27 | });
28 |
29 | it('should compile Handlebars templates, and ignore unknown partials', function (cb) {
30 | var stream = template(
31 | {
32 | people: ['foo', 'bar'],
33 | message: 'BAZ'
34 | },
35 | {
36 | ignorePartials : true,
37 | helpers : { toLower : function(str) { return str.toLowerCase(); } }
38 | });
39 |
40 | stream.on('data', function (data) {
41 | assert.equal(data.contents.toString(), 'foobar baz');
42 | cb();
43 | });
44 |
45 | stream.write(new gutil.File({
46 | contents: new Buffer('{{> header}}{{#each people}}{{.}}{{/each}} {{toLower message}}')
47 | }));
48 |
49 | stream.end();
50 | });
51 |
52 | it('should compile Handlebars templates, and use batched partials', function (cb) {
53 | var stream = template({}, { batch: ['test/partials'] });
54 |
55 | stream.on('data', function (data) {
56 | assert.equal(data.contents.toString(), 'Header Goes Here');
57 | cb();
58 | });
59 |
60 | stream.write(new gutil.File({
61 | contents: new Buffer('{{> header-test}}')
62 | }));
63 |
64 | stream.end();
65 | });
66 |
67 | it('should compile Handlebars templates, and use batched NESTED partials', function (cb) {
68 | var stream = template({}, { batch: ['test/partials'] });
69 |
70 | stream.on('data', function (data) {
71 | assert.equal(data.contents.toString(), 'Mobile Header Goes Here');
72 | cb();
73 | });
74 |
75 | stream.write(new gutil.File({
76 | contents: new Buffer('{{> mobile/header-test}}')
77 | }));
78 |
79 | stream.end();
80 | });
81 |
82 | it('should compile Handlebars templates, and use multiple batched NESTED partials directories', function (cb) {
83 | var stream = template({}, { batch: ['test/partials/desktop', 'test/partials/mobile'] });
84 |
85 | stream.on('data', function (data) {
86 | assert.equal(data.contents.toString(), 'Desktop Header Goes Here');
87 | cb();
88 | });
89 |
90 | stream.write(new gutil.File({
91 | contents: new Buffer('{{> desktop/header-test}}')
92 | }));
93 |
94 | stream.end();
95 | });
96 |
97 |
98 | it('should compile Handlebars templates with no helpers or partials', function (cb) {
99 | var stream = template( {people: ['foo', 'bar']});
100 |
101 | stream.on('data', function (data) {
102 | assert.equal(data.contents.toString(), 'foobar');
103 | cb();
104 | });
105 |
106 | stream.write(new gutil.File({
107 | contents: new Buffer('{{#each people}}{{.}}{{/each}}')
108 | }));
109 |
110 | stream.end();
111 | });
112 |
113 |
114 | it('should use file.data if available', function (cb) {
115 | var stream = template({ foo: 'foo', bar: 'bar' });
116 |
117 | stream.on('data', function (data) {
118 | assert.equal(data.contents.toString(), 'foo BAZ
');
119 | cb();
120 | });
121 |
122 | var file = new gutil.File({
123 | contents: new Buffer('{{foo}} {{bar}}
')
124 | });
125 | file.data = { bar: 'BAZ' };
126 |
127 | stream.write(file);
128 |
129 | stream.end();
130 |
131 | });
132 |
133 | it('should not require a default data object', function (cb) {
134 | var stream = template();
135 |
136 | stream.on('data', function (data) {
137 | assert.equal(data.contents.toString(), 'BAZ
');
138 | cb();
139 | });
140 |
141 | var file = new gutil.File({
142 | contents: new Buffer('{{foo}}
')
143 | });
144 | file.data = { foo: 'BAZ' };
145 |
146 | stream.write(file);
147 |
148 | stream.end();
149 |
150 | });
151 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var gutil = require('gulp-util');
2 | var through = require('through2');
3 | var Handlebars = require('handlebars');
4 | var fs = require('fs');
5 | var extend = require('util')._extend;
6 | var path = require('path');
7 |
8 | function handlebars(data, opts) {
9 |
10 | var options = opts || {};
11 | var hb = handlebars.Handlebars;
12 |
13 | //Go through a partials object
14 | if(options.partials){
15 | for(var p in options.partials){
16 | hb.registerPartial(p, options.partials[p]);
17 | }
18 | }
19 | //Go through a helpers object
20 | if(options.helpers){
21 | for(var h in options.helpers){
22 | hb.registerHelper(h, options.helpers[h]);
23 | }
24 | }
25 |
26 | // Do not search for more than 10 nestings
27 | var maxDepth = 10;
28 | // Process only files with given extension names
29 | var allowedExtensions = ['hb', 'hbs', 'handlebars', 'html'];
30 |
31 | var isDir = function (filename) {
32 | var stats = fs.statSync(filename);
33 | return stats && stats.isDirectory();
34 | };
35 |
36 | var isHandlebars = function (filename) {
37 | return allowedExtensions.indexOf(filename.split('.').pop()) !== -1;
38 | };
39 |
40 | var partialName = function (filename, base) {
41 | var name = path.join(path.dirname(filename), path.basename(filename, path.extname(filename)));
42 | if (name.indexOf(base) === 0) {
43 | name = name.slice(base.length);
44 | }
45 | // Change the name of the partial to use / in the partial name, not \
46 | name = name.replace(/\\/g, '/');
47 |
48 | // Remove leading _ and / character
49 | var firstChar = name.charAt(0);
50 | if( firstChar === '_' || firstChar === '/' ){
51 | name = name.substring(1);
52 | }
53 |
54 | return name;
55 | };
56 |
57 | var registerPartial = function (filename, base) {
58 | if (!isHandlebars(filename)) { return; }
59 | var name = partialName(filename, base);
60 | var template = fs.readFileSync(filename, 'utf8');
61 |
62 | hb.registerPartial(name, template);
63 | };
64 |
65 | var registerPartials = function (dir, base, depth) {
66 | if (depth > maxDepth) { return; }
67 | base = base || dir;
68 | fs.readdirSync(dir).forEach(function (basename) {
69 | var filename = path.join(dir, basename);
70 | if (isDir(filename)) {
71 | registerPartials(filename, base);
72 | } else {
73 | registerPartial(filename, base);
74 | }
75 | });
76 | };
77 |
78 | // Go through a partials directory array
79 | if(options.batch){
80 | // Allow single string
81 | if(typeof options.batch === 'string') options.batch = [options.batch];
82 |
83 | options.batch.forEach(function (dir) {
84 | dir = path.normalize(dir);
85 | registerPartials(dir, dir, 0);
86 | });
87 | }
88 |
89 | /**
90 | * For handling unknown partials
91 | * @method mockPartials
92 | * @param {string} content Contents of handlebars file
93 | */
94 | var mockPartials = function(content){
95 | var regex = /{{> (.*)}}/gim, match, partial;
96 | if(content.match(regex)){
97 | while((match = regex.exec(content)) !== null){
98 | partial = match[1];
99 | //Only register an empty partial if the partial has not already been registered
100 | if(!hb.partials.hasOwnProperty(partial)){
101 | hb.registerPartial(partial, '');
102 | }
103 | }
104 | }
105 | };
106 |
107 |
108 | return through.obj(function (file, enc, cb) {
109 | var _data = extend({}, data);
110 |
111 | if (file.isNull()) {
112 | this.push(file);
113 | return cb();
114 | }
115 |
116 | if (file.isStream()) {
117 | this.emit('error', new gutil.PluginError('gulp-compile-handlebars', 'Streaming not supported'));
118 | return cb();
119 | }
120 |
121 | try {
122 | var fileContents = file.contents.toString();
123 | if(options.ignorePartials){
124 | mockPartials(fileContents);
125 | }
126 |
127 | // Enable gulp-data usage, Extend default data with data from file.data
128 | if(file.data){
129 | _data = extend(_data, file.data);
130 | }
131 | var template = hb.compile(fileContents, options.compile);
132 | file.contents = new Buffer(template(_data));
133 | } catch (err) {
134 | this.emit('error', new gutil.PluginError('gulp-compile-handlebars', err));
135 | }
136 |
137 | this.push(file);
138 | cb();
139 | });
140 | }
141 |
142 | handlebars.reset = function(){
143 | // Expose the Handlebars object
144 | handlebars.Handlebars = Handlebars.create();
145 | }
146 |
147 | handlebars.reset();
148 |
149 | module.exports = handlebars;
150 |
--------------------------------------------------------------------------------