├── 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 | [](https://travis-ci.org/TakenPilot/gulp-static-handlebars)
7 |
8 | [](https://codeclimate.com/github/TakenPilot/gulp-static-handlebars)
9 |
10 | [](https://coveralls.io/r/TakenPilot/gulp-static-handlebars?branch=master)
11 |
12 | [](https://david-dm.org/TakenPilot/gulp-static-handlebars.svg?style=flat)
13 |
14 | [](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!!
\nHELPER
\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';
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!!
\npartial 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!!
\nimmediate 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!!
\nimmediate 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!
\nImported Helper
\nImported 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\nImported 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 | });
--------------------------------------------------------------------------------