ldc-legaulois/vite-plugin-webp.ts
2025-07-17 12:07:46 +02:00

125 lines
4.4 KiB
TypeScript

import type { OutputAsset, OutputBundle, OutputOptions } from "rollup";
import * as cliProgress from "cli-progress";
import fs from "fs";
import path from "path";
import sharp from "sharp";
interface Manifest {
[key: string]: {
file: string;
src: string;
};
}
type convertImagesToWebpType = {
name: string;
apply: string;
generateBundle: (_: OutputOptions, bundle: OutputBundle) => Promise<void>;
closeBundle: () => void;
}
/** @var string[] imageExtensions Formats accepted for the .webp convertion. */
const imageExtensions = [".jpg", ".jpeg", ".png"];
export default function convertImagesToWebp(): convertImagesToWebpType {
return {
name: "convert-images-to-webp",
apply: "build",
/**
* Generate all webp images during the compilation.
*
* @param _ OutputOptions
* @param bundle OutputBundle
* @return Promise<void>
*/
generateBundle: async (_: OutputOptions, bundle: OutputBundle): Promise<void> => {
const generatedFiles: string[] = [];
const imageFiles = Object.entries(bundle).filter(([fileName, asset]) => {
const ext = path.extname(fileName).toLowerCase();
return asset.type === "asset" && imageExtensions.includes(ext);
});
console.log("\ngenerate webp images:");
// Init progress bar.
const bar = new cliProgress.SingleBar({
format: "{bar} {percentage}%",
barCompleteChar: "\u2588",
barIncompleteChar: "\u2591",
}, cliProgress.Presets.shades_classic);
bar.start(imageFiles.length, 0);
// Convert each files into webp and save a copy to the same path.
for (const [fileName, asset] of imageFiles) {
const ext = path.extname(fileName).toLowerCase();
const assetPath = path.resolve("public/build", fileName);
const webpPath = assetPath.replace(ext, ".webp");
const dir = path.dirname(webpPath);
fs.mkdirSync(dir, { recursive: true });
try {
await sharp(Buffer.from((asset as OutputAsset).source))
.webp()
.toFile(webpPath);
generatedFiles.push(webpPath);
} catch (err) {
console.error(`error conversion WebP for ${fileName} :`, err);
}
// Increase the status progress bar.
bar.increment();
}
// Stop the progress bar and notify terminal user.
bar.stop();
console.log(`\x1b[32m✓ \x1b[37m${generatedFiles.length} webp images created.`);
},
/**
* Update the manifest.json after the compilation.
*
* @returns void
*/
closeBundle: (): void => {
// Get the manifest.json file.
const manifestPath = path.resolve("public/build/manifest.json");
if (!fs.existsSync(manifestPath)) {
console.error("manifest.json doesn't exist");
return;
}
// Get all entries from the manifest.json file.
const manifest: Manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
const entries = Object.entries(manifest);
const filesToUpdate: string[] = [];
// Duplicate each image entries for its equivalent in .webp format.
for (const [originalSrc, entry] of entries) {
const { file } = entry;
const ext = path.extname(file).toLowerCase();
if (!imageExtensions.includes(ext)) continue;
const webpFile = file.replace(ext, ".webp");
const webpPath = path.resolve("public/build", webpFile);
if (fs.existsSync(webpPath)) {
const webpKey = originalSrc.replace(ext, ".webp");
manifest[webpKey] = {
file: webpFile,
src: webpKey,
};
filesToUpdate.push(webpKey);
}
}
// Save the manifest.json updated and notify terminal user.
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
console.log("\x1b[32m✓ \x1b[37mmanifest.json updated");
},
};
}