├── .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(), '
  • foo
  • bar
  • 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(), '
  • foo
  • bar
  • 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(), '
  • foo
  • bar
  • '); 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 | --------------------------------------------------------------------------------