diff --git a/examples/full/bundle.config.js b/examples/full/bundle.config.js index a1fc7125..9a28903d 100644 --- a/examples/full/bundle.config.js +++ b/examples/full/bundle.config.js @@ -135,7 +135,11 @@ module.exports = { copy: [ { src: './bower_components/bootstrap/dist/fonts/**/*.*', - base: './bower_components/bootstrap/dist/' + base: './bower_components/bootstrap/dist/', + watch: false + }, + { + src: './partials/**/*.*' }, './images/**/*.*' ] diff --git a/examples/full/partials/a-partial-file.html b/examples/full/partials/a-partial-file.html new file mode 100644 index 00000000..750a2ff4 --- /dev/null +++ b/examples/full/partials/a-partial-file.html @@ -0,0 +1,3 @@ +
+ yo +
\ No newline at end of file diff --git a/examples/full/public/partials/a-partial-file.html b/examples/full/public/partials/a-partial-file.html new file mode 100644 index 00000000..750a2ff4 --- /dev/null +++ b/examples/full/public/partials/a-partial-file.html @@ -0,0 +1,3 @@ +
+ yo +
\ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 6744a282..160446af 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -57,7 +57,7 @@ gulp.task('test-cover', 'Unit tests and coverage', function (cb) { thresholds : { statements : 95, branches : 92, - functions : 95, + functions : 94, lines : 95 }, coverageDirectory : 'coverage', diff --git a/lib/service/logger.js b/lib/service/logger.js index e87adc21..ad3f107d 100644 --- a/lib/service/logger.js +++ b/lib/service/logger.js @@ -7,6 +7,7 @@ function Logger() { Logger.prototype.log = function () { var config = cache.get('config'); if (!config || !config.options || config.options.quietMode !== true) { + // replace with gulp logger once it's done? https://github.com/gulpjs/gulp-util/issues/33 gutil.log.apply(gutil.log, arguments); } }; diff --git a/lib/stream-bundles-util.js b/lib/stream-bundles-util.js new file mode 100644 index 00000000..1004a2a1 --- /dev/null +++ b/lib/stream-bundles-util.js @@ -0,0 +1,16 @@ +var logger = require('./service/logger'), + gutil = require('gulp-util'), + warnPrefix = gutil.colors.bgYellow.black('WARN'); + +function StreamBundlesUtil() { +} + +StreamBundlesUtil.prototype.warnIfNoBundleProperty = function (config) { + if (config && !config.bundle && config.file && config.file.relative) { // can guarantee !!file b/c (config instanceof Config) + logger.log(warnPrefix, "No '" + gutil.colors.cyan('bundle') + + "' property found in " + gutil.colors.magenta(config.file.relative) + ". Did you mean to define one?"); + } +}; + +// naturally a singleton because node's require caches the value assigned to module.exports +module.exports = new StreamBundlesUtil(); \ No newline at end of file diff --git a/lib/stream-bundles-watch.js b/lib/stream-bundles-watch.js index c9939105..c885fdbd 100644 --- a/lib/stream-bundles-watch.js +++ b/lib/stream-bundles-watch.js @@ -14,7 +14,9 @@ var through = require('through2'), bundleDone = require('./watch/bundle-done'), initOptionDefaults = require('./init-option-defaults'), results = require('./results').incremental, - streamFiles = require('./stream-files'); + streamFiles = require('./stream-files'), + streamCopy = require('./stream-copy'), + streamBundlesUtil = require('./stream-bundles-util'); function _bundle(config, env) { var bundles = config.bundle, @@ -126,15 +128,61 @@ function _bundle(config, env) { } +function watchStringCopyStream(config, item, base) { + gulp.watch(item) + .on('change', function (file) { // log changed file? + streamCopy.getStringCopyStream(item, base) + .pipe(gulp.dest(config.options.dest)); + }); +} + +function watchObjectCopyStream(config, item, base) { + var watchPath = pathifySrc(item.src, base); + gulp.watch(watchPath) + .on('change', function (file) { // log changed file? + streamCopy.getObjectCopyStream(item, base) + .pipe(gulp.dest(config.options.dest)); + }); +} + +function _copy(config) { + var base = (config.options) ? config.options.base : '.'; // can guarantee !!options b/c (config instanceof Config) + + logger.log("Starting '" + gutil.colors.cyan("watch") + "' for files to copy..."); + + if (typeof config.copy === 'string') { + watchStringCopyStream(config, config.copy, base); + } else if (util.isArray(config.copy)) { + _.forEach(config.copy, function (item) { + if (typeof item === 'string') { + return watchStringCopyStream(config, item, base); + } else if (typeof item === 'object' && !util.isArray(item) && + config.copy.watch !== false) { + return watchObjectCopyStream(config, item, base); + } + streamCopy.throwUnsupportedSyntaxError(); + }); + } else if (typeof config.copy === 'object' && config.copy.watch !== false) { + watchObjectCopyStream(config, config.copy, base); + } else { + streamCopy.throwUnsupportedSyntaxError(); + } +} + function bundle(config) { + if (config.bundle) { if (config.options && config.options.bundleAllEnvironments) { // can guarantee !!options b/c (config instanceof Config) bundleAllEnvironments(config, _bundle); } else { _bundle(config, process.env.NODE_ENV); } - } else { - throw new gutil.PluginError('gulp-bundle-assets', 'Missing required config property "bundle"'); + } + + streamBundlesUtil.warnIfNoBundleProperty(config); + + if (config.copy) { + _copy(config); } } diff --git a/lib/stream-bundles.js b/lib/stream-bundles.js index 13b4c906..d1b23cb1 100644 --- a/lib/stream-bundles.js +++ b/lib/stream-bundles.js @@ -1,8 +1,5 @@ -var path = require('path'), - util = require('util'), +var util = require('util'), BundleKeys = require('./model/bundle-keys'), - gulp = require('gulp'), - using = require('./using'), gutil = require('gulp-util'), logger = require('./service/logger'), _ = require('lodash'), @@ -11,46 +8,8 @@ var path = require('path'), bundleAllEnvironments = require('./bundle-all-environments'), initOptionDefaults = require('./init-option-defaults'), streamFiles = require('./stream-files'), - warnPrefix = gutil.colors.bgYellow.black('WARN'); - -// assume configBase will ALWAYS be defined (and defaulted to '.') -function getCustomBase(configBase, relativeBase) { - if (!relativeBase) { - return configBase; - } - return path.join(configBase, relativeBase); -} - -/** - * - * @param {String} item - * @param base - * @returns {*} - */ -function getStringCopyStream(item, base) { - return gulp.src(pathifySrc(item, base), { base: base }) - .pipe(using.copy(base)); -} - -/** - * @param {Object} item - * @param base - * @returns {*} - */ -function getObjectCopyStream(item, base) { - return gulp.src(pathifySrc(item.src, base), { base: getCustomBase(base, item.base) }) - .pipe(using.copy(base)); -} - -function getCopyStream(item, base) { - if (typeof item === 'string') { - return getStringCopyStream(item, base); - } else if (typeof item === 'object' && !util.isArray(item)) { - return getObjectCopyStream(item, base); - } - throw new gutil.PluginError('gulp-bundle-assets', 'Unsupported syntax for copy. See here for supported variations: ' + - 'https://github.com/chmontgomery/gulp-bundle-assets/blob/master/examples/copy/bundle.config.js'); -} + streamCopy = require('./stream-copy'), + streamBundlesUtil = require('./stream-bundles-util'); function _bundle(config, env) { var streams = [], @@ -98,38 +57,42 @@ function _bundle(config, env) { return streams; } - -function bundle(config) { +function _copy(config) { var streams = [], copy = config.copy, base = (config.options) ? config.options.base : '.'; // can guarantee !!options b/c (config instanceof Config) + if (typeof copy === 'string') { + streams.push(streamCopy.getStringCopyStream(copy, base)); + } else if (util.isArray(copy)) { + _.forEach(copy, function (item) { + streams.push(streamCopy.getCopyStream(item, base)); + }); + } else if (typeof copy === 'object') { + streams.push(streamCopy.getObjectCopyStream(copy, base)); + } else { + streamCopy.throwUnsupportedSyntaxError(); + } + + return streams; +} + + +function bundle(config) { + var streams = []; + if (config.bundle) { if (config.options && config.options.bundleAllEnvironments) { // can guarantee !!options b/c (config instanceof Config) streams = streams.concat(bundleAllEnvironments(config, _bundle)); } else { streams = streams.concat(_bundle(config, process.env.NODE_ENV)); } - } else if (config && config.file && config.file.relative) { // can guarantee !!file b/c (config instanceof Config) - // replace with gulp logger once they're done with it https://github.com/gulpjs/gulp-util/issues/33 - logger.log(warnPrefix, "No '" + gutil.colors.cyan('bundle') + - "' property found in " + gutil.colors.magenta(config.file.relative) + ". Did you mean to define one?"); } - if (copy) { - - if (typeof copy === 'string') { - streams.push(getStringCopyStream(copy, base)); - } else if (util.isArray(copy)) { - _.forEach(copy, function (item) { - streams.push(getCopyStream(item, base)); - }); - } else if (typeof copy === 'object') { - streams.push(getObjectCopyStream(copy, base)); - } else { - throw new gutil.PluginError('gulp-bundle-assets', 'Unsupported syntax for copy. Should be a string, array or object.'); - } + streamBundlesUtil.warnIfNoBundleProperty(config); + if (config.copy) { + streams = streams.concat(_copy(config)); } return streams; diff --git a/lib/stream-copy.js b/lib/stream-copy.js new file mode 100644 index 00000000..9c22fe5a --- /dev/null +++ b/lib/stream-copy.js @@ -0,0 +1,54 @@ +var gulp = require('gulp'), + gutil = require('gulp-util'), + util = require('util'), + path = require('path'), + using = require('./using'), + pathifySrc = require('./pathify-config-src'); + +function StreamCopy() { +} + +// assume configBase will ALWAYS be defined (and defaulted to '.') +StreamCopy.prototype.getCustomBase = function (configBase, relativeBase) { + if (!relativeBase) { + return configBase; + } + return path.join(configBase, relativeBase); +}; + +/** + * @param {String} item + * @param base + * @returns {*} + */ +StreamCopy.prototype.getStringCopyStream = function (item, base) { + return gulp.src(pathifySrc(item, base), { base: base }) + .pipe(using.copy(base)); +}; + +/** + * @param {Object} item + * @param base + * @returns {*} + */ +StreamCopy.prototype.getObjectCopyStream = function (item, base) { + return gulp.src(pathifySrc(item.src, base), { base: this.getCustomBase(base, item.base) }) + .pipe(using.copy(base)); +}; + +StreamCopy.prototype.getCopyStream = function (item, base) { + if (typeof item === 'string') { + return this.getStringCopyStream(item, base); + } else if (typeof item === 'object' && !util.isArray(item)) { + return this.getObjectCopyStream(item, base); + } + this.throwUnsupportedSyntaxError(); +}; + +StreamCopy.prototype.throwUnsupportedSyntaxError = function () { + throw new gutil.PluginError('gulp-bundle-assets', 'Unsupported syntax for copy. See here for supported variations: ' + + 'https://github.com/chmontgomery/gulp-bundle-assets/blob/master/examples/copy/bundle.config.js'); +}; + +// naturally a singleton because node's require caches the value assigned to module.exports +module.exports = new StreamCopy(); \ No newline at end of file diff --git a/test/integ/index-test.js b/test/integ/index-test.js index b49f9f2d..6b24aa6b 100644 --- a/test/integ/index-test.js +++ b/test/integ/index-test.js @@ -427,15 +427,16 @@ describe('integration tests', function () { file.relative === 'fonts/glyphicons-halflings-regular.ttf' || file.relative === 'fonts/glyphicons-halflings-regular.woff' || file.relative === 'images/empire_icon.png' || - file.relative === 'images/rebel_icon.png') { + file.relative === 'images/rebel_icon.png' || + file.relative === 'partials/a-partial-file.html' ) { staticFileCount++; } else { helpers.errorUnexpectedFileInStream(file); } fileCount++; }, function () { - (fileCount).should.eql(22); - (staticFileCount).should.eql(14); + (fileCount).should.eql(23); + (staticFileCount).should.eql(15); }); }); diff --git a/test/unit/stream-bundles-watch-test.js b/test/unit/stream-bundles-watch-test.js index 24cd7b2f..8e688a2b 100644 --- a/test/unit/stream-bundles-watch-test.js +++ b/test/unit/stream-bundles-watch-test.js @@ -78,4 +78,159 @@ describe('stream-bundles-watch', function () { }); + it('should start watch for copy files string', function (done) { + + watchReturn.on = function (eventName, fn) { + eventName.should.eql('change'); + fn.should.be.type('function'); + }; + sinon.spy(watchReturn, 'on'); + gulpStub.watch = sinon.stub().returns(watchReturn); + streamBundlesWatch = proxyquire(libPath + '/stream-bundles-watch', + { 'gulp': gulpStub }); + + var config = new ConfigModel({ + copy: 'some/file' + }, {}); + + streamBundlesWatch(config); + + gulpStub.watch.calledOnce.should.be.ok; + watchReturn.on.calledOnce.should.be.ok; + + done(); + + }); + + it('should start watch for copy files array', function (done) { + + watchReturn.on = function (eventName, fn) { + eventName.should.eql('change'); + fn.should.be.type('function'); + }; + sinon.spy(watchReturn, 'on'); + gulpStub.watch = sinon.stub().returns(watchReturn); + streamBundlesWatch = proxyquire(libPath + '/stream-bundles-watch', + { 'gulp': gulpStub }); + + var config = new ConfigModel({ + copy: [ + 'some/file', + 'some/file2' + ] + }, {}); + + streamBundlesWatch(config); + + gulpStub.watch.calledTwice.should.be.ok; + watchReturn.on.calledTwice.should.be.ok; + + done(); + + }); + + it('should start watch for copy files object', function (done) { + + watchReturn.on = function (eventName, fn) { + eventName.should.eql('change'); + fn.should.be.type('function'); + }; + sinon.spy(watchReturn, 'on'); + gulpStub.watch = sinon.stub().returns(watchReturn); + streamBundlesWatch = proxyquire(libPath + '/stream-bundles-watch', + { 'gulp': gulpStub }); + + var config = new ConfigModel({ + copy: { + src: 'some/file', + base: 'some' + } + }, {}); + + streamBundlesWatch(config); + + gulpStub.watch.calledOnce.should.be.ok; + watchReturn.on.calledOnce.should.be.ok; + + done(); + + }); + + it('should start watch for copy files object in array', function (done) { + + watchReturn.on = function (eventName, fn) { + eventName.should.eql('change'); + fn.should.be.type('function'); + }; + sinon.spy(watchReturn, 'on'); + gulpStub.watch = sinon.stub().returns(watchReturn); + streamBundlesWatch = proxyquire(libPath + '/stream-bundles-watch', + { 'gulp': gulpStub }); + + var config = new ConfigModel({ + copy: [ + { + src: 'some/file', + base: 'some' + } + ] + }, {}); + + streamBundlesWatch(config); + + gulpStub.watch.calledOnce.should.be.ok; + watchReturn.on.calledOnce.should.be.ok; + + done(); + + }); + + it('should throw error for invalid copy property value type', function (done) { + + watchReturn.on = function (eventName, fn) { + eventName.should.eql('change'); + fn.should.be.type('function'); + }; + sinon.spy(watchReturn, 'on'); + gulpStub.watch = sinon.stub().returns(watchReturn); + streamBundlesWatch = proxyquire(libPath + '/stream-bundles-watch', + { 'gulp': gulpStub }); + + var config = new ConfigModel({ + copy: true + }, {}); + + (function () { + streamBundlesWatch(config); + }).should.throw(/^Unsupported syntax for copy./); + + done(); + + }); + + it('should throw error for invalid nested copy property value type', function (done) { + + watchReturn.on = function (eventName, fn) { + eventName.should.eql('change'); + fn.should.be.type('function'); + }; + sinon.spy(watchReturn, 'on'); + gulpStub.watch = sinon.stub().returns(watchReturn); + streamBundlesWatch = proxyquire(libPath + '/stream-bundles-watch', + { 'gulp': gulpStub }); + + var config = new ConfigModel({ + copy: [ + true + ] + }, {}); + + (function () { + streamBundlesWatch(config); + }).should.throw(/^Unsupported syntax for copy./); + + done(); + + }); + });