Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: dependency map #49

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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 (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)
.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.keys(depMapStats).map(k => depMapStats[k]))
// Resolve to depMapStats' promises' data.
.then(_ => depMapStats)
.then(promiseMap => {
const map = {};
const setValue = key => value => {
map[key] = value;
};
for (var key in promiseMap) {
promiseMap[key].then(setValue(key));
}
return map;
});
}

if (options.extra) {
var extraFiles = [];
for (var i = 0; i < options.extra.length; ++i) {
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down