jSquash WebP
WebAssembly WebP encoder and decoder for browsers, Web Workers, and edge runtimes
Repository Health
Technical Analysis
@jsquash/webp is a WebAssembly-powered WebP codec extracted from Google’s Squoosh app, giving JavaScript code a way to encode and decode WebP images without native bindings or a server round-trip. It wraps libwebp compiled to Wasm behind two small async functions, encode and decode, that operate directly on ImageData and ArrayBuffer values. Because the codec avoids dynamic code evaluation and doesn’t depend on the TextEncoder/TextDecoder APIs, it runs cleanly in restrictive V8 environments such as Cloudflare Workers and Vercel Edge Functions, in addition to standard browser tabs, Web Workers, and (with extra setup) Node.js. It is one of eight sibling codec packages in the jSquash monorepo alongside AVIF, JPEG, JPEG XL, PNG, QOI, Oxipng, and a resize module, all sharing the same encode/decode/init pattern.
What You Get
- A default-exported
encode(imageData, options?)function that returns a WebP-encodedArrayBufferfrom rawImageData - A default-exported
decode(arrayBuffer)function that returns decodedImageDatafrom WebP binary input - Separate SIMD and non-SIMD encoder Wasm builds, auto-selected at runtime via feature detection (
wasm-feature-detect) - Exported
init()functions on both encode and decode entry points for manually supplying a precompiledWebAssembly.Module, needed for Cloudflare Workers and similar restricted runtimes - Full TypeScript types for
EncodeOptions, covering libwebp’s quality, lossless, alpha, filtering, and multithreading knobs - Ready-made example projects for CDN usage, Node.js, Cloudflare Workers (both ES module and legacy service worker formats), Rollup, Vite, Webpack, and Deno
Common Use Cases
- Converting user-uploaded PNG/JPEG images to WebP in the browser before upload, to cut bandwidth and storage costs
- Building an on-the-fly image optimization edge function on Cloudflare Workers or Vercel Edge that re-encodes fetched images as WebP
- Decoding WebP files inside a Web Worker for canvas-based image editing tools without blocking the main thread
- Running WebP encode/decode benchmarks or format-conversion utilities in Node.js scripts and CLIs
Under The Hood
Architecture - The package is a thin TypeScript wrapper (encode.ts, decode.ts, meta.ts, utils.ts) around Emscripten-generated glue code in codec/enc and codec/dec, which binds a small C++ shim (webp_enc.cpp, webp_dec.cpp) to the vendored libwebp v1.0.2 C library compiled to Wasm binaries (webp_enc.wasm and a SIMD variant, webp_dec.wasm). initEmscriptenModule() in utils.ts centralizes Wasm instantiation with noInitialRun and an optional custom instantiateWasm callback, letting callers supply a precompiled WebAssembly.Module for edge runtimes. It’s one of eight structurally identical codec packages (avif, jpeg, jxl, oxipng, png, qoi, resize, webp) in a Turborepo monorepo, all following the same encode/decode/init contract.
Tech Stack - TypeScript compiled with tsc to ESM-only output (no bundler for the library itself). The sole runtime dependency is wasm-feature-detect, used to pick between SIMD and non-SIMD encoder builds. The C++/Wasm layer is built separately via a Makefile invoking Emscripten against vendored libwebp source. Monorepo tooling is Turborepo with ESLint/typescript-eslint and Prettier, plus a GitHub Actions workflow that installs, lints, builds, and runs integration tests on Node 22.
Code Quality - Integration tests in test/node/webp.test.ts use the ava runner, covering a decode round-trip against a fixture WebP file and an encode call against a blank ImageData buffer — real but shallow coverage exercising only the happy path, with no error-case or option-variation tests. Source files are short, single-purpose, and carry consistent license/attribution comments documenting the maintainer’s modifications from the original Squoosh source. Error handling is minimal: encode/decode throw generic messages when the underlying Wasm call returns falsy, with no input validation beyond that.
API Design - The public surface is deliberately minimal: default-exported encode/decode async functions plus an init() escape hatch on each, mirrored identically across all eight sibling packages so switching codecs requires no restructuring. Encode options merge against libwebp-derived defaults so callers can pass a partial config. The main friction for newcomers is that Wasm binary loading is bundler-dependent and under-abstracted, requiring bundler-specific workarounds documented in the README (Vite optimizeDeps excludes, Nuxt transpile config).