From f111acad1084ed37407364cd0450fc38043b104f Mon Sep 17 00:00:00 2001 From: Tim B Date: Thu, 19 Jan 2017 19:07:22 +0100 Subject: [PATCH 1/4] add support for an extra option: dependencyMap `dependencyMap` may contain an object, giving a map of dependencies for any input file it has the same effect as the `extra` option, but allows to set the dependencies per input file, as opposed to globally --- index.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 3ad16c7..64ed3eb 100644 --- a/index.js +++ b/index.js @@ -45,6 +45,12 @@ function Newer(options) { } } + if (options.dependencyMap) { + if (typeof options.dependencyMap !== 'object') { + throw new PluginError(PLUGIN_NAME, 'Requires options.dependencyMap to be an object of string to array of strings'); + } + } + /** * Path to destination directory or file. * @type {string} @@ -94,6 +100,45 @@ function Newer(options) { */ this._extraStats = null; + /** + * Changing any dependencies of a file should force the file itself to be + * rebuilt. + * @type Promise({[string]: {mtime: int}})? + */ + this._dependencyMapStats = null; + + if (options.dependencyMap) { + const depMapStats = {}; + for (let key in options.dependencyMap) { + const deps = options.dependencyMap[key]; + const depsStats = deps.map(fn => Q.nfcall(fs.stat, fn).fail(e => null)); + depMapStats[key] = Q.all(depsStats) + .then(function(resolvedStats) { + return resolvedStats.reduce((a, b) => { + if (!a) return b; + if (!b) return a; + return a.mtime > b.mtime ? a : b; + }); + }) + .fail(err => { + // Ignore missing files, deps might have changed. + }); + } + this._dependencyMapStats = Q.all(Object.values(depMapStats)) + // Resolve to depMapStats' promises' data. + .then(_ => depMapStats) + .then(promiseMap => { + const map = {}; + const setValue = key => value => { + map[key] = value; + }; + for (let key in promiseMap) { + promiseMap[key].then(setValue(key)); + } + return map; + }); + } + if (options.extra) { var extraFiles = []; for (var i = 0; i < options.extra.length; ++i) { @@ -148,8 +193,8 @@ Newer.prototype._transform = function(srcFile, encoding, done) { return; } var self = this; - Q.resolve([this._destStats, this._extraStats]) - .spread(function(destStats, extraStats) { + Q.resolve([this._destStats, this._extraStats, this._dependencyMapStats]) + .spread(function(destStats, extraStats, depMapStats) { if ((destStats && destStats.isDirectory()) || self._ext || self._map) { // stat dest/relative file var relative = srcFile.relative; @@ -162,29 +207,32 @@ Newer.prototype._transform = function(srcFile, encoding, done) { } var destFileJoined = self._dest ? path.join(self._dest, destFileRelative) : destFileRelative; - return Q.all([Q.nfcall(fs.stat, destFileJoined), extraStats]); + return Q.all([Q.nfcall(fs.stat, destFileJoined), extraStats, depMapStats]); } else { // wait to see if any are newer, then pass through all if (!self._bufferedFiles) { self._bufferedFiles = []; } - return [destStats, extraStats]; + return [destStats, extraStats, depMapStats]; } }).fail(function(err) { if (err.code === 'ENOENT') { // dest file or directory doesn't exist, pass through all - return Q.resolve([null, this._extraStats]); + return Q.resolve([null, this._extraStats, this._dependencyMapStats]); } else { // unexpected error return Q.reject(err); } - }).spread(function(destFileStats, extraFileStats) { + }).spread(function(destFileStats, extraFileStats, depMapStats) { var newer = !destFileStats || srcFile.stat.mtime > destFileStats.mtime; // If *any* extra file is newer than a destination file, then ALL // are newer. if (extraFileStats && extraFileStats.mtime > destFileStats.mtime) { newer = true; } + if (depMapStats && depMapStats[srcFile.path] && depMapStats[srcFile.path].mtime > destFileStats.mtime) { + newer = true; + } if (self._all) { self.push(srcFile); } else if (!newer) { From cc6ed42e66c168e957e69aa5b62ac7097a2be92e Mon Sep 17 00:00:00 2001 From: Tim B Date: Fri, 20 Jan 2017 12:21:39 +0100 Subject: [PATCH 2/4] compatibility fix for node v6 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 64ed3eb..f48f822 100644 --- a/index.js +++ b/index.js @@ -124,7 +124,7 @@ function Newer(options) { // Ignore missing files, deps might have changed. }); } - this._dependencyMapStats = Q.all(Object.values(depMapStats)) + this._dependencyMapStats = Q.all(Object.keys(depMapStats).map(k => depMapStats[k])) // Resolve to depMapStats' promises' data. .then(_ => depMapStats) .then(promiseMap => { From 8775dc55f002550e05acb8c73ad86fb585b43d35 Mon Sep 17 00:00:00 2001 From: Tim B Date: Fri, 20 Jan 2017 12:32:00 +0100 Subject: [PATCH 3/4] add info about dependencyMap option --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 015a79d..bdb1883 100644 --- a/readme.md +++ b/readme.md @@ -71,6 +71,7 @@ gulp.task('concat', function() { * **options.ext** - `string` Source files will be matched to destination files with the provided extension (e.g. '.css'). * **options.map** - `function` Map relative source paths to relative destination paths (e.g. `function(relativePath) { return relativePath + '.bak'; }`) * **options.extra** - `string` or `array` An extra file, file glob, or list of extra files and/or globs, to check for updated time stamp(s). If any of these files are newer than the destination files, then all source files will be passed into the stream. + * **options.dependencyMap** - `object` A map of source paths to an `array` of file paths of their dependencies. These files will be checked for updated time stamp(s). If any of these dependency files are newer than the destination files, then all source files will be passed into the stream. For example, this can be used with source maps of CSS builds where many input files map to many output files. Create a [transform stream](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) that passes through files whose modification time is more recent than the corresponding destination file's modification time. From 5771cad58cf68e19122d4bf35e07a81c1556d8e7 Mon Sep 17 00:00:00 2001 From: Tim B Date: Wed, 25 Jan 2017 12:06:29 +0100 Subject: [PATCH 4/4] fix test failing in node v4 "Block-scoped declarations (let, const, function, class) not yet supported outside strict mode" --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index f48f822..a7f363f 100644 --- a/index.js +++ b/index.js @@ -109,7 +109,7 @@ function Newer(options) { if (options.dependencyMap) { const depMapStats = {}; - for (let key in options.dependencyMap) { + for (var key in options.dependencyMap) { const deps = options.dependencyMap[key]; const depsStats = deps.map(fn => Q.nfcall(fs.stat, fn).fail(e => null)); depMapStats[key] = Q.all(depsStats) @@ -132,7 +132,7 @@ function Newer(options) { const setValue = key => value => { map[key] = value; }; - for (let key in promiseMap) { + for (var key in promiseMap) { promiseMap[key].then(setValue(key)); } return map;