logo
HomeSkillsCarrieraPortfolioBlogContatti
  • Home
  • Skills
  • Carriera
  • Portfolio
  • Blog
  • Contatti

Blurkit

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

BlurHashThumbHashWASM
post_image
Codice Sorgente
Live Demo
Creato con ❤️ da Okazakee | Codice Sorgente
CMS|Informativa sulla Privacy
16/05/2026
1
12
Codice Sorgente
Live Demo

La Storia

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.

Cosa Lo Rende Speciale

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. ✨

L'Arte Tecnica

Funzionalità Principali

  • 🔄 API Universale: Una funzione encode per Node, Deno, browser, edge e WASM
  • 📦 Doppio Algoritmo: Supporto per BlurHash e ThumbHash con interfacce identiche
  • ⚙️ Codec WASM Opzionali: Backend di decode leggero distribuito come pacchetto opzionale separato
  • 🧩 Elaborazione Batch: API batch fail-fast e a successo parziale per pipeline di manifest
  • 🔧 CLI e Generazione Manifest: Genera placeholder da file locali con supporto glob

Il Segreto del Successo

Astrazione del Runtime:

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.

Strategia dei Codec:

  • Node e Bun usano sharp per decode a velocità nativa
  • Deno, edge e WASM usano un pacchetto companion di codec WASM (dipendenza peer opzionale)
  • Browser e edge preferiscono ImageDecoder e OffscreenCanvas nativi, con fallback a WASM
  • Ogni backend implementa lo stesso contratto encode

Design Cache e Batch:

  • L'interfaccia cache è volutamente di basso livello (get e set) con invalidazione a carico del chiamante
  • encodeMany è fail-fast e encodeManySettled restituisce envelope di risultato per singolo elemento
  • Una CLI integrata wrappa il pipeline per elaborazione file locali e generazione manifest

Sfide e Soluzioni

Il Puzzle Multi-Runtime 🧩

  • Sfida: Ogni runtime ha capacità di decode, modelli di threading e accesso al filesystem diversi
  • Soluzione: Progettato un layer di risoluzione dell'input pluggable e conditional exports che selezionano il backend corretto in fase di build
  • Risultato: Uno stesso specifier di import funziona identicamente su sette runtime

La Separazione dei Codec WASM 🔌

  • Sfida: I codec WASM aggiungono dimensione al bundle che i consumatori browser e edge non necessitano
  • Soluzione: Estratto il decode WASM in una dipendenza peer opzionale separata (blurkit-wasm-codecs)
  • Risultato: La libreria core rimane leggera e il supporto WASM si carica solo quando necessario

Il Contratto di Cache 💾

  • Sfida: Le strategie di invalidazione della cache sono specifiche del dominio e non esiste una soluzione universale
  • Soluzione: Mantenuta l'interfaccia cache minimale (get e set) e distribuito esattamente un helper in memoria
  • Risultato: I chiamanti gestiscono la propria politica di persistenza e la libreria rimane non opinionata

Approfondimento Tecnico

Risoluzione tramite Conditional Exports

Il cuore della strategia multi-runtime è nella mappa di export:

Code
Copiato!
{
  ".": {
    "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.

Pipeline di Encode

Una vista semplificata del flusso:

Code
Copiato!
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 }
}

Risoluzione dell'Input per Runtime

RuntimePercorso FileBufferBlob/FileURL
Node✅✅❌✅
Deno✅✅❌✅
Browser❌❌✅✅ (CORS)
Cloudflare❌❌❌✅

Dietro le Quinte

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.