fix:imagetools

This commit is contained in:
lovebird 2025-03-18 10:09:29 +01:00
parent 0f6bcc4267
commit a64acb9f71
11 changed files with 141 additions and 138 deletions

File diff suppressed because one or more lines are too long

8
package-lock.json generated
View File

@ -67,6 +67,7 @@
"sharp": "^0.29.3", "sharp": "^0.29.3",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"tailwindcss": "^4.0.7", "tailwindcss": "^4.0.7",
"ts-retry": "^6.0.0",
"type-fest": "^4.34.1", "type-fest": "^4.34.1",
"vite": "^6.1.1", "vite": "^6.1.1",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
@ -15132,6 +15133,12 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/ts-retry": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/ts-retry/-/ts-retry-6.0.0.tgz",
"integrity": "sha512-WsVRE/P+VNYbiQC3E6TeIXBRCQj7vzjN4MlXd84AC88K7WwuWShN7A3Q/QSV/yd1hjO8qn2Cevdqny2HMwKUaA==",
"license": "MIT"
},
"node_modules/tsconfck": { "node_modules/tsconfck": {
"version": "3.1.5", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz",
@ -16759,6 +16766,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@astropub/codecs": "0.4.4", "@astropub/codecs": "0.4.4",
"@polymech/fs": "file:../../../polymech-mono/packages/fs",
"file-type": "17.1.1", "file-type": "17.1.1",
"find-cache-dir": "3.3.2", "find-cache-dir": "3.3.2",
"find-up": "^6.3.0", "find-up": "^6.3.0",

View File

@ -3,6 +3,9 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"exports": {
".": "./src/index.js"
},
"scripts": { "scripts": {
"dev": "astro dev --mode dev --host=0.0.0.0 --o-images=medium", "dev": "astro dev --mode dev --host=0.0.0.0 --o-images=medium",
"start": "astro dev", "start": "astro dev",
@ -77,6 +80,7 @@
"sharp": "^0.29.3", "sharp": "^0.29.3",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"tailwindcss": "^4.0.7", "tailwindcss": "^4.0.7",
"ts-retry": "^6.0.0",
"type-fest": "^4.34.1", "type-fest": "^4.34.1",
"vite": "^6.1.1", "vite": "^6.1.1",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",

View File

@ -4,12 +4,7 @@ import objectHash from "object-hash";
import getImageSources from "./getImageSources.js"; import getImageSources from "./getImageSources.js";
import getProcessedImage from "./getProcessedImage.js"; import getProcessedImage from "./getProcessedImage.js";
import getArtDirectedImages from "./getArtDirectedImages.js"; import getArtDirectedImages from "./getArtDirectedImages.js";
import pMap from "p-map";
const imagesData = new Map(); const imagesData = new Map();
const delay = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
export default async function ({ export default async function ({
src, src,
type, type,
@ -23,29 +18,26 @@ export default async function ({
artDirectives, artDirectives,
transformConfigs, transformConfigs,
}) { }) {
try {
const args = Array.from(arguments); const args = Array.from(arguments);
const hash = objectHash(args); const hash = objectHash(args);
if (imagesData.has(hash)) { if (imagesData.has(hash)) {
return imagesData.get(hash); return imagesData.get(hash);
} }
// const start = performance.now();
const { path, base, rest, image, imageWidth, imageHeight, imageFormat } = const { path, base, rest, image, imageWidth, imageHeight, imageFormat } =
await getProcessedImage(src, transformConfigs); await getProcessedImage(src, transformConfigs);
await delay();
src = path; src = path;
rest.aspect = `${imageWidth / imageHeight}`; rest.aspect = `${imageWidth / imageHeight}`;
if (!fallbackFormat) { if (!fallbackFormat) {
fallbackFormat = imageFormat; fallbackFormat = imageFormat;
} }
// Fetch both image sources and art-directed images const [mainImage, artDirectedImages] = await Promise.all([
const [mainImage, artDirectedImages] = await pMap( getImageSources(
[
async () =>
await getImageSources(
src, src,
base, base,
image, image,
@ -60,8 +52,7 @@ export default async function ({
includeSourceFormat, includeSourceFormat,
rest rest
), ),
async () => { getArtDirectedImages(
return await getArtDirectedImages(
artDirectives, artDirectives,
placeholder, placeholder,
format, format,
@ -71,28 +62,18 @@ export default async function ({
includeSourceFormat, includeSourceFormat,
formatOptions, formatOptions,
rest rest
); ),
}, ]);
],
async (task) => await task(), const images = [...artDirectedImages, mainImage];
{ concurrency: 1 }
);
// Ensure artDirectedImages is an array
const images = Array.isArray(artDirectedImages) ? [...artDirectedImages, mainImage] : [mainImage];
const uuid = crypto.randomBytes(4).toString("hex").toUpperCase(); const uuid = crypto.randomBytes(4).toString("hex").toUpperCase();
const returnObject = { const returnObject = {
uuid, uuid,
images, images,
}; };
imagesData.set(hash, returnObject); imagesData.set(hash, returnObject)
//const end = performance.now();
//console.log( `Responsive Image sets generated for ${type} at ${args[0].src} in ${end - start}ms`);
return returnObject; return returnObject;
} catch (error) {
console.error("Error processing images:", error);
debugger
throw error;
}
} }

View File

@ -3,8 +3,9 @@ import getSrcset from "./getSrcset.js";
import getConfigOptions from "./getConfigOptions.js"; import getConfigOptions from "./getConfigOptions.js";
import getFallbackImage from "./getFallbackImage.js"; import getFallbackImage from "./getFallbackImage.js";
import pMap from "p-map"; import pMap from "p-map";
import { DEFAULT_IO_DELAY } from "../../constants.js";
function delay(ms = 150) { function delay(ms = DEFAULT_IO_DELAY) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }

View File

@ -6,28 +6,32 @@ import { getSrcPath } from "./getSrcPath.js";
import getResolvedSrc from "./getResolvedSrc.js"; import getResolvedSrc from "./getResolvedSrc.js";
import { cwd, sharp } from "../../utils/runtimeChecks.js"; import { cwd, sharp } from "../../utils/runtimeChecks.js";
import throwErrorIfUnsupported from "./throwErrorIfUnsupported.js"; import throwErrorIfUnsupported from "./throwErrorIfUnsupported.js";
import { retryAsync } from "ts-retry";
import { DEFAULT_IO_DELAY } from "../../constants.js";
const { getImageDetails } = await (sharp const { getImageDetails } = await (sharp
? import("./imagetools.js") ? import("./imagetools.js")
: import("./codecs.js")); : import("./codecs.js"));
export default async function getProcessedImage(src, transformConfigs) {
throwErrorIfUnsupported(src, extname(src).slice(1));
export async function _getProcessedImage(src, transformConfigs) {
throwErrorIfUnsupported(src, extname(src).slice(1));
let base; let base;
if (src.match("(http://|https://|data:image/).*")) { if (src.match("(http://|https://|data:image/).*")) {
({ src, base } = await getResolvedSrc(src)) ({ src, base } = await getResolvedSrc(src));
} else { } else {
const { const {
default: { isSsrBuild }, default: { isSsrBuild },
} = await import("../../astroViteConfigs.js") } = await import("../../astroViteConfigs.js");
if (isSsrBuild) { if (isSsrBuild) {
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url);
const assetPath = resolve(filename, "../../client") + src const assetPath = resolve(filename, "../../client") + src;
src = "/" + relative(cwd, assetPath); src = "/" + relative(cwd, assetPath);
} }
} }
const { const {
w, w,
h, h,
@ -57,3 +61,8 @@ export default async function getProcessedImage(src, transformConfigs) {
imageFormat, imageFormat,
}; };
} }
export default async function getProcessedImage(src, transformConfigs) {
//@todo : hiccups in vips/sharp
return retryAsync(() => _getProcessedImage(src, transformConfigs), { delay: DEFAULT_IO_DELAY, maxTry: 3 })
}

View File

@ -13,9 +13,7 @@ const { fileTypeFromBuffer } = await import("file-type");
export default async function getResolvedSrc(src) { export default async function getResolvedSrc(src) {
const token = crypto.createHash("md5").update(src).digest("hex"); const token = crypto.createHash("md5").update(src).digest("hex");
let filepath = fsCachePath + token; let filepath = fsCachePath + token;
const fileExists = (() => { const fileExists = (() => {
for (const type of supportedImageTypes) { for (const type of supportedImageTypes) {
const fileExists = fs.existsSync(filepath + `.${type}`); const fileExists = fs.existsSync(filepath + `.${type}`);
@ -28,18 +26,14 @@ export default async function getResolvedSrc(src) {
} }
})(); })();
try {
if (!fileExists) { if (!fileExists) {
const buffer = Buffer.from(await (await fetch(src)).arrayBuffer()); const buffer = Buffer.from(await (await fetch(src)).arrayBuffer());
const { ext } = (await fileTypeFromBuffer(buffer)) || {}; const { ext } = (await fileTypeFromBuffer(buffer)) || {};
throwErrorIfUnsupported(src, ext); throwErrorIfUnsupported(src, ext);
filepath += `.${ext}`; filepath += `.${ext}`;
fs.writeFileSync(filepath, buffer); fs.writeFileSync(filepath, buffer);
} }
const base = /^https?:/.test(src) const base = /^https?:/.test(src)
? parse(new URL(src).pathname).name ? parse(new URL(src).pathname).name
: undefined; : undefined;
@ -47,4 +41,8 @@ export default async function getResolvedSrc(src) {
src = join("/", relative(cwd, filepath)); src = join("/", relative(cwd, filepath));
return { src, base }; return { src, base };
} catch (e) {
console.error(`Error while resolving src: ${src} : ${e.message}`)
return null
}
} }

View File

@ -0,0 +1 @@
export const DEFAULT_IO_DELAY = 350;

View File

@ -5,6 +5,7 @@ import { posix as path, resolve } from "node:path"
import { saveAndCopyAsset } from "./utils/saveAndCopyAsset.js" import { saveAndCopyAsset } from "./utils/saveAndCopyAsset.js"
import vitePluginAstroImageTools, { store } from "../plugin/index.js" import vitePluginAstroImageTools, { store } from "../plugin/index.js"
import pMap from "p-map" import pMap from "p-map"
import { DEFAULT_IO_DELAY } from "../constants.js"
const filename = fileURLToPath(import.meta.url); const filename = fileURLToPath(import.meta.url);
const astroViteConfigsPath = resolve(filename, "../../astroViteConfigs.js"); const astroViteConfigsPath = resolve(filename, "../../astroViteConfigs.js");
@ -60,7 +61,7 @@ export default {
assetPaths, assetPaths,
async ([assetPath, { hash, image, buffer }]) => { async ([assetPath, { hash, image, buffer }]) => {
// delay, otherwise unknown errors occur (sharp/vips) // delay, otherwise unknown errors occur (sharp/vips)
await new Promise((resolve) => setTimeout(resolve, 150)); await new Promise((resolve) => setTimeout(resolve, DEFAULT_IO_DELAY));
try { try {
await saveAndCopyAsset( await saveAndCopyAsset(
hash, hash,

View File

@ -36,7 +36,7 @@ export const PRODUCTS_TARGET = (lang) => `./content/${lang}/products`
// Product assets // Product assets
export const ASSETS_LOCALE = true export const ASSETS_LOCAL = false
// OSRL - Language // OSRL - Language
export const IS_DEV = true export const IS_DEV = true

View File

@ -14,7 +14,7 @@ import { sync as mv } from '@polymech/fs/move'
import { logger } from '@/base/index.js' import { logger } from '@/base/index.js'
import { removeArrayValues, removeArrays, removeBufferValues, removeEmptyObjects } from '@/base/objects.js' import { removeArrayValues, removeArrays, removeBufferValues, removeEmptyObjects } from '@/base/objects.js'
import { ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT, DEFAULT_IMAGE_URL, ASSETS_LOCALE } from '../app/config.js' import { ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT, DEFAULT_IMAGE_URL, ASSETS_LOCAL } from '../app/config.js'
import { GalleryImage, MetaJSON } from './images.js' import { GalleryImage, MetaJSON } from './images.js'
import { validateFilename, sanitizeFilename } from "@polymech/fs/utils" import { validateFilename, sanitizeFilename } from "@polymech/fs/utils"
@ -138,7 +138,7 @@ export const gallery = async (
)) ))
} }
if (!ASSETS_LOCALE) { if (!ASSETS_LOCAL) {
galleryFiles = await pMap(galleryFiles, async (f) => (await default_filter(assetUrl(f))) ? f : null, { concurrency: 5 }) galleryFiles = await pMap(galleryFiles, async (f) => (await default_filter(assetUrl(f))) ? f : null, { concurrency: 5 })
galleryFiles = galleryFiles.filter((f) => f !== null) galleryFiles = galleryFiles.filter((f) => f !== null)
galleryFiles = default_sort(galleryFiles) galleryFiles = default_sort(galleryFiles)
@ -181,7 +181,7 @@ export const gallery = async (
delete imageMeta.exif.icc delete imageMeta.exif.icc
delete imageMeta.exif.xmp delete imageMeta.exif.xmp
delete imageMeta.exif.iptc delete imageMeta.exif.iptc
const src = ASSETS_LOCALE ? filePath : await image_url(assetUrl(file)) const src = ASSETS_LOCAL ? filePath : await image_url(assetUrl(file))
const ret: GalleryImage = const ret: GalleryImage =
{ {
name: path.parse(file).name, name: path.parse(file).name,