You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

617 lines
14 KiB

'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var _Promise = _interopDefault(require('babel-runtime/core-js/promise'));
var _asyncToGenerator = _interopDefault(require('babel-runtime/helpers/asyncToGenerator'));
var _extends = _interopDefault(require('babel-runtime/helpers/extends'));
var _Object$keys = _interopDefault(require('babel-runtime/core-js/object/keys'));
var PluginError = _interopDefault(require('plugin-error'));
var through = _interopDefault(require('through2'));
var Cache = _interopDefault(require('cache-swap'));
var File = _interopDefault(require('vinyl'));
var pick = _interopDefault(require('object.pick'));
var _JSON$stringify = _interopDefault(require('babel-runtime/core-js/json/stringify'));
var _Object$assign = _interopDefault(require('babel-runtime/core-js/object/assign'));
var _Array$from = _interopDefault(require('babel-runtime/core-js/array/from'));
var _Reflect$deleteProperty = _interopDefault(require('babel-runtime/core-js/reflect/delete-property'));
var _Reflect$apply = _interopDefault(require('babel-runtime/core-js/reflect/apply'));
var _Map = _interopDefault(require('babel-runtime/core-js/map'));
var EventEmitter = _interopDefault(require('events'));
var crypto = _interopDefault(require('crypto'));
const version = "1.0.2";
const whitespaces = 2;
const eventListenersCount = 3;
function makeHash(key) {
return crypto.createHash('md5').update(key).digest('hex');
}
class TaskProxy {
constructor(task, inputOptions) {
this.task = task;
this.options = inputOptions;
this._cacheQueue = new _Map();
this._removeListeners = [];
if (task) {
this.patchTask();
}
}
patchTask() {
const task = this.task,
_transform = task._transform;
task._transform = (chunk, encoding, next) => {
_Reflect$apply(_transform, task, [chunk, encoding, (...args) => {
next(...args); // eslint-disable-line
task.emit('gulp-cache:transformed');
}]);
};
}
processFile(inputFile, signals = new EventEmitter()) {
process.nextTick(() => {
this._processFileAsync(inputFile, signals);
});
return signals;
}
_processFileAsync(inputFile, signals = new EventEmitter()) {
var _this = this;
return _asyncToGenerator(function* () {
const cached = yield _this._checkForCachedValue(inputFile);
// If we found a cached value
// The path of the cache key should also be identical to the original one when the file path changed inside the task
const cachedValue = cached.value,
cachedValueIsEmpty = !Array.isArray(cachedValue) || !cachedValue.length;
const cachedValuesWithNormalPaths = cachedValueIsEmpty ? [] : cachedValue.filter(function (file) {
return (!file.gulpCache$filePathChangedInsideTask || file.gulpCache$originalPath === inputFile.path) && (!file.gulpCache$fileBaseChangedInsideTask || file.gulpCache$originalBase === inputFile.base);
});
if (cachedValuesWithNormalPaths.length) {
cachedValuesWithNormalPaths.forEach(function (cachedFile) {
// Extend the cached value onto the file, but don't overwrite original path info
const file = new File(_extends({}, cachedFile, pick(inputFile, ['cwd', 'base', 'stat', 'history', 'path']), {
// file contents
contents: cachedFile.contents
}));
// Restore the file path if it was set
if (cachedFile.path && cachedFile.gulpCache$filePathChangedInsideTask) {
file.path = cachedFile.path;
}
// Restore the file base if it was set
if (cachedFile.base && cachedFile.gulpCache$fileBaseChangedInsideTask) {
file.base = cachedFile.base;
}
_Reflect$deleteProperty(file, 'gulpCache$filePathChangedInsideTask');
_Reflect$deleteProperty(file, 'gulpCache$fileBaseChangedInsideTask');
_Reflect$deleteProperty(file, 'gulpCache$originalPath');
_Reflect$deleteProperty(file, 'gulpCache$originalBase');
signals.emit('file', file);
});
signals.emit('done');
_this._removeListeners.push(function () {
// Remove all listeners from `signals`
signals.removeAllListeners();
});
return;
}
_this._runProxiedTaskAndQueueCache(inputFile, cached.key, signals);
})();
}
flush(next) {
var _this2 = this;
return _asyncToGenerator(function* () {
const task = _this2.task;
try {
if (typeof task._flush == 'function') {
task._flush((() => {
var _ref = _asyncToGenerator(function* (...args) {
yield _this2._flush();
next(...args);
});
return function () {
return _ref.apply(this, arguments);
};
})());
} else {
yield _this2._flush();
next();
return;
}
} catch (err) {
next(err);
return;
}
})();
}
_flush() {
var _this3 = this;
return _asyncToGenerator(function* () {
_this3._removeListeners.forEach(function (remove) {
remove();
});
_this3._removeListeners = [];
yield _Promise.all(_Array$from(_this3._cacheQueue).map((() => {
var _ref2 = _asyncToGenerator(function* ([cachedKey, files]) {
return _this3._storeCachedResult(cachedKey, files);
});
return function (_x) {
return _ref2.apply(this, arguments);
};
})()));
_this3._cacheQueue = new _Map();
})();
}
removeCachedResult(file) {
var _this4 = this;
return _asyncToGenerator(function* () {
const cachedKey = yield _this4._getFileKey(file);
return _this4._removeCached(_this4.options.name, cachedKey);
})();
}
_getFileKey(file) {
var _this5 = this;
return _asyncToGenerator(function* () {
const getKey = _this5.options.key,
key = yield getKey(file);
return key ? makeHash(key) : key;
})();
}
_checkForCachedValue(file) {
var _this6 = this;
return _asyncToGenerator(function* () {
const key = yield _this6._getFileKey(file);
// If no key returned, bug out early
if (!key) {
return {
value: null,
key
};
}
var _options = _this6.options;
const cacheName = _options.name,
restore = _options.restore;
const cached = yield _this6._getCached(cacheName, key);
if (!cached) {
return {
value: null,
key
};
}
let parsedContents = null;
try {
parsedContents = JSON.parse(cached.contents);
} catch (err) {
parsedContents = [{ cached: cached.contents }];
}
if (restore) {
parsedContents = parsedContents.map(function (parsedFile) {
const restoredFile = restore(parsedFile);
// Force restore service properties
restoredFile.gulpCache$filePathChangedInsideTask = parsedFile.gulpCache$filePathChangedInsideTask;
restoredFile.gulpCache$fileBaseChangedInsideTask = parsedFile.gulpCache$fileBaseChangedInsideTask;
restoredFile.gulpCache$originalPath = parsedFile.gulpCache$originalPath;
restoredFile.gulpCache$originalBase = parsedFile.gulpCache$originalBase;
return restoredFile;
});
}
return {
value: parsedContents,
key
};
})();
}
_getValueFromResult(result) {
var _this7 = this;
return _asyncToGenerator(function* () {
const getValue = _this7.options.value;
if (typeof getValue !== 'function') {
if (typeof getValue === 'string') {
return {
[getValue]: result[getValue]
};
}
return getValue;
}
return getValue(result);
})();
}
_storeCachedResult(key, result) {
var _this8 = this;
return _asyncToGenerator(function* () {
// If we didn't have a cachedKey, skip caching result
if (!key) {
return result;
}
const options = _this8.options;
const files = (yield _Promise.all(result.map((() => {
var _ref3 = _asyncToGenerator(function* ({ file, meta }) {
if (options.success !== true && !(yield options.success(file))) {
return null;
}
return _Object$assign((yield _this8._getValueFromResult(file)), meta);
});
return function (_x2) {
return _ref3.apply(this, arguments);
};
})()))).filter(Boolean);
return _this8._addCached(_this8.options.name, key, _JSON$stringify(files, null, whitespaces));
})();
}
_queueCache(file, cachedKey, originalBase, originalPath) {
var _this9 = this;
return _asyncToGenerator(function* () {
const _cacheQueue = _this9._cacheQueue;
const item = {
file: file.clone({ contents: false }),
meta: {
// Check if the task changed the file path
gulpCache$filePathChangedInsideTask: file.path !== originalPath,
// Check if the task changed the base path
gulpCache$fileBaseChangedInsideTask: file.base !== originalBase,
// Keep track of the original path
gulpCache$originalPath: originalPath,
// Keep track of the original base
gulpCache$originalBase: originalBase
}
};
if (_cacheQueue.has(cachedKey)) {
_cacheQueue.get(cachedKey).push(item);
} else {
_cacheQueue.set(cachedKey, [item]);
}
})();
}
_runProxiedTaskAndQueueCache(file, cachedKey, signals = new EventEmitter()) {
const originalBase = file.base,
originalPath = file.path;
signals.on('cache', file => {
this._queueCache(file, cachedKey, originalBase, originalPath);
signals.emit('file', file);
});
return this._runProxiedTask(file, cachedKey, signals);
}
_runProxiedTask(file, cachedKey, signals = new EventEmitter()) {
const task = this.task,
hasCacheListener = Boolean(signals.listenerCount('cache'));
function onError(err) {
signals.emit('error', err);
}
function onData(datum) {
if (datum._cachedKey !== cachedKey) {
return;
}
_Reflect$deleteProperty(datum, '_cachedKey');
if (hasCacheListener) {
signals.emit('cache', datum);
} else {
signals.emit('file', datum);
}
}
function onTransformed() {
signals.emit('done');
}
this._removeListeners.push(() => {
// Be good citizens and remove our listeners
task.removeListener('error', onError);
task.removeListener('gulp-cache:transformed', onTransformed);
task.removeListener('data', onData);
// Reduce the maxListeners back down
task.setMaxListeners(task._maxListeners - eventListenersCount);
// Remove all listeners from `signals`
signals.removeAllListeners();
});
// Bump up max listeners to prevent memory leak warnings
const currMaxListeners = task._maxListeners || 0;
task.setMaxListeners(currMaxListeners + eventListenersCount);
task.on('data', onData);
task.once('gulp-cache:transformed', onTransformed);
task.once('error', onError);
file._cachedKey = cachedKey;
// Run through the other task and grab output (or error)
task.write(file);
return signals;
}
/**
* Cache promise wrappers.
*/
_addCached(...args) {
return new _Promise((resolve, reject) => {
this.options.fileCache.addCached(...args, (err, res) => {
if (err) {
reject(err);
return;
}
resolve(res);
});
});
}
_getCached(...args) {
return new _Promise((resolve, reject) => {
this.options.fileCache.getCached(...args, (err, res) => {
if (err) {
reject(err);
return;
}
resolve(res);
});
});
}
_removeCached(...args) {
return new _Promise((resolve, reject) => {
this.options.fileCache.removeCached(...args, err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
}
module.exports = exports['default'];
const fileCache = new Cache({ cacheDirName: 'gulp-cache' });
function defaultKey(file) {
return `${version}${file.contents.toString('base64')}`;
}
function defaultRestore(restored) {
if (restored.contents) {
// Handle node 0.11 buffer to JSON as object with { type: 'buffer', data: [...] }
if (restored && restored.contents && Array.isArray(restored.contents.data)) {
restored.contents = new Buffer(restored.contents.data);
} else if (Array.isArray(restored.contents)) {
restored.contents = new Buffer(restored.contents);
} else if (typeof restored.contents === 'string') {
restored.contents = new Buffer(restored.contents, 'base64');
}
}
const restoredFile = new File(restored);
// Restore any properties that the original task put on the file;
// but omit the normal properties of the file
_Object$keys(restored).forEach(key => {
if (File.isCustomProp(key)) {
restoredFile[key] = restored[key];
}
});
return restoredFile;
}
function defaultValue(file) {
const vinylProps = ['cwd', 'base', 'contents', 'stat', 'history', 'path'],
customProps = _Object$keys(file).filter(File.isCustomProp);
// Convert from a File object (from vinyl) into a plain object
return pick(file, [...vinylProps, ...customProps]);
}
const defaultOptions = {
fileCache,
name: 'default',
success: true,
key: defaultKey,
restore: defaultRestore,
value: defaultValue
};
plugin.Cache = Cache;
plugin.fileCache = fileCache;
plugin.defaultOptions = defaultOptions;
function plugin(task, inputOptions) {
// Check for required task option
if (!task) {
throw new PluginError('gulp-cache', 'Must pass a task to cache()');
}
const options = _extends({}, plugin.defaultOptions, task.cacheable, inputOptions);
const taskProxy = new TaskProxy(task, options);
function each(file, enc, next) {
if (file.isNull()) {
next(null, file);
return;
}
if (file.isStream()) {
next(new PluginError('gulp-cache', 'Cannot operate on stream sources'));
return;
}
const signals = taskProxy.processFile(file);
signals.on('error', err => {
next(new PluginError('gulp-cache', err));
});
signals.on('file', file => {
this.push(file);
});
signals.on('done', () => {
next(null);
});
}
function flush(next) {
taskProxy.flush(next);
}
return through.obj(each, flush);
}
plugin.clear = function clear(inputOptions) {
let each = (() => {
var _ref = _asyncToGenerator(function* (file, enc, next) {
if (file.isNull()) {
next(null, file);
return;
}
if (file.isStream()) {
next(new PluginError('gulp-cache', 'Cannot operate on stream sources'));
return;
}
try {
yield taskProxy.removeCachedResult(file);
next(null, file);
return;
} catch (err) {
next(new PluginError('gulp-cache', err));
return;
}
});
return function each(_x, _x2, _x3) {
return _ref.apply(this, arguments);
};
})();
const options = _extends({}, plugin.defaultOptions, inputOptions);
const taskProxy = new TaskProxy(null, options);
return through.obj(each);
};
plugin.clearAll = function clearAll() {
return new _Promise((resolve, reject) => {
fileCache.clear(null, err => {
if (err) {
reject(new PluginError('gulp-cache', `Problem clearing the cache: ${err.message}`));
return;
}
resolve();
});
});
};
module.exports = exports['default'];
module.exports = plugin;
//# sourceMappingURL=index.js.map