118 lines
4.2 KiB
JavaScript
118 lines
4.2 KiB
JavaScript
// @ts-check
|
|
import fs from "node:fs"
|
|
import { fileURLToPath } from "node:url"
|
|
import { posix as path, resolve } from "node:path"
|
|
import { saveAndCopyAsset } from "./utils/saveAndCopyAsset.js"
|
|
import vitePluginAstroImageTools, { store } from "../plugin/index.js"
|
|
import pMap from "p-map"
|
|
|
|
const filename = fileURLToPath(import.meta.url);
|
|
const astroViteConfigsPath = resolve(filename, "../../astroViteConfigs.js");
|
|
|
|
import { get_cached, set_cached, get_path_cached } from '@polymech/cache'
|
|
|
|
export default {
|
|
name: "imagetools",
|
|
hooks: {
|
|
"astro:config:setup": async function ({ config, command, updateConfig }) {
|
|
const environment = command;
|
|
const isSsrBuild =
|
|
command === "build" && !!config.adapter && config.output === "server";
|
|
|
|
let projectBase = path.normalize(config.base);
|
|
if (projectBase.startsWith("./")) projectBase = projectBase.slice(1);
|
|
if (!projectBase.startsWith("/")) projectBase = "/" + projectBase;
|
|
if (projectBase.endsWith("/")) projectBase = projectBase.slice(0, -1);
|
|
|
|
const astroViteConfigs = {
|
|
environment,
|
|
isSsrBuild,
|
|
projectBase,
|
|
publicDir: fileURLToPath(config.publicDir.href),
|
|
rootDir: fileURLToPath(config.root.href),
|
|
};
|
|
|
|
await fs.promises.writeFile(
|
|
astroViteConfigsPath,
|
|
`export default ${JSON.stringify(astroViteConfigs)}`
|
|
);
|
|
|
|
updateConfig({
|
|
vite: {
|
|
plugins: [vitePluginAstroImageTools],
|
|
},
|
|
});
|
|
},
|
|
|
|
"astro:build:done": async function closeBundle() {
|
|
const { default: astroViteConfigs } = await import(
|
|
// @ts-ignore
|
|
"../astroViteConfigs.js"
|
|
);
|
|
|
|
const { mode, outDir, assetsDir, isSsrBuild } = astroViteConfigs;
|
|
|
|
if (mode === "production") {
|
|
const allEntries = [...store.entries()];
|
|
const assetPaths = allEntries.filter(
|
|
([, { hash = null } = {}]) => hash
|
|
);
|
|
|
|
await pMap(
|
|
assetPaths,
|
|
async ([assetPath, { hash, image, buffer }]) => {
|
|
// Retry mechanism with exponential backoff for image processing
|
|
const retryWithBackoff = async (fn, retries = 3, baseDelay = 10) => {
|
|
for (let i = 0; i < retries; i++) {
|
|
try {
|
|
return await fn();
|
|
} catch (error) {
|
|
if (i === retries - 1) {
|
|
throw error; // Last attempt failed
|
|
}
|
|
|
|
// Check if it's a vips/sharp related error that we should retry
|
|
const isRetryableError = error.message.includes('vips') ||
|
|
error.message.includes('sharp') ||
|
|
error.message.includes('UNKNOWN: unknown error') ||
|
|
error.code === 'EBUSY' ||
|
|
error.code === 'ENOENT' ||
|
|
error.errno === -4094;
|
|
|
|
if (!isRetryableError) {
|
|
throw error; // Don't retry non-transient errors
|
|
}
|
|
|
|
const delay = baseDelay * Math.pow(2, i); // Exponential backoff
|
|
console.warn(`Retry attempt ${i + 1}/${retries} for image ${assetPath} after ${delay}ms delay:`, error.message);
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
}
|
|
}
|
|
};
|
|
|
|
try {
|
|
await retryWithBackoff(async () => {
|
|
await saveAndCopyAsset(
|
|
hash,
|
|
image,
|
|
buffer,
|
|
outDir,
|
|
assetsDir,
|
|
assetPath,
|
|
isSsrBuild
|
|
);
|
|
});
|
|
console.log(`Image processed: ${assetPath}`);
|
|
} catch (error) {
|
|
console.error(`Failed to process image ${assetPath} after retries:`, error);
|
|
// Continue processing other images even if one fails
|
|
}
|
|
},
|
|
// higher concurrency causes sharp/vips errors as well
|
|
{ concurrency: 1 }
|
|
);
|
|
}
|
|
},
|
|
},
|
|
};
|