polymech-astro/packages/imagetools_3/plugin/hooks/load.js
2025-12-25 02:08:14 +01:00

198 lines
5.4 KiB
JavaScript

// @ts-check
import path from "node:path";
import objectHash from "object-hash";
import { store } from "../index.js";
import { getCachedBuffer } from "../utils/cache.js";
import { getSrcPath } from "../../api/utils/getSrcPath.js";
import { getAssetPath, getConfigOptions } from "../utils/shared.js";
import { sharp, supportedImageTypes } from "../../utils/runtimeChecks.js";
import { get_cached_object, set_cached_object } from '@polymech/cache';
const { getLoadedImage, getTransformedImage } = await (sharp
? import("../utils/imagetools.js")
: import("../utils/codecs.js"));
export default async function load(id) {
try {
var fileURL = new URL(`file://${id}`);
} catch (error) {
return null;
}
const { search, searchParams } = fileURL;
id = id.replace(search, "");
const ext = path.extname(id).slice(1);
if (!supportedImageTypes.includes(ext)) return null;
const { default: astroViteConfigs } = await import(
// @ts-ignore
"../../astroViteConfigs.js"
);
const { environment, projectBase, assetFileNames } = astroViteConfigs;
const src = await getSrcPath(id);
const rootRelativePosixSrc = path.posix.normalize(
path.relative("", src).split(path.sep).join(path.posix.sep)
);
// Include search params in the hash calculation to force re-processing on change
const getHash = (width) =>
objectHash(
{ width, options, rootRelativePosixSrc, search },
// @ts-ignore
{ algorithm: "sha256" }
);
const base =
typeof arguments[1] === "string"
? arguments[1]
: path.basename(src, path.extname(src));
const config = Object.fromEntries(searchParams);
// Use the full ID (including search params) + src for the store key
// This ensures that the same file with different params is treated as different source
const storeKey = src + search;
const { image: loadedImage, width: imageWidth } =
store.get(storeKey) || store.set(storeKey, await getLoadedImage(src, ext)).get(storeKey);
const { type, widths, options, extension, raw, inline } = getConfigOptions(
config,
ext,
imageWidth
);
if (raw) {
const testConfig = { ...config };
delete testConfig.raw;
delete testConfig.inline;
delete testConfig.base64;
if (Object.keys(testConfig).length > 0) {
throw new Error(
"If raw is set, no other options can be set except inline and base64"
);
}
}
if (inline) {
if (widths.length > 1) {
throw new Error(
`The base64 or inline parameter can't be used with multiple widths`
);
}
const [width] = widths;
const hash = getHash(width);
if (store.has(hash)) {
return `export default "${store.get(hash)}"`;
} else {
const config = { width, ...options };
const { image, buffer } = raw
? {
image: sharp ? loadedImage : null,
buffer: !sharp ? loadedImage.data : null,
}
: await getTransformedImage({
src,
image: loadedImage,
config,
type,
})
const dataUri = `data:${type};base64,${(
buffer || (await getCachedBuffer(hash, image))
).toString("base64")}`
store.set(hash, dataUri)
return `export default "${dataUri}"`
}
} else {
const sources = await Promise.all(
widths.map(async (width) => {
const hash = getHash(width);
const assetPath = getAssetPath(
base,
assetFileNames,
extension,
width,
hash
);
if (!store.has(assetPath)) {
const config = { width, ...options };
// Create cache key for this specific image transformation
const cacheKey = {
src: id,
width,
type,
extension,
options: objectHash(options)
};
let imageObject = null;
// Only use cache in production builds
if (environment === "production") {
imageObject = await get_cached_object(cacheKey, 'imagetools-plugin');
if (imageObject) {
console.log(`[imagetools-cache] Cache hit for ${assetPath}`);
}
}
// Process image if not cached
if (!imageObject) {
const { image, buffer } = raw
? {
image: sharp && loadedImage,
buffer: !sharp && loadedImage.data,
}
: await getTransformedImage({
src,
image: loadedImage,
config,
type,
});
imageObject = { hash, type, image, buffer };
// Cache the processed result in production
if (environment === "production") {
await set_cached_object(cacheKey, 'imagetools-plugin', imageObject, {
src: id,
width,
type,
timestamp: Date.now()
});
console.log(`[imagetools-cache] Cached ${assetPath}`);
}
}
store.set(assetPath, imageObject);
}
const modulePath = environment === "dev" ? assetPath : projectBase + assetPath;
return { width, modulePath };
})
);
const srcset =
sources.length > 1
? sources
.map(({ width, modulePath }) => `${modulePath} ${width}w`)
.join(", ")
: sources[0].modulePath;
return `export default "${srcset}"`;
}
}