Skip to content

Commit

Permalink
smartMode, overwriteMode, ignoreDestinationFiles implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Aslan Baryshnikov committed Apr 25, 2022
1 parent a7aeaea commit 86f51eb
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 19 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

A utility for processing and converting images to the requirements of the modern web.

## Features

- Recursive directory optimization
- Keeping the original directory hierarchy
- Flexible behavior customization

## Install

```shell
Expand Down Expand Up @@ -45,6 +51,29 @@ Type: `String`

Set the destination folder. The folder does not need to be empty, but images with the same name will be overwritten.

##### smartMode?

Type: `Boolean`<br>
Default `true`

If true, moves the file if there is no file with the same name in the destination folder, or if the processed file is smaller.

##### overwriteMode?

Type: `Boolean`<br>
Default `false`

If true, only existing files in the destination folder will be overwritten. If your destination folder is empty, there will be no changes (because nothing to update). Only available when smartMode is enabled.

##### ignoreDestinationFiles?

Type: `Boolean`<br>
Default `false`

If true, disables checking that the source file and the file in the destination folder do not match. True is recommended only if you don't care about files in the destination folder.

Note: In most cases, you should replace an obsolete file with a more recent one to avoid erroneous overwriting.

##### mask?

Type: `String`<br>
Expand Down
5 changes: 5 additions & 0 deletions helpers/getFileSize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const fs = require('fs');

const getFileSize = (filePath) => fs.statSync(filePath).size;

module.exports = getFileSize;
4 changes: 2 additions & 2 deletions helpers/getTotalSize.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const fs = require('fs');
const getAllFiles = require('./getAllFiles');
const getFileSize = require('./getFileSize');

const getTotalSize = (directoryPath) => {
const arrayOfFiles = getAllFiles(directoryPath);

let totalSize = 0;

arrayOfFiles.forEach((filePath) => {
totalSize += fs.statSync(filePath).size;
totalSize += getFileSize(filePath);
});

return totalSize;
Expand Down
10 changes: 10 additions & 0 deletions helpers/moveFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const fs = require('fs');
const path = require('path');

const moveFile = (from, to) => {
const dir = path.dirname(to);
fs.mkdirSync(dir, { recursive: true });
fs.renameSync(from, to);
};

module.exports = moveFile;
12 changes: 12 additions & 0 deletions helpers/tmpDir.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fs = require('fs');
const path = require('path');
const os = require('os');

const createTmpDir = (prefix) => fs.mkdtempSync(path.join(os.tmpdir(), prefix));

const removeTmpDir = (tmpDir) => fs.rmSync(tmpDir, { recursive: true });

module.exports = {
createTmpDir,
removeTmpDir,
};
97 changes: 81 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const fs = require('fs');
const imagemin = require('imagemin');
const imageminGifsicle = require('imagemin-gifsicle');
const imageminMozjpeg = require('imagemin-mozjpeg');
Expand All @@ -7,6 +8,10 @@ const imageminWebp = require('imagemin-webp');
const convertBytes = require('./helpers/convertBytes');
const getTotalSize = require('./helpers/getTotalSize');
const getAllDirectories = require('./helpers/getAllDirectories');
const getAllFiles = require('./helpers/getAllFiles');
const { createTmpDir, removeTmpDir } = require('./helpers/tmpDir');
const getFileSize = require('./helpers/getFileSize');
const moveFile = require('./helpers/moveFile');

const PACKAGE_NAME = 'image-optimist';
const PROCESSING_TIME = 'Processing time';
Expand All @@ -18,6 +23,9 @@ const imageOptimist = async (config) => {
const c = {
sourcePath: null,
destinationPath: null,
smartMode: true,
overwriteMode: false,
ignoreDestinationFiles: false,
mask: '*.{jpg,jpeg,png,svg,gif}',
webp: [
imageminWebp({
Expand Down Expand Up @@ -48,22 +56,29 @@ const imageOptimist = async (config) => {
const errors = [];
if (c.sourcePath === null) errors.push('sourcePath is required');
if (c.destinationPath === null) errors.push('destinationPath is required');
if (errors.length !== 0) console.error(JSON.stringify(errors));
if (c.overwriteMode && !c.smartMode) errors.push('overwriteMode is only available when smartMode is enabled');
if (errors.length !== 0) {
console.error(`${PACKAGE_NAME} finished with errors:\n`, JSON.stringify(errors));
return;
}

const initialDirSize = getTotalSize(c.sourcePath);

let tmpDir;
try {
const inputDirectories = getAllDirectories(c.sourcePath);

if (c.smartMode) tmpDir = createTmpDir(PACKAGE_NAME);

// Basic processing
const outputDirectories = [c.destinationPath];
const outputDirectories = [];
// eslint-disable-next-line no-restricted-syntax
for (const directory of inputDirectories) {
const input = `${directory}/${c.mask}`;
const output = directory.replace(c.sourcePath, c.destinationPath);
const output = directory.replace(c.sourcePath, c.smartMode ? tmpDir : c.destinationPath);
const relativePath = `.${directory.replace(c.sourcePath, '')}`;
outputDirectories.push(output);
console.log(`Images from ${relativePath} are being processed...`);
console.log(`Processing: ${relativePath}`);
// eslint-disable-next-line no-await-in-loop
await imagemin([input], {
destination: output,
Expand All @@ -76,28 +91,78 @@ const imageOptimist = async (config) => {
// eslint-disable-next-line no-restricted-syntax
for (const directory of outputDirectories) {
const input = `${directory}/*.{jpeg,jpg,png}`;
const relativePath = `.${directory.replace(c.destinationPath, '')}`;
console.log(`Images from ${relativePath} are being converted to webp...`);
const relativePath = `.${directory.replace(c.smartMode ? tmpDir : c.destinationPath, '')}`;
console.log(`Converting to WebP: ${relativePath}`);
// eslint-disable-next-line no-await-in-loop
await imagemin([input], {
destination: directory,
plugins: c.webp,
});
}
}

// Smart mode (see readme for details)
if (c.smartMode) {
const tmpFiles = getAllFiles(tmpDir);
// eslint-disable-next-line no-restricted-syntax
for (const tmpFile of tmpFiles) {
const tmpFileSize = getFileSize(tmpFile);
const sourceFile = tmpFile.replace(tmpDir, c.sourcePath);
const sourceFileIsExists = fs.existsSync(sourceFile);
const sourceFileSize = sourceFileIsExists ? getFileSize(sourceFile) : null;
const candidate = tmpFile.replace(tmpDir, c.destinationPath);
const candidateIsExists = fs.existsSync(candidate);
const candidateSize = candidateIsExists ? getFileSize(candidate) : null;
const candidateRelative = `.${candidate.replace(c.destinationPath, '')}`;

if (c.overwriteMode ? candidateIsExists : true) {
if (
!candidateIsExists
|| !sourceFileIsExists
|| candidateSize === sourceFileSize
|| c.ignoreDestinationFiles
) {
// eslint-disable-next-line no-nested-ternary
if (candidateIsExists ? tmpFileSize < candidateSize : true) {
moveFile(tmpFile, candidate);
if (candidateIsExists) console.log(`Updated: ${candidateRelative}`);
else console.log(`Created: ${candidateRelative}`);
} else {
console.log(`Not updated (already has optimal size): ${candidateRelative}`);
}
} else {
console.warn(`Conflict: ${sourceFile} and ${candidate}\nThe source file and the file in the`
+ ' destination folder are not the same. You should replace an'
+ ' obsolete file with a more recent one to avoid erroneous overwriting.'
+ ' You can set ignoreDestinationFiles to true if you do not care about'
+ ' files in the destination folder.');
}
} else {
console.log(`Not created (overwriteMode is enabled): ${candidateRelative}`);
}
}
}

const finalDirSize = getTotalSize(c.destinationPath);
const diffDirSize = initialDirSize - finalDirSize;

console.log(`Initial: ${convertBytes(initialDirSize)}`);
console.log(`Final${c.webp ? ' + WebP' : ''}: ${convertBytes(finalDirSize)}`);
console.log(`Diff: ${convertBytes(diffDirSize)}`);
console.timeEnd(PROCESSING_TIME);
console.log(`${PACKAGE_NAME} finished`);
} catch (e) {
console.error(`${PACKAGE_NAME} finished with error:\n`, e);
return;
} finally {
try {
if (tmpDir) {
removeTmpDir(tmpDir);
}
} catch (e) {
console.error(`An error has occurred while removing the temp folder at ${tmpDir}.`
+ 'Please remove it manually. Error:\n', e);
}
}

const finalDirSize = getTotalSize(c.destinationPath);
const diffDirSize = initialDirSize - finalDirSize;

console.log(`Initial: ${convertBytes(initialDirSize)}`);
console.log(`Final${c.webp ? ' + webp' : ''}: ${convertBytes(finalDirSize)}`);
console.log(`Diff: ${convertBytes(diffDirSize)}`);
console.timeEnd(PROCESSING_TIME);
console.log(`${PACKAGE_NAME} finished`);
};

module.exports = imageOptimist;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "image-optimist",
"version": "1.0.13",
"version": "1.0.14",
"description": "A utility for processing and converting images to the requirements of the modern web.",
"private": false,
"main": "index.js",
Expand Down

0 comments on commit 86f51eb

Please sign in to comment.