File
Blob: src/lib/vips.ts
| 1 | /** |
| 2 | * Lazy, guarded loader for wasm-vips (libvips compiled to WASM — the same engine |
| 3 | * as `sharp`). It's used as a progressive enhancement for high-quality (lanczos3) |
| 4 | * downscaling. Initialization is attempted once; on any failure — or in browsers |
| 5 | * without cross-origin isolation / SharedArrayBuffer — we resolve to null and the |
| 6 | * caller falls back to the canvas resampler. |
| 7 | */ |
| 8 | |
| 9 | // wasm-vips ships rich types, but we treat it loosely at this dynamic boundary. |
| 10 | type Vips = any; |
| 11 | |
| 12 | let vipsPromise: Promise<Vips | null> | null = null; |
| 13 | |
| 14 | function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> { |
| 15 | return new Promise<T>((resolve, reject) => { |
| 16 | const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms); |
| 17 | promise.then( |
| 18 | (value) => { |
| 19 | clearTimeout(timer); |
| 20 | resolve(value); |
| 21 | }, |
| 22 | (err) => { |
| 23 | clearTimeout(timer); |
| 24 | reject(err); |
| 25 | }, |
| 26 | ); |
| 27 | }); |
| 28 | } |
| 29 | |
| 30 | export function loadVips(): Promise<Vips | null> { |
| 31 | if (vipsPromise) return vipsPromise; |
| 32 | vipsPromise = (async () => { |
| 33 | try { |
| 34 | const mod: any = await import("wasm-vips"); |
| 35 | const factory = (mod.default ?? mod) as (opts?: unknown) => Promise<Vips>; |
| 36 | // dynamicLibraries: [] skips the optional HEIF/JXL/SVG modules we don't need. |
| 37 | const vips = await withTimeout(factory({ dynamicLibraries: [] }), 20_000, "wasm-vips init"); |
| 38 | return vips ?? null; |
| 39 | } catch (err) { |
| 40 | console.warn("[wimp] wasm-vips unavailable — falling back to the canvas engine.", err); |
| 41 | return null; |
| 42 | } |
| 43 | })(); |
| 44 | return vipsPromise; |
| 45 | } |