I built my first client-side image compressor using pure JavaScript canvas operations. It worked for small images. On a 10MB JPEG, it froze the browser tab for 3 seconds. Moving the compression to a Web Worker with a WebAssembly-compiled codec brought that to 300ms with zero UI freeze. Understanding why requires looking at what the browser is actually doing when you drop a file.
Tools built with this architecture.
All FileMint tools use Web Workers and WASM where appropriate. No upload. No server.
View All Local Tools โThe File API: Reading Files Without a Network Request
1// Reading a file from disk into browser memory2// This uses the File API โ no network request involved3ย 4const fileInput = document.getElementById('file-input') as HTMLInputElement;5ย 6fileInput.addEventListener('change', async () => {7 const file = fileInput.files?.[0];8 if (!file) return;9ย 10 // Method 1: ArrayBuffer (for binary processing โ PDFs, images)11 const buffer = await file.arrayBuffer();12 // โ Raw binary data in memory, ready for WASM libraries13ย 14 // Method 2: Text (for text files โ JSON, CSV, YAML)15 const text = await file.text();16 // โ UTF-8 string17ย 18 // Method 3: DataURL (for displaying in <img> or <canvas>)19 const url = await new Promise<string>(resolve => {20 const reader = new FileReader();21 reader.onload = () => resolve(reader.result as string);22 reader.readAsDataURL(file);23 });24ย 25 // File metadata โ read without loading file content26 console.log(file.name); // "document.pdf"27 console.log(file.size); // 1048576 (bytes)28 console.log(file.type); // "application/pdf"29 console.log(file.lastModified); // Unix timestamp30});WebAssembly: Running C++ Code in the Browser
WebAssembly is a binary format that browsers can compile and execute at near-native speed. It was designed specifically for performance-critical code, the kind of computational work that file processing requires.
1// Loading and using a WebAssembly module2// Example: a hypothetical image processing WASM module3ย 4async function loadWasmModule(): Promise<WebAssembly.Instance> {5 // Fetch the compiled WASM binary6 const response = await fetch('/codecs/image-processor.wasm');7 const wasmBuffer = await response.arrayBuffer();8ย 9 // Compile and instantiate โ V8 compiles to machine code10 const { instance } = await WebAssembly.instantiate(wasmBuffer, {11 env: {12 // WASM imports โ memory allocators, etc.13 malloc: (size: number) => 0, // Simplified14 }15 });16ย 17 return instance;18}19ย 20// Real-world: using squoosh's WASM codecs for image compression21// The squoosh library wraps MozJPEG, libavif, libwebp in WASM22import { ImagePool } from '@squoosh/lib';23ย 24async function compressImage(file: File): Promise<Blob> {25 const pool = new ImagePool();26 const image = pool.ingestImage(await file.arrayBuffer());27ย 28 await image.encode({29 webp: { quality: 85 }, // MozWebP at quality 8530 });31ย 32 const { webp } = await image.encodedWith;33 await pool.close();34ย 35 return new Blob([webp.binary], { type: 'image/webp' });36}Web Workers: Background Threads Without Blocking the UI
1// main.ts โ main thread2const worker = new Worker(new URL('./compression.worker.ts', import.meta.url));3ย 4// Transfer the ArrayBuffer to the worker (zero-copy โ no duplication)5const buffer = await file.arrayBuffer();6worker.postMessage({ buffer, quality: 85 }, [buffer]);7// โ Passing buffer in the "transfer" array transfers ownership8// The buffer is no longer accessible in the main thread after this9ย 10worker.addEventListener('message', (event) => {11 const { compressed, duration } = event.data;12 console.log(`Compressed in ${duration}ms`);13 downloadFile(new Blob([compressed], { type: 'image/webp' }));14});15ย 16// compression.worker.ts โ background thread17// This runs on a separate OS thread โ UI stays responsive18self.addEventListener('message', async (event) => {19 const { buffer, quality } = event.data;20 const start = performance.now();21ย 22 // Heavy WASM compression โ safe here, not on the main thread23 const compressed = await compressWithWasm(buffer, quality);24ย 25 self.postMessage({26 compressed,27 duration: performance.now() - start28 }, [compressed]); // Transfer result back (zero-copy)29});OffscreenCanvas: GPU Rendering Off the Main Thread
1// OffscreenCanvas allows GPU canvas operations in a Web Worker2// (Normally canvas is restricted to the main thread)3ย 4// In a Web Worker:5const canvas = new OffscreenCanvas(1920, 1080);6const ctx = canvas.getContext('2d');7ย 8// Draw image data to canvas (off main thread)9ctx.drawImage(bitmap, 0, 0);10ย 11// Export as WebP โ GPU-accelerated, off main thread12const blob = await canvas.convertToBlob({ type: 'image/webp', quality: 0.85 });13ย 14// Transfer blob back to main thread for download15self.postMessage({ blob }, []);Triggering Downloads Without a Server
1// Writing processed data back to the user's filesystem2// No server involved โ the browser handles the download3ย 4function downloadBlob(blob: Blob, filename: string): void {5 // Create a temporary object URL pointing to the blob in memory6 const url = URL.createObjectURL(blob);7ย 8 // Trigger a download via a programmatic click9 const link = document.createElement('a');10 link.href = url;11 link.download = filename;12 document.body.appendChild(link);13 link.click();14 document.body.removeChild(link);15ย 16 // Release the object URL from memory (important for large files)17 // The blob remains in memory until garbage collected,18 // but the URL reference is freed immediately19 URL.revokeObjectURL(url);20}21ย 22// For ZIP files containing multiple outputs (e.g., batch compression):23import JSZip from 'jszip';24ย 25async function downloadAsZip(files: { name: string; blob: Blob }[]): Promise<void> {26 const zip = new JSZip();27 files.forEach(({ name, blob }) => zip.file(name, blob));28 const zipBlob = await zip.generateAsync({ type: 'blob' });29 downloadBlob(zipBlob, 'compressed-images.zip');30}For the practical user perspective on why this architecture matters for privacy, our client-side processing privacy guide explains what โno uploadโ means in terms you can verify with DevTools. For the broader comparison of when to use server-side processing instead, our client-side vs server-side guide covers the genuine tradeoffs.
Tools built on this architecture.
FileMint tools use Web Workers, WebAssembly, and OffscreenCanvas. No server required.
View All Local Tools โ