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
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
|
|
|