diff --git a/README.md b/README.md new file mode 100644 index 0000000..f449152 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +##gulp-asset-cache +A disk based caching task for [gulp](http://gulpjs.com/). This plugin was built mainly to deal with the issues around having no dist directory and wanting to prevent image/video compression from happening multiple times on larger teams. If you do have the luxury of a src/dist file structure I recommend [gulp-changed](https://www.npmjs.com/package/gulp-changed) or [gulp-newer](https://www.npmjs.com/package/gulp-newer) as they easily integrate with that file structure. + +##Installation +Install package with NPM and add it to your development dependencies: +`npm install --save-dev gulp-asset-cache` + +##Usage +``` +var gulp = require('gulp'), + imagemin = require('gulp-imagemin'), + assetCache = require('gulp-asset-cache'); + +gulp.task('images', function() { + return gulp.src('./images/*.{jpg,png,jpeg,gif,svg}) + // Specify the location and name of the cache file + .pipe(assetCache('./images/.image-cache)) + .pipe(imagemin({ + verbose: true + })) + .pipe(gulp.dest('./images/')); +}); +``` + +This will create a cache file named `.image-cache` of all files passed through the pipeline to be excluded from subsequent runs. + +##Parameters +####`cacheName` +``` +.pipe(assetCache( )) +``` +> [Optional] The location to store the cache-file. + +* Defaults to `./.asset-cache` + + +##Tests +``` +npm test +``` + +##To Do +* Add a method to identify when already cached files have changed and should be rerun. + +##License +[The MIT License(MIT)](https://github.com/Polyneue/gulp-asset-cache/blob/master/LICENSE) + +Copyright (c) 2016 [Ed Mendoza](http://www.edmendoza.com) \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..47a9d9e --- /dev/null +++ b/index.js @@ -0,0 +1,87 @@ +'use strict'; + +// Dependencies +var fs = require('fs'), + through = require('through2'), + path = require('path'), + gutil = require('gulp-util'), + md5 = require('md5'); + +// Helpers +var PluginError = gutil.PluginError, + gc = gutil.colors; + +// Constants +const PLUGIN_NAME = 'gulp-asset-cache'; + +/** + * Create a cache file for filtering + * @param {string} cacheName - Path to cache file + * @return stream + */ +var assetCache = function(cacheName) { + var currentCache = {}, + cacheFile = {}; + + // Set default cache if not specified + if (!cacheName) { + cacheName = './.asset-cache'; + } + + // Try to load an existing cache file + try { + cacheFile = JSON.parse(fs.readFileSync(cacheName)); + } catch (err) { + cacheFile = {}; + } + + /** + * Update the cache and pass through uncached files + * @param {File} file - A vinyl file + * @param {enc} encoding - Encoding (ignored) + * @param {function(err, file)} done - Callback + */ + function transform(file, enc, cb) { + + if (file.isNull()) { + return cb(); + } + + if (file.isStream()) { + throw new PluginError(PLUGIN_NAME, 'Streams not currently supported.'); + } + + if (file.isBuffer()) { + var relativePath = path.relative(__dirname, path.dirname(file.path)) + '/' + path.basename(file.path), + hash = md5(relativePath); + + // Update cache object + currentCache[relativePath] = hash; + + if (cacheFile[relativePath] === currentCache[relativePath]) { + // Skip cached file + gutil.log(PLUGIN_NAME + ':' + gc.green(' ✔ ') + relativePath + gc.grey(' (cached)')); + return cb(); + } else { + // Push uncached file + gutil.log(PLUGIN_NAME + ':' + gc.red(' ✖ ') + relativePath + gc.grey(' (uncached)')); + this.push(file); + return cb(); + } + } + } + + /** + * Flush updated cache file to disk + * @param {function(err, file)} done - Callback + */ + function flush(cb) { + fs.writeFile(cacheName, JSON.stringify(currentCache), cb); + } + + // Return stream + return through.obj(transform, flush); +} + +// Exports +module.exports = assetCache; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..e2cbac5 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "gulp-asset-cache", + "version": "1.0.0", + "description": "A disk based caching task for gulp", + "main": "index.js", + "scripts": { + "test": "mocha --reporter spec" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Polyneue/gulp-asset-cache.git" + }, + "keywords": [ + "cache", + "gulp", + "gulpplugin" + ], + "author": "Ed Mendoza (http://www.edmendoza.com)", + "license": "MIT", + "bugs": { + "url": "https://github.com/Polyneue/gulp-asset-cache/issues" + }, + "homepage": "https://github.com/Polyneue/gulp-asset-cache#readme", + "devDependencies": { + "chai": "^3.5.0", + "gulp-util": "^3.0.7", + "mocha": "^2.5.3" + }, + "dependencies": { + "gulp-util": "^3.0.7", + "md5": "^2.1.0", + "through2": "^2.0.1" + } +} diff --git a/test/.test-existing-cache b/test/.test-existing-cache new file mode 100644 index 0000000..8dab0f9 --- /dev/null +++ b/test/.test-existing-cache @@ -0,0 +1 @@ +{"test/foo.png":"9a00e3f54e6a23ee446348a075ddcfb1"} \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..513fd65 --- /dev/null +++ b/test/test.js @@ -0,0 +1,94 @@ +'use strict'; + +// Dependencies +var assetCache = require('../index'), + chai = require('chai'), + gutil = require('gulp-util'), + fs = require('fs'), + expect = chai.expect; + +describe('gulp-asset-cache', function() { + var cacheNewTest = './test/.test-new-cache', + cacheExistingTest = './test/.test-existing-cache', + fileOne = new gutil.File({ path: './test/foo.png', contents: new Buffer('Foo') }), + fileTwo = new gutil.File({ path: './test/deep/bar.jpg', contents: new Buffer('Bar') }), + fileThree = new gutil.File({ path: './test/baz.gif', contents: new Buffer('Baz') }); + + describe('with an existing cache', function() { + + it('should filter files out of the pipe and pass on uncached ones', function(done) { + var stream = assetCache(cacheExistingTest); + + stream.pipe(gutil.buffer(function(err, files) { + expect(files).to.not.have.keys(['./test/deep/bar.jpg']); + done(); + // Reset test file + fs.writeFile(cacheExistingTest, '{"test/foo.png":"9a00e3f54e6a23ee446348a075ddcfb1"}', 'utf8'); + })); + + stream.write(fileOne); + stream.write(fileTwo); + stream.end(); + }); + + it('should update the cache with piped files', function(done) { + var stream = assetCache(cacheExistingTest); + + stream.on('finish', function() { + fs.readFile(cacheExistingTest, 'utf8', function(err, data) { + var cacheObj = JSON.parse(data); + expect(cacheObj).to.have.keys(['test/deep/bar.jpg', 'test/baz.gif']); + expect(cacheObj).to.not.have.keys(['test/foo.png']); + done(); + }); + + // Reset test file + fs.unlink(cacheExistingTest, function() { + fs.writeFile(cacheExistingTest, '{"test/foo.png":"9a00e3f54e6a23ee446348a075ddcfb1"}', 'utf8'); + }) + }); + + stream.write(fileTwo); + stream.write(fileThree); + stream.end(); + }); + }); + + describe('without a cache', function() { + + it('should create a cache file and populate with piped files', function(done) { + var stream = assetCache(cacheNewTest); + + stream.on('finish', function() { + fs.readFile(cacheNewTest, 'utf8', function(err, data) { + expect(JSON.parse(data)).to.have.keys(['test/foo.png', 'test/deep/bar.jpg']); + done(); + }); + // Remove test File + fs.unlink(cacheNewTest); + }); + + stream.write(fileOne); + stream.write(fileTwo); + stream.end(); + }); + + it('should create a default test file and populate with piped files', function(done) { + var stream = assetCache(); + + stream.on('finish', function() { + fs.readFile('./.asset-cache', 'utf8', function(err, data) { + expect(JSON.parse(data)).to.have.keys(['test/foo.png', 'test/deep/bar.jpg']) + done(); + }) + + // Remove test file + fs.unlink('./.asset-cache'); + }); + + stream.write(fileOne); + stream.write(fileTwo); + stream.end(); + }); + }); +}); \ No newline at end of file