├── test ├── mocha.opts ├── fixtures │ ├── test-data.html │ ├── partials │ │ └── test.hbs │ ├── test-data-with-partial.html │ ├── helpers │ │ ├── helper-function-export.js │ │ └── helper-object-export.js │ └── test-data-with-helper.html └── test.js ├── .gitignore ├── .travis.yml ├── package.json ├── readme.md └── index.js /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require blanket -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | coverage.html -------------------------------------------------------------------------------- /test/fixtures/test-data.html: -------------------------------------------------------------------------------- 1 |
{{contents}}
-------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" -------------------------------------------------------------------------------- /test/fixtures/partials/test.hbs: -------------------------------------------------------------------------------- 1 | partial 1 {{contents}} -------------------------------------------------------------------------------- /test/fixtures/test-data-with-partial.html: -------------------------------------------------------------------------------- 1 |
{{contents}}
2 |
{{> test}}
-------------------------------------------------------------------------------- /test/fixtures/helpers/helper-function-export.js: -------------------------------------------------------------------------------- 1 | module.exports = function (options) { 2 | return 'Imported Single Helper'; 3 | }; -------------------------------------------------------------------------------- /test/fixtures/helpers/helper-object-export.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "test": function (options) { 3 | return 'Imported Helper'; 4 | } 5 | }; -------------------------------------------------------------------------------- /test/fixtures/test-data-with-helper.html: -------------------------------------------------------------------------------- 1 |
{{contents}}
2 |
{{#test}}testHelper1{{/test}}
3 |
{{#helper-function-export}}testHelper2{{/helper-function-export}}
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-static-handlebars", 3 | "version": "1.0.10", 4 | "description": "Compile handlebars into static files, taking data, partials and helpers from outside sources like files, database, and json through callbacks and promises.", 5 | "main": "index.js", 6 | "repository": "http://github.com/TakenPilot/gulp-static-handlebars.git", 7 | "scripts": { 8 | "test": "./node_modules/.bin/mocha -R mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js" 9 | }, 10 | "config": { 11 | "blanket": { 12 | "pattern": "index.js", 13 | "data-cover-never": "node_modules" 14 | } 15 | }, 16 | "keywords": [ 17 | "gulpfriendly", 18 | "gulpplugin", 19 | "handlebars", 20 | "static", 21 | "pipe", 22 | "promise", 23 | "async" 24 | ], 25 | "author": "Dane Stuckel", 26 | "license": "ISC", 27 | "dependencies": { 28 | "bluebird": "^2.3.2", 29 | "gulp-util": "^3.0.1", 30 | "handlebars": "^2.0.0", 31 | "lodash": "^2.4.1", 32 | "through2": "^0.6.2" 33 | }, 34 | "devDependencies": { 35 | "blanket": "^1.1.6", 36 | "chai": "^1.9.1", 37 | "coveralls": "^2.11.2", 38 | "gulp": "^3.8.8", 39 | "gulp-rename": "^1.2.0", 40 | "mocha": "^1.21.4", 41 | "mocha-lcov-reporter": "0.0.1", 42 | "sinon": "^1.10.3", 43 | "vinyl": "^0.4.3", 44 | "vinyl-fs": "^0.3.11" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Gulp Static Handlebars 2 | ---------------------- 3 | 4 | Reads data, partials and helpers from asynchronous sources like a databases, file systems, or promises. 5 | 6 | [![Build Status](https://travis-ci.org/TakenPilot/gulp-static-handlebars.svg?branch=master)](https://travis-ci.org/TakenPilot/gulp-static-handlebars) 7 | 8 | [![Code Climate](https://codeclimate.com/github/TakenPilot/gulp-static-handlebars/badges/gpa.svg)](https://codeclimate.com/github/TakenPilot/gulp-static-handlebars) 9 | 10 | [![Coverage Status](https://img.shields.io/coveralls/TakenPilot/gulp-static-handlebars.svg)](https://coveralls.io/r/TakenPilot/gulp-static-handlebars?branch=master) 11 | 12 | [![Dependencies](https://david-dm.org/TakenPilot/gulp-static-handlebars.svg?style=flat)](https://david-dm.org/TakenPilot/gulp-static-handlebars.svg?style=flat) 13 | 14 | [![NPM version](https://badge.fury.io/js/gulp-static-handlebars.svg)](http://badge.fury.io/js/gulp-static-handlebars) 15 | 16 | ##Example with any A+ compatible promises library: 17 | 18 | ```JavaScript 19 | 20 | function getData() { 21 | return Promise.resolve({contents: 'whatever'}); 22 | } 23 | 24 | function getHelpers() { 25 | return Promise.resolve({menu: function(options) { return 'menu!'; }}); 26 | } 27 | 28 | function getPartials() { 29 | return Promise.resolve({header: '
', footer: ''}); 30 | } 31 | 32 | gulp.src('./app/index.hbs') 33 | .pipe(handlebars(getData(), {helpers: getHelpers(), partials: getPartials()})) 34 | .pipe(gulp.dest('./dist')); 35 | 36 | ``` 37 | 38 | ##Another example with vinyl pipes 39 | 40 | ```JavaScript 41 | 42 | gulp.src('./app/index.hbs') 43 | .pipe(handlebars({contents:"whatever"}, { 44 | helpers: gulp.src('./app/helpers/**/*.js'), 45 | partials: gulp.src('./app/partials/**/*.hbs') 46 | })) 47 | .pipe(gulp.dest('./dist')); 48 | 49 | ``` 50 | 51 | ##Install 52 | 53 | ```Sh 54 | 55 | npm install gulp-static-handlebars 56 | 57 | ``` 58 | 59 | ##Running Tests 60 | 61 | To run the basic tests, just run `mocha` normally. 62 | 63 | This assumes you've already installed the local npm packages with `npm install`. 64 | 65 | ##To Do: 66 | 67 | * Support more handlebars options 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var gutil = require('gulp-util'); 2 | var through = require('through2'); 3 | var Handlebars = require('handlebars'); 4 | var Promise = require('bluebird'); 5 | var _ = require('lodash'); 6 | var Path = require('path'); 7 | 8 | /** 9 | * Duck-typing to allow different promise implementations to work. 10 | */ 11 | function isPromise(obj) { 12 | return !!obj && _.isFunction(obj.then); 13 | } 14 | 15 | function isPipe(obj) { 16 | return !!obj && _.isFunction(obj.pipe); 17 | } 18 | 19 | function isFile(obj) { 20 | return !!obj && _.isFunction(obj.isStream) && _.isFunction(obj.isBuffer) && _.isString(obj.path); 21 | } 22 | 23 | function getNameFromPath(path) { 24 | return path.split(Path.sep).pop().split('.').shift(); 25 | } 26 | 27 | function getPromiseFromPipe(pipe, fn) { 28 | var d = Promise.defer(); 29 | pipe.pipe(through.obj(function (file, enc, cb) { 30 | var str = file.contents.toString(); 31 | fn(file, str); 32 | this.push(file); 33 | cb(); 34 | }, function () { 35 | //end 36 | d.resolve(); 37 | })); 38 | return d.promise; 39 | } 40 | 41 | function getPromises(obj, fn) { 42 | if (_.isPlainObject(obj)) { 43 | return _.map(obj, function (result, name) { 44 | if (isPromise(result)) { 45 | return result.then(function (partial) { 46 | fn(name, partial); 47 | return partial; 48 | }); 49 | } else if (_.isFunction(result)) { 50 | fn(name, result); 51 | return result; 52 | } 53 | return null; 54 | }); 55 | } else if (isPipe(obj)) { 56 | return [getPromiseFromPipe(obj, fn)]; 57 | } 58 | return []; 59 | } 60 | 61 | module.exports = function (data, options) { 62 | var dataDependencies; 63 | options = options || {}; 64 | var dependencies = []; 65 | //Go through a partials object 66 | 67 | if (data) { 68 | if (isPromise(data)) { 69 | dataDependencies = data.then(function (result) { 70 | data = result; 71 | return result; 72 | }); 73 | } else { 74 | dataDependencies = Promise.resolve(data); 75 | } 76 | dependencies.push(dataDependencies); 77 | } 78 | 79 | 80 | if (options.partials) { 81 | var partialDependencies = getPromises(options.partials, function (id, contents) { 82 | if (isFile(id)) { 83 | id = getNameFromPath(id.path); 84 | } 85 | Handlebars.registerPartial(id, contents); 86 | }); 87 | dependencies = dependencies.concat(partialDependencies); 88 | } 89 | //Go through a helpers object 90 | if (options.helpers) { 91 | var helperDependencies = getPromises(options.helpers, function (id, contents) { 92 | if (isFile(id)) { 93 | var helperFile = require(id.path); 94 | if (_.isFunction(helperFile)) { 95 | Handlebars.registerHelper(getNameFromPath(id.path), helperFile); 96 | } else if (_.isObject(helperFile)) { 97 | _.each(helperFile, function(value, key) { 98 | Handlebars.registerHelper(key, value); 99 | }); 100 | } 101 | } else if (_.isString(id) && _.isFunction(contents)) { 102 | id = id.toLowerCase(); 103 | Handlebars.registerHelper(id, contents); 104 | } 105 | }); 106 | dependencies = dependencies.concat(helperDependencies); 107 | } 108 | 109 | 110 | return through.obj(function (file, enc, callback) { 111 | var self = this; 112 | Promise.all(dependencies).then(function () { 113 | file.contents = new Buffer(Handlebars.compile(file.contents.toString())(data)); 114 | self.push(file); 115 | }.bind(this)).catch(function (err) { 116 | self.emit('error', new gutil.PluginError('gulp-static-handlebars', err)); 117 | }).finally(function () { 118 | callback(); 119 | }); 120 | }); 121 | }; 122 | 123 | module.exports.Handlebars = Handlebars; 124 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | handlebars = require('../.'), 3 | expect = require('chai').expect, 4 | gulp = require('gulp'), 5 | rename = require('gulp-rename'), 6 | File = require('vinyl'); 7 | 8 | describe('Gulp Static Handlebars', function () { 9 | 10 | afterEach(function () { 11 | handlebars.Handlebars.unregisterPartial('test'); 12 | handlebars.Handlebars.unregisterHelper('helper-function-export'); 13 | handlebars.Handlebars.unregisterHelper('test'); 14 | }); 15 | 16 | it('should load helpers', function (done) { 17 | //arrange 18 | var helper = function (options) { 19 | return 'HELPER'; 20 | }, 21 | expectedContents = '
contents!!
\n
HELPER
\n
'; 22 | var deferred = Promise.defer(); 23 | 24 | //act 25 | gulp.src('./test/fixtures/test-data-with-helper.html') 26 | .pipe(handlebars({contents: "contents!!"}, {helpers: {'test': deferred.promise}})) 27 | .on('data', function (data) { 28 | expect(data.contents.toString()).to.equal(expectedContents); 29 | done(); 30 | }); 31 | deferred.resolve(helper); 32 | }); 33 | 34 | it('should load partials', function (done) { 35 | //arrange 36 | var partial = '
Partial
', 37 | expectedContents = '
contents!!
\n
Partial
'; 38 | var deferred = Promise.defer(); 39 | 40 | //act 41 | gulp.src('./test/fixtures/test-data-with-partial.html') 42 | .pipe(handlebars({contents: "contents!!"}, {partials: {'test': deferred.promise}})) 43 | .on('data', function (data) { 44 | expect(data.contents.toString()).to.equal(expectedContents); 45 | done(); 46 | }); 47 | deferred.resolve(partial); 48 | }); 49 | 50 | it('should load data if promise', function (done) { 51 | //arrange 52 | var expectedContents = '
Some dynamic content
'; 53 | var deferred = Promise.defer(); 54 | 55 | //act 56 | gulp.src('./test/fixtures/test-data.html') 57 | .pipe(handlebars(deferred.promise)) 58 | .on('data', function (data) { 59 | expect(data.contents.toString()).to.equal(expectedContents); 60 | done(); 61 | }); 62 | deferred.resolve({contents: "Some dynamic content"}); 63 | }); 64 | 65 | it('should load partials from pipe if available', function (done) { 66 | //arrange 67 | var expectedContents = '
contents!!
\n
partial 1 contents!!
'; 68 | 69 | //act 70 | gulp.src('./test/fixtures/test-data-with-partial.html') 71 | .pipe(handlebars({contents: "contents!!"}, {partials: gulp.src('./test/fixtures/partials/**/*')})) 72 | .on('data', function (data) { 73 | expect(data.contents.toString()).to.equal(expectedContents); 74 | done(); 75 | }); 76 | }); 77 | 78 | it('should load partials from data from inline functions', function (done) { 79 | //arrange 80 | var expectedContents = '
contents!!
\n
immediate data
'; 81 | 82 | //act 83 | gulp.src('./test/fixtures/test-data-with-partial.html') 84 | .pipe(handlebars({contents: "contents!!"}, {partials: { 85 | 'test': function () { return 'immediate data'; } 86 | }})) 87 | .on('data', function (data) { 88 | expect(data.contents.toString()).to.equal(expectedContents); 89 | done(); 90 | }); 91 | }); 92 | 93 | it('should load partials from data from mapped promises', function (done) { 94 | //arrange 95 | var expectedContents = '
contents!!
\n
immediate data
'; 96 | 97 | //act 98 | gulp.src('./test/fixtures/test-data-with-partial.html') 99 | .pipe(handlebars({contents: "contents!!"}, {partials: { 100 | 'test': Promise.delay('immediate data', 50) 101 | }})) 102 | .on('data', function (data) { 103 | expect(data.contents.toString()).to.equal(expectedContents); 104 | done(); 105 | }); 106 | }); 107 | 108 | it('should not load partials from plain mapped strings (must have scope)', function (done) { 109 | gulp.src('./test/fixtures/test-data-with-partial.html') 110 | .pipe(handlebars({contents: "contents!!"}, {partials: { 111 | 'test': 'things' 112 | }})) 113 | .on('error', function () { 114 | done(); 115 | }); 116 | }); 117 | 118 | it('should not load partials from arrays (must have a name for each partial)', function (done) { 119 | gulp.src('./test/fixtures/test-data-with-partial.html') 120 | .pipe(handlebars({contents: "contents!!"}, {partials: ['things']})) 121 | .on('error', function () { 122 | done(); 123 | }); 124 | }); 125 | 126 | it('should ignore null files as data', function (done) { 127 | var f = new File(); 128 | gulp.src('./test/fixtures/test-data.html') 129 | .pipe(handlebars(f)) 130 | .on('data', function () {}) 131 | .on('end', function () { 132 | done(); 133 | }); 134 | }); 135 | 136 | it('should ignore null files as partials', function (done) { 137 | var f = new File({ 138 | cwd: "/", 139 | base: "/test/", 140 | path: "/test/whatever", 141 | contents: null 142 | }); 143 | gulp.src('./test/fixtures/test-data.html') 144 | .pipe(handlebars({contents: "contents!!"}, {partials: f})) 145 | .on('data', function () {}) 146 | .on('end', function () { 147 | done(); 148 | }); 149 | }); 150 | 151 | it('should ignore null files as helpers', function (done) { 152 | var f = new File({ 153 | cwd: "/", 154 | base: "/test/", 155 | path: "/test/whatever", 156 | contents: null 157 | }); 158 | gulp.src('./test/fixtures/test-data.html') 159 | .pipe(handlebars({contents: "contents!!"}, {helpers: f})) 160 | .on('data', function () {}) 161 | .on('end', function () { 162 | done(); 163 | }); 164 | }); 165 | 166 | it('should load helpers from pipe if available', function (done) { 167 | //arrange 168 | var expectedContents = '
contents!
\n
Imported Helper
\n
Imported Single Helper
'; 169 | 170 | //act 171 | gulp.src('./test/fixtures/test-data-with-helper.html') 172 | .pipe(handlebars({contents: "contents!"}, {helpers: gulp.src('./test/fixtures/helpers/**/*')})) 173 | .on('data', function (data) { 174 | expect(data.contents.toString()).to.equal(expectedContents); 175 | done(); 176 | }); 177 | }); 178 | 179 | it('should not fail on no partial files', function (done) { 180 | //arrange 181 | var expectedContents = '
contents!
'; 182 | 183 | //act 184 | gulp.src('./test/fixtures/test-data.html') 185 | //even though no partials are found, the template doesn't use partials --> no failure. 186 | .pipe(handlebars({contents: "contents!"}, {partials: gulp.src('./test/fixtures/something/**/*')})) 187 | .on('data', function (data) { 188 | expect(data.contents.toString()).to.equal(expectedContents); 189 | }).on('end', function () { 190 | done(); 191 | }); 192 | }); 193 | 194 | it('should not fail on no helper files', function (done) { 195 | //arrange 196 | var expectedContents = '
contents!
'; 197 | 198 | //act 199 | gulp.src('./test/fixtures/test-data.html') 200 | //even though no helpers are found, the template doesn't use helpers --> no failure. 201 | .pipe(handlebars({contents: "contents!"}, {helpers: gulp.src('./test/fixtures/something/**/*')})) 202 | .on('data', function (data) { 203 | expect(data.contents.toString()).to.equal(expectedContents); 204 | }).on('end', function () { 205 | done(); 206 | }); 207 | }); 208 | 209 | it('can load helper file exporting single function using filename as name of helper', function (done) { 210 | //arrange 211 | var expectedContents = '
contents!
\n
\n
Imported Single Helper
'; 212 | 213 | //act 214 | gulp.src('./test/fixtures/test-data-with-helper.html') 215 | .pipe(handlebars({contents: "contents!"}, {helpers: gulp.src('./test/fixtures/helpers/helper-function-export.js')})) 216 | .on('data', function (data) { 217 | expect(data.contents.toString()).to.equal(expectedContents); 218 | }).on('end', function () { 219 | done(); 220 | }); 221 | }); 222 | 223 | it('can rename files afterward', function (done) { 224 | //arrange 225 | var expectedContents = '
contents!
'; 226 | 227 | //act 228 | gulp.src('./test/fixtures/test-data.html') 229 | .pipe(handlebars({contents: "contents!"})) 230 | .pipe(rename('./test/test/test')) 231 | .on('data', function (data) { 232 | expect(data.contents.toString()).to.equal(expectedContents); 233 | }).on('end', function () { 234 | done(); 235 | }); 236 | }); 237 | }); --------------------------------------------------------------------------------