Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"site": "npx eleventy",
"clean": "rimraf dist",
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development npm-run-all clean --parallel 'site -- --serve' 'css -- --watch' 'js -- --watch' assets",
"build": "npm-run-all clean --parallel site css js assets",
"dev": "cross-env NODE_ENV=development npm-run-all clean assets --parallel 'site -- --serve' 'css -- --watch' 'js -- --watch'",
"build": "npm-run-all clean --parallel site css js && npm run assets",
"build:development": "cross-env NODE_ENV=development npm run build",
"build:production": "cross-env NODE_ENV=production npm run build",
"debug": "cross-env DEBUG=* npx eleventy",
Expand Down
122 changes: 91 additions & 31 deletions scripts/images.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,101 @@ const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminPngquant = require('imagemin-pngquant');
const imageminSvgo = require('imagemin-svgo');
const imageminGifsicle = require('imagemin-gifsicle');
const chunk = require('lodash.chunk');

const util = require('util');
const path = require('path');
const fs = require('graceful-fs');
const makeDir = require('make-dir');
const writeFile = util.promisify(fs.writeFile);
const fs = require('fs/promises');

const srcDir = 'src';
const distDir = 'dist';
const BATCH_SIZE = 8;
const imagePattern = /\.(jpg|jpeg|png|svg|gif)$/i;

const allPlugins = [
imageminMozjpeg({ quality: 75 }),
imageminPngquant({ quality: [0.6, 0.8] }),
imageminSvgo({ plugins: [{ removeViewBox: false }] }),
imageminGifsicle({ colors: 96, optimizationLevel: 2 }),
];

function destinationPathFor(imagePath) {
return path.join(distDir, path.relative(srcDir, imagePath));
}

async function collectImagePaths(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
const paths = [];

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (entry.isDirectory()) {
paths.push(...(await collectImagePaths(fullPath)));
} else if (imagePattern.test(entry.name)) {
paths.push(fullPath);
}
}

return paths;
}

async function writeOptimizedFile(file) {
const destinationPath = destinationPathFor(file.sourcePath);
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
await fs.writeFile(destinationPath, file.data);
}

async function copyOriginalImage(imagePath) {
const destinationPath = destinationPathFor(imagePath);
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
await fs.copyFile(imagePath, destinationPath);
}

async function optimizeImage(imagePath) {
try {
const files = await imagemin([imagePath], { plugins: allPlugins });

if (!files.length) {
throw new Error(`No output for ${imagePath}`);
}

await writeOptimizedFile(files[0]);
} catch (error) {
console.warn(
`Warning: could not optimize ${imagePath}, copying original (${
error.code || error.message
})`
);
await copyOriginalImage(imagePath);
}
}

async function optimizeBatch(batch) {
try {
const files = await imagemin(batch, { plugins: allPlugins });
await Promise.all(files.map(writeOptimizedFile));
} catch (error) {
console.warn(
`Warning: batch optimize failed (${
error.code || error.message
}), retrying file-by-file`
);
await Promise.all(batch.map(optimizeImage));
}
}

(async () => {
const files = await imagemin([srcDir + '/**/*.{jpg,jpeg,png,svg,gif}'], {
plugins: [
imageminMozjpeg({ quality: 75 }),
imageminPngquant({
quality: [0.6, 0.8],
}),
imageminSvgo({
plugins: [{ removeViewBox: false }],
}),
imageminGifsicle({
colors: 96,
optimizationLevel: 2,
}),
],
});

const { length } = files;
files.forEach(async v => {
let source = path.parse(v.sourcePath);
v.destinationPath = `${source.dir.replace(srcDir, distDir)}/${source.name}${
source.ext
}`;
await makeDir(path.dirname(v.destinationPath));
await writeFile(v.destinationPath, v.data);
});
console.log(`${length} image${length !== 1 ? 's' : ''} minified`);
})();
const imagePaths = await collectImagePaths(srcDir);
const batches = chunk(imagePaths, BATCH_SIZE);

for (const batch of batches) {
await optimizeBatch(batch);
}

console.log(
`${imagePaths.length} image${imagePaths.length !== 1 ? 's' : ''} processed`
);
})().catch(error => {
console.error(error);
process.exit(1);
});