Blurkit
Generazione universale di placeholder per immagini su runtime Node, Deno, browser, edge, Cloudflare e WASM. 🖼️

Generazione universale di placeholder per immagini su runtime Node, Deno, browser, edge, Cloudflare e WASM. 🖼️

blurkit nasce da una semplice frustrazione. Ogni volta che costruivo un'applicazione che caricava immagini, volevo quelle anteprime sfocate che rendono l'esperienza utente più raffinata. BlurHash e ThumbHash sono gli algoritmi di riferimento per questo scopo, ma integrarli in ambienti diversi come Node, Deno e browser richiedeva librerie specifiche per ogni piattaforma. Il codice non era mai portabile e la configurazione era sempre macchinosa. Così ho costruito la libreria che avrei voluto esistesse: una API di encode che funziona ovunque, con i dettagli interni del runtime gestiti sotto il cofano.
Immagina di scrivere la stessa chiamata encode per un job batch lato server e per un'anteprima di upload nel browser. Questo è il punto centrale. La libreria espone una singola funzione encode su sette runtime tra cui Node, Bun, Deno, browser, edge workers, Cloudflare Workers e WASM standalone. Astrae il decode delle immagini, l'elaborazione dei pixel e il calcolo dell'hash dietro un'interfaccia coerente. Cambia il runtime, mantieni il codice. ✨
Ogni runtime espone la stessa API pubblica da entrypoint dedicati (blurkit/node, blurkit/deno, blurkit/browser, ecc.). Le conditional exports in package.json risolvono l'implementazione corretta al momento dell'import. Non c'è configurazione, environment sniffing o controllo a runtime.
sharp per decode a velocità nativaImageDecoder e OffscreenCanvas nativi, con fallback a WASMget e set) con invalidazione a carico del chiamanteencodeMany è fail-fast e encodeManySettled restituisce envelope di risultato per singolo elementoblurkit-wasm-codecs)get e set) e distribuito esattamente un helper in memoriaIl cuore della strategia multi-runtime è nella mappa di export:
{
".": {
"deno": { "import": "./dist/root-deno.js" },
"browser": { "import": "./dist/root-browser.js" },
"worker": { "import": "./dist/root-edge.js" },
"node": { "import": "./dist/root-node.js" },
"default": { "import": "./dist/root-edge.js" }
}
}Ogni entrypoint importa solo i moduli specifici del runtime di cui ha bisogno. L'entrypoint Deno evita completamente i built-in di Node.js. L'entrypoint browser utilizza Web API. L'entrypoint edge è ottimizzato per ambienti worker.
Una vista semplificata del flusso:
async function encode(input: Input, options: Options): Promise<BlurResult> {
const reader = resolveInputReader(input) // consapevole del runtime
const raw = await reader.decode(options) // specifico del codec
const hash = computeHash(raw, options) // BlurHash o ThumbHash
const dataURL = await renderPlaceholder(raw, options) // specifico del formato
return { algorithm, hash, dataURL, width, height }
}| Runtime | Percorso File | Buffer | Blob/File | URL |
|---|---|---|---|---|
| Node | ✅ | ✅ | ❌ | ✅ |
| Deno | ✅ | ✅ | ❌ | ✅ |
| Browser | ❌ | ❌ | ✅ | ✅ (CORS) |
| Cloudflare | ❌ | ❌ | ❌ | ✅ |
Questo progetto è stato un esercizio di astrazione disciplinata. La parte più difficile non è stata implementare BlurHash o ThumbHash. Quegli algoritmi sono ben documentati. La vera sfida è stata progettare un pipeline che potesse degradare gracefulmente attraverso ambienti diversi senza far trapelare complessità al chiamante. Ogni vincolo specifico del runtime è esposto nell'API piuttosto che nascosto: Node richiede sharp, Deno richiede il pacchetto codec WASM, il browser rifiuta percorsi file e dipende da CORS. La libreria non finge che queste differenze non esistano. Le rende semplicemente gestibili.