grunt-contrib-compress
Version:
Compress files and folders
269 lines (219 loc) • 7.85 kB
JavaScript
/*
* grunt-contrib-compress
* http://gruntjs.com/
*
* Copyright (c) 2016 Chris Talkington, contributors
* Licensed under the MIT license.
*/
;
var fs = require('fs');
var path = require('path');
var prettyBytes = require('pretty-bytes');
var chalk = require('chalk');
var zlib = require('zlib');
var archiver = require('archiver');
var streamBuffers = require('stream-buffers');
var _ = require('lodash');
module.exports = function(grunt) {
var exports = {
options: {}
};
var fileStatSync = function() {
var filepath = path.join.apply(path, arguments);
if (grunt.file.exists(filepath)) {
return fs.statSync(filepath);
}
return false;
};
// 1 to 1 gziping of files
exports.gzip = function(files, done) {
exports.singleFile(files, zlib.createGzip, 'gz', done);
grunt.log.ok('Compressed ' + chalk.cyan(files.length) + ' ' +
grunt.util.pluralize(files.length, 'file/files.'));
};
// 1 to 1 deflate of files
exports.deflate = function(files, done) {
exports.singleFile(files, zlib.createDeflate, 'deflate', done);
grunt.log.ok('Compressed ' + chalk.cyan(files.length) + ' ' +
grunt.util.pluralize(files.length, 'file/files.'));
};
// 1 to 1 deflateRaw of files
exports.deflateRaw = function(files, done) {
exports.singleFile(files, zlib.createDeflateRaw, 'deflate', done);
grunt.log.ok('Compressed ' + chalk.cyan(files.length) + ' ' +
grunt.util.pluralize(files.length, 'file/files.'));
};
// 1 to 1 brotlify of files
exports.brotli = function(files, done) {
exports.singleFile(files, zlib.createBrotliCompress, 'br', done);
grunt.log.ok('Compressed ' + chalk.cyan(files.length) + ' ' +
grunt.util.pluralize(files.length, 'file/files.'));
};
// 1 to 1 compression of files, expects a compatible zlib method to be passed in, see above
exports.singleFile = function(files, algorithm, extension, done) {
grunt.util.async.forEachSeries(files, function(filePair, nextPair) {
grunt.util.async.forEachSeries(filePair.src, function(src, nextFile) {
// Must be a file
if (grunt.file.isDir(src)) {
return nextFile();
}
// Ensure the dest folder exists
grunt.file.mkdir(path.dirname(filePair.dest));
var srcStream = fs.createReadStream(src);
var originalSize = exports.getSize(src);
var destStream;
function initDestStream() {
destStream = fs.createWriteStream(filePair.dest);
destStream.on('close', function() {
var compressedSize = exports.getSize(filePair.dest);
var ratio = Math.round(parseInt(compressedSize, 10) / parseInt(originalSize, 10) * 100) + '%';
grunt.verbose.writeln('Created ' + chalk.cyan(filePair.dest) + ' (' + compressedSize + ') - ' + chalk.cyan(ratio) + ' of the original size');
nextFile();
});
}
// write to memory stream if source and destination are the same
var tmpStream;
if (src === filePair.dest) {
tmpStream = new streamBuffers.WritableStreamBuffer();
tmpStream.on('close', function() {
initDestStream();
destStream.write(this.getContents());
destStream.end();
});
} else {
initDestStream();
}
var compressor = algorithm.call(zlib, exports.options);
compressor.on('error', function(err) {
grunt.log.error(err);
grunt.fail.warn(algorithm + ' failed.');
nextFile();
});
srcStream.pipe(compressor).pipe(tmpStream || destStream);
}, nextPair);
}, done);
};
// Compress with tar, tgz and zip
exports.tar = function(files, done) {
if (typeof exports.options.archive !== 'string' || exports.options.archive.length === 0) {
grunt.fail.warn('Unable to compress; no valid archive file was specified.');
return;
}
var mode = exports.options.mode;
if (mode === 'tgz') {
mode = 'tar';
exports.options.gzip = true;
}
var archive = archiver.create(mode, exports.options);
var dest = exports.options.archive;
var dataWhitelist = ['comment', 'date', 'mode', 'store', 'gid', 'uid'];
var sourcePaths = {};
// Ensure dest folder exists
grunt.file.mkdir(path.dirname(dest));
// Where to write the file
var destStream = fs.createWriteStream(dest);
archive.on('error', function(err) {
grunt.log.error(err);
grunt.fail.warn('Archiving failed.');
});
archive.on('entry', function(file) {
var sp = sourcePaths[file.name] || 'unknown';
grunt.verbose.writeln('Archived ' + chalk.cyan(sp) + ' -> ' + chalk.cyan(dest + '/' + file.name));
});
destStream.on('error', function(err) {
grunt.log.error(err);
grunt.fail.warn('WriteStream failed.');
});
destStream.on('close', function() {
var size = archive.pointer();
grunt.verbose.writeln('Created ' + chalk.cyan(dest) + ' (' + exports.getSize(size) + ')');
done();
});
archive.pipe(destStream);
files.forEach(function(file) {
var isExpandedPair = file.orig.expand || false;
file.src.forEach(function(srcFile) {
var fstat = fileStatSync(srcFile);
if (!fstat) {
grunt.fail.warn('unable to stat srcFile (' + srcFile + ')');
return;
}
var internalFileName = isExpandedPair ? file.dest : exports.unixifyPath(path.join(file.dest || '', srcFile));
// check if internal file name is not a dot, should not be present in an archive
if (internalFileName === '.' || internalFileName === './') {
return;
}
if (fstat.isDirectory() && internalFileName.slice(-1) !== '/') {
srcFile += '/';
internalFileName += '/';
}
var fileData = {
name: internalFileName,
stats: fstat
};
for (var i = 0; i < dataWhitelist.length; i++) {
if (typeof file[dataWhitelist[i]] === 'undefined') {
continue;
}
if (typeof file[dataWhitelist[i]] === 'function') {
fileData[dataWhitelist[i]] = file[dataWhitelist[i]](srcFile);
} else {
fileData[dataWhitelist[i]] = file[dataWhitelist[i]];
}
}
if (fstat.isFile()) {
archive.file(srcFile, fileData);
} else if (fstat.isDirectory()) {
archive.append(null, fileData);
} else {
grunt.fail.warn('srcFile (' + srcFile + ') should be a valid file or directory');
return;
}
sourcePaths[internalFileName] = srcFile;
});
});
grunt.log.ok('Compressed ' + chalk.cyan(files.length) + ' ' +
grunt.util.pluralize(files.length, 'file/files.'));
archive.finalize();
};
exports.getSize = function(filename, pretty) {
var size = 0;
if (typeof filename === 'string') {
try {
size = fs.statSync(filename).size;
} catch (e) {}
} else {
size = filename;
}
if (pretty !== false) {
if (!exports.options.pretty) {
return size + ' bytes';
}
return prettyBytes(size);
}
return Number(size);
};
exports.autoDetectMode = function(dest) {
if (exports.options.mode) {
return exports.options.mode;
}
if (!dest) {
return 'gzip';
}
if (_.endsWith(dest, '.tar.gz')) {
return 'tgz';
}
var ext = path.extname(dest).replace('.', '');
if (ext === 'gz') {
return 'gzip';
}
if (ext === 'br') {
return 'brotli';
}
return ext;
};
exports.unixifyPath = function(filepath) {
return process.platform === 'win32' ? filepath.replace(/\\/g, '/') : filepath;
};
return exports;
};