Commit 1cb8d76e authored by Philipp Nowinski's avatar Philipp Nowinski
Browse files

[FEATURE] improve performance and output of the image task

parent 38173a7e
......@@ -17,6 +17,7 @@ module.exports = class task {
constructor() {
this._settings = settings;
this._config = settings.config;
this._embedded = false;
this._path = this._settings.getPath();
this._logger = require('./logger');
this.extensionName = settings.Extension;
......@@ -63,6 +64,12 @@ module.exports = class task {
return `${this._path}/${_path}`;
}
/**
* Displays an augmented error message with information about the env
*
* @param _errorMessage
* @private
*/
_handleError(_errorMessage) {
this._logger.error(chalk.bgRed(chalk.whiteBright(` Task ${chalk.black(this.constructor.name)} for extension ${chalk.black(this.extensionName)} failed: `)));
this._logger.error(_errorMessage);
......@@ -87,4 +94,14 @@ module.exports = class task {
let writeFile = util.promisify(fs.writeFile);
return writeFile(_filePath, _content);
}
/**
* Sets the _embedded property, which indicates that this task is not being
* run on its own, but is part of an orchestrated task
*
* @param {boolean} _value
*/
set embedded(_value) {
this._embedded = _value;
}
};
......@@ -8,7 +8,7 @@ module.exports = class Build extends Task {
/**
* Run all important tasks
*
*
* @override
*/
async run() {
......@@ -16,13 +16,19 @@ module.exports = class Build extends Task {
let images = this._settings.tasks.images();
let css = this._settings.tasks.css();
let js = this._settings.tasks.js();
await Promise.all(this._config.extensions.map(async _extension => {
settings.setExtension(_extension);
let tasks = [];
tasks.push(new images().run());
tasks.push(new css().run());
tasks.push(new js().run());
let task = new images();
task.embedded = true;
tasks.push(task.run());
task = new css();
task.embedded = true;
tasks.push(task.run());
task = new js();
task.embedded = true;
tasks.push(task.run());
await Promise.all(tasks);
}));
resolve();
......
......@@ -9,18 +9,24 @@ const imageminGifsicle = require('imagemin-gifsicle');
const imageminSvgo = require('imagemin-svgo');
const prettyBytes = require('pretty-bytes');
const chalk = require('chalk');
const async = require('async');
const ora = require('ora');
/**
* This task handles image optimization
*/
module.exports = class Watch extends Task {
module.exports = class Images extends Task {
/**
* Runner function
*
*
* @override
*/
async run(_subTask) {
this._totalBefore = 0;
this._totalAfter = 0;
this._touchedFiles = 0;
this._failedFiles = [];
return new Promise(async resolve => {
this.getElapsed = hirestime();
if (_subTask === 'uploaded') {
......@@ -38,14 +44,20 @@ module.exports = class Watch extends Task {
async _optimizeUploadedImages() {
return new Promise(async resolve => {
let tasks = [];
this._config.images.optimize.forEach(async imagesPath => {
tasks.push(new Promise(async resolve => {
const files = await globby([`${imagesPath}/**/*.{png,jpg,gif,svg}`]);
await this._optimize(files);
resolve();
}));
});
await Promise.all(tasks);
try {
this._config.images.optimize.forEach(async imagesPath => {
tasks.push(new Promise(async resolve => {
const files = await globby([`${imagesPath}/**/*.{png,jpg,gif,svg}`]);
await this._optimize(files, imagesPath);
resolve();
}));
});
await Promise.all(tasks);
} catch (error) {
this._handleError(error);
}
this._logger.info(`Task ${chalk.bold('images')} finished after ${this.getElapsed(hirestime.S)}s`);
resolve();
});
}
......@@ -56,7 +68,11 @@ module.exports = class Watch extends Task {
return new Promise(async resolve => {
const imagesPath = this._getFullPath(this._config.directories.images);
const files = await globby([`${imagesPath}/**/*.{png,jpg,gif,svg}`]);
await this._optimize(files);
try {
await this._optimize(files, imagesPath);
} catch (error) {
this._handleError(error);
}
this._logger.info(`Task ${chalk.bold('images')} finished after ${this.getElapsed(hirestime.S)}s`);
resolve();
});
......@@ -64,38 +80,116 @@ module.exports = class Watch extends Task {
/**
* Optimize a set of given images
*
*
* @param {Array} _files An array of filenames
* @param {String} _imagesPath
*/
async _optimize(_files) {
return new Promise(async (_resolve, _reject) => {
await Promise.all(_files.map(this._optimizeImage.bind(this)));
_resolve();
async _optimize(_files, _imagesPath) {
return new Promise(async (_resolve) => {
if (!this._embedded) {
this._loadingSpinner = ora();
this._loadingSpinner.start(`Optimizing images...`);
}
let q = async.queue((image, callback) => {
this._optimizeImage(image).then(callback);
}, 20);
_files.map(async (image) => {
q.push(image, (_error) => {
if (_error) {
this._handleError(_error.message);
}
});
});
q.drain = () => {
if (!this._embedded) {
this._loadingSpinner.stop();
}
let saved = this._totalBefore - this._totalAfter;
if (this._failedFiles.length) {
// emit empty line before
console.log();
this._logger.warning(chalk.bgYellow(chalk.black(' Please note that the following images have been skipped due to an error:' )));
this._failedFiles.forEach((file) => {
this._logger.warning(chalk.white(` - ${file.name}:`));
this._logger.warning(chalk.whiteBright(` ${typeof file.error === 'string' ? file.error : 'Error unknown'}`));
});
// emit empty line after
console.log();
}
this._logger.success(`Optimized ${chalk.white(this._touchedFiles)} of ${_files.length} images in ${_imagesPath}`);
this._logger.success(`Checked ${chalk.white(_files.length)} images and saved ${chalk.white(prettyBytes(saved))} (~ ${chalk.white(Math.round((saved / this._totalBefore) * 100) + '%')})`);
_resolve();
};
});
}
/**
* Optimizes an image
*
* @param _image
* @return {Promise}
* @private
*/
async _optimizeImage(_image) {
return new Promise(async resolve => {
let buffer = fs.readFileSync(_image);
let result = await imagemin.buffer(buffer, {
plugins: [
imageminJpegtran(),
imageminPngquant({quality: '65-80'}),
imageminGifsicle(),
imageminSvgo()
]
fs.readFile(_image, async (error, buffer) => {
if (error) {
this._handleError(error.message);
}
let result;
try {
result = await imagemin.buffer(buffer, {
plugins: [
imageminJpegtran(),
imageminPngquant({quality: '65-80'}),
imageminGifsicle(),
imageminSvgo()
]
});
} catch (error) {
this._failedFiles.push({
name: _image,
error: error.message
});
if (!this._embedded) {
this._loadingSpinner.clear();
this._loadingSpinner.frame();
}
this._logger.warning(`Skipping ${_image}`);
return resolve();
}
this._totalBefore += buffer.length;
this._totalAfter += result.length;
if (buffer.length === result.length) {
// don't bother
resolve();
} else {
let info;
if (buffer.length === result.length) {
info = chalk.dim('(already optimized)');
} else {
let saved = buffer.length - result.length;
let savedPercentage = Math.round((saved / buffer.length) * 100);
info = `(saved ${prettyBytes(saved)} ~ ${savedPercentage}%)`;
if (savedPercentage >= 20) {
info = chalk.bgGreen(chalk.black(`${info}`));
}
}
let message = `${chalk.white(_image.replace(`${this._getFullPath(this._config.directories.images)}/`, ''))} ${chalk.magenta(`${info}`)}`;
fs.writeFile(_image, result, (error) => {
if (error) {
this._handleError(error.message);
}
if (!this._embedded) {
this._loadingSpinner.clear();
this._loadingSpinner.frame();
}
this._logger.success(message);
this._touchedFiles += 1;
resolve();
});
}
});
let info;
if (buffer.length === result.length) {
info = chalk.dim('(already optimized)');
} else {
let saved = buffer.length - result.length;
info = `(saved ${prettyBytes(saved)} ~ ${Math.round((saved / buffer.length) * 100)}%)`;
}
let message = `${chalk.white(_image.replace(`${this._getFullPath(this._config.directories.images)}/`, ''))} ${chalk.magenta(`${info}`)}`;
fs.writeFileSync(_image, result);
this._logger.success(message);
resolve();
});
}
};
{
"name": "sgc",
"version": "3.2.1",
"version": "3.2.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -1988,9 +1988,12 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
},
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
"requires": {
"lodash": "^4.17.10"
}
},
"async-each": {
"version": "1.0.1",
......@@ -3351,6 +3354,11 @@
"restore-cursor": "^2.0.0"
}
},
"cli-spinners": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz",
"integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg=="
},
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
......@@ -4291,6 +4299,14 @@
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"defaults": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"requires": {
"clone": "^1.0.2"
}
},
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
......@@ -9472,6 +9488,34 @@
}
}
},
"ora": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-3.0.0.tgz",
"integrity": "sha512-LBS97LFe2RV6GJmXBi6OKcETKyklHNMV0xw7BtsVn2MlsgsydyZetSCbCANr+PFLmDyv4KV88nn0eCKza665Mg==",
"requires": {
"chalk": "^2.3.1",
"cli-cursor": "^2.1.0",
"cli-spinners": "^1.1.0",
"log-symbols": "^2.2.0",
"strip-ansi": "^4.0.0",
"wcwidth": "^1.0.1"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
"ordered-read-streams": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz",
......@@ -10128,6 +10172,13 @@
"requires": {
"async": "1.5.2",
"is-number-like": "^1.0.3"
},
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
}
}
},
"posix-character-classes": {
......@@ -15517,6 +15568,14 @@
"wrap-fn": "^0.1.0"
}
},
"wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"requires": {
"defaults": "^1.0.3"
}
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment