Skip to content

Instantly share code, notes, and snippets.

@espresso3389
Created October 1, 2024 16:06
Show Gist options
  • Save espresso3389/e5ac100f1c5deac11976f76f50f3acda to your computer and use it in GitHub Desktop.
Save espresso3389/e5ac100f1c5deac11976f76f50f3acda to your computer and use it in GitHub Desktop.
Node.js program that converts HTML with img files to a single HTML that uses embedded image data using data protocol
const fs = require('fs').promises;
const path = require('path');
const cheerio = require('cheerio');
const sharp = require('sharp');
const zlib = require('zlib');
const util = require('util');
const gzip = util.promisify(zlib.gzip);
async function optimizeAndConvertToDataURI(imagePath, baseDir, useLossless) {
try {
const fullPath = path.join(baseDir, imagePath);
const fileContent = await fs.readFile(fullPath);
const fileExt = path.extname(imagePath).toLowerCase();
let optimizedBuffer;
let mimeType;
if (fileExt === '.svg') {
// Compress SVG to SVGZ
optimizedBuffer = await gzip(fileContent);
mimeType = 'image/svg+xml';
} else {
const image = sharp(fileContent);
const metadata = await image.metadata();
let formats;
if (useLossless) {
formats = ['png'];
} else {
formats = ['webp', 'jpeg', 'png'];
}
let smallestBuffer = await image.toBuffer();
mimeType = `image/${metadata.format}`;
for (const format of formats) {
const options = format === 'png' ? { lossless: true } : { quality: 80 };
const testBuffer = await image[format](options).toBuffer();
if (testBuffer.length < smallestBuffer.length) {
smallestBuffer = testBuffer;
mimeType = `image/${format}`;
}
}
optimizedBuffer = smallestBuffer;
}
return `data:${mimeType};base64,${optimizedBuffer.toString('base64')}`;
} catch (error) {
console.error(`Error processing image file ${imagePath}:`, error.message);
return imagePath; // Return original path if there's an error
}
}
async function convertHtml(inputHtml, baseDir, useLossless) {
const $ = cheerio.load(inputHtml);
const imgPromises = [];
$('img').each((index, element) => {
const src = $(element).attr('src');
if (src && !src.startsWith('data:') && !src.startsWith('http://') && !src.startsWith('https://')) {
const promise = optimizeAndConvertToDataURI(src, baseDir, useLossless).then(dataUri => {
$(element).attr('src', dataUri);
});
imgPromises.push(promise);
}
});
await Promise.all(imgPromises);
return $.html();
}
async function processHtmlFile(inputFile, outputFile, useLossless) {
try {
const inputHtml = await fs.readFile(inputFile, 'utf8');
const baseDir = path.dirname(inputFile);
const outputHtml = await convertHtml(inputHtml, baseDir, useLossless);
await fs.writeFile(outputFile, outputHtml);
console.log(`Processed HTML saved to ${outputFile}`);
} catch (error) {
console.error('Error processing HTML file:', error.message);
}
}
function printUsage() {
console.log('Usage: node script.js <input_file> <output_file> [--lossless]');
console.log('Example: node script.js input.html output.html');
console.log('Example with lossless compression: node script.js input.html output.html --lossless');
}
// Main execution
if (process.argv.length < 4 || process.argv.length > 5) {
printUsage();
process.exit(1);
}
const inputFile = process.argv[2];
const outputFile = process.argv[3];
const useLossless = process.argv[4] === '--lossless';
processHtmlFile(inputFile, outputFile, useLossless);
@espresso3389
Copy link
Author

The code is totally generated by calude.ai:
https://claude.site/artifacts/927180d1-753a-4453-a9cb-bc846e49c1b5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment