Skip to content
File

Blob: src/lib/vips.ts

typescript46 lines
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.
10type Vips = any;
11 
12let vipsPromise: Promise<Vips | null> | null = null;
13 
14function 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 
30export 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}