This guide covers using truss as a browser-oriented WebAssembly module instead of as a CLI or HTTP server.
The WASM adapter exposes the shared Rust image pipeline through a small JavaScript-facing surface. It is designed for local, client-side transforms:
- Input is raw image bytes from the browser.
- Output is transformed bytes plus JSON metadata.
- No server-side fetch, storage backend, signed URL, or secret-backed auth path is involved.
If you need remote URL fetches, storage backends, signed URLs, or server-side enforcement, use the HTTP server instead.
The repository now includes the source for the official npm package at packages/truss-wasm. That package is intended for publication as @nao1215/truss-wasm.
Its official build uses:
wasmsvgavifwasm-bindgen --target web- a small npm wrapper that initializes the Wasm module at import time
Implication:
- bundler-based consumers can import the package directly
- the package does not require an explicit
init()call - AVIF decode/encode is enabled
- WebP output stays lossless in the official package
The demo on GitHub Pages is built with the wasm and svg features only:
./scripts/build-wasm-demo.shThat script currently runs:
cargo build \
--release \
--locked \
--target wasm32-unknown-unknown \
--lib \
--no-default-features \
--features "wasm,svg"Implication:
- SVG processing is enabled.
- AVIF decode/encode is disabled.
- Lossy WebP encoding is disabled.
If your product needs AVIF or lossy WebP support, build your own artifact with the matching feature flags:
rustup target add wasm32-unknown-unknown
# Keep this version aligned with Cargo.toml.
cargo install wasm-bindgen-cli --version 0.2.114
cargo build \
--release \
--locked \
--target wasm32-unknown-unknown \
--lib \
--no-default-features \
--features "wasm,svg,avif,webp-lossy"
wasm-bindgen \
--target web \
--out-dir web/dist/pkg \
target/wasm32-unknown-unknown/release/truss.wasmFeature flags relevant to browser builds:
| Feature | Effect |
|---|---|
wasm |
Enables the wasm-bindgen browser adapter |
svg |
Enables SVG sanitization, SVG input handling, and SVG output for SVG inputs |
avif |
Enables AVIF decode and encode |
webp-lossy |
Enables quality-controlled lossy WebP output |
For bundler-based apps, the official package build is the recommended default. The raw wasm-bindgen output pair remains useful for static-page or custom hosting flows.
The examples in this guide assume a static page that lives next to the generated pkg/ directory, for example web/dist/index.html importing ./pkg/truss.js. If your app serves the generated files from another asset root, adjust the import path and wasm-bindgen --out-dir accordingly.
For bundler-based browser apps, use the official package:
import {
getCapabilitiesJson,
inspectImageJson,
transformImage,
} from "@nao1215/truss-wasm";
const inputBytes = new Uint8Array(await file.arrayBuffer());
const capabilities = JSON.parse(getCapabilitiesJson());
const inspected = JSON.parse(inspectImageJson(inputBytes, undefined));
const result = transformImage(
inputBytes,
undefined,
JSON.stringify({
format: "jpeg",
width: 1200,
quality: 82,
autoOrient: true,
}),
);The official package wraps the raw browser bindings and initializes the Wasm module at import time, so there is no explicit init() step.
For Vite, add vite-plugin-top-level-await, as shown in examples/vite-truss-wasm/vite.config.js.
For a runnable browser consumer example, see examples/vite-truss-wasm.
For a local install-and-transform smoke check that exercises the packed npm artifact from a throwaway consumer, run:
node ./scripts/run-wasm-consumer-smoke.mjsTo verify that the Vite example still bundles correctly against the current repository checkout, run:
node ./scripts/run-wasm-vite-example-smoke.mjsTo verify the checked-in Vite example and its browser runtime inside this repository checkout, run:
node ./scripts/run-wasm-vite-example-runtime-smoke.mjsFor direct static hosting of the raw Wasm bindings, the generated package exports a default init function plus named helpers:
import init, {
getCapabilitiesJson,
inspectImageJson,
transformImage,
transformImageWithWatermark,
} from "./pkg/truss.js";
await init();
const inputBytes = new Uint8Array(await file.arrayBuffer());
const capabilities = JSON.parse(getCapabilitiesJson());
const inspected = JSON.parse(inspectImageJson(inputBytes, undefined));
const result = transformImage(
inputBytes,
undefined,
JSON.stringify({
format: "jpeg",
width: 1200,
quality: 82,
autoOrient: true,
}),
);
const response = JSON.parse(result.responseJson);
const outputBlob = new Blob([result.bytes], {
type: response.artifact.mimeType,
});This example assumes a main-thread browser page with File, Blob, and object URL APIs available. The low-level WASM exports themselves only require byte arrays and strings.
If you already know the input format, declaredMediaType may be one of jpeg, png, webp, avif, bmp, tiff, or svg. Pass undefined if you want truss to rely on byte sniffing alone.
Browser builds can differ based on compile-time features. Always inspect capabilities at startup instead of assuming all formats are present.
type WasmCapabilities = {
svg: boolean;
webpLossy: boolean;
avif: boolean;
};| Field | Meaning |
|---|---|
svg |
SVG input/output processing is available |
webpLossy |
Quality-controlled lossy WebP output is available |
avif |
AVIF decode and encode are available |
The GitHub Pages demo uses svg: true, webpLossy: false, avif: false.
Returns a JSON string with the WasmCapabilities shape shown above.
Inspects image bytes and returns:
type WasmInspectResponse = {
artifact: {
mediaType: string;
mimeType: string;
width: number | null;
height: number | null;
frameCount: number;
hasAlpha: boolean | null;
};
};This is useful for building your UI before running a transform.
Returns a WasmTransformOutput object:
type WasmTransformOutput = {
bytes: Uint8Array;
responseJson: string;
};responseJson decodes to:
type WasmTransformResponse = {
artifact: {
mediaType: string;
mimeType: string;
width: number | null;
height: number | null;
frameCount: number;
hasAlpha: boolean | null;
};
warnings: string[];
suggestedExtension: string;
};Use artifact.mimeType when creating a Blob, and suggestedExtension when generating a download filename.
transformImageWithWatermark(inputBytes, declaredMediaType?, optionsJson, watermarkBytes, watermarkOptionsJson)
This matches transformImage, but overlays a raster watermark before encoding the output.
Watermark options:
type WasmWatermarkOptions = {
position?: "center" | "top" | "right" | "bottom" | "left" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
opacity?: number;
margin?: number;
};Defaults:
position:bottom-rightopacity:50margin:10
Validation:
opacitymust be between1and100.marginis a non-negative integer number of pixels.
optionsJson must match this JSON shape:
type WasmTransformOptions = {
width?: number;
height?: number;
fit?: "contain" | "cover" | "fill" | "inside";
position?: "center" | "top" | "right" | "bottom" | "left" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
format?: "jpeg" | "png" | "webp" | "avif" | "bmp" | "tiff" | "svg";
quality?: number;
optimize?: "none" | "auto" | "lossless" | "lossy";
targetQuality?: string;
background?: string;
rotate?: 0 | 90 | 180 | 270;
autoOrient?: boolean;
keepMetadata?: boolean;
preserveExif?: boolean;
crop?: string;
blur?: number;
sharpen?: number;
};Notes:
widthandheightmust be greater than zero when provided.fitandpositionrequire bothwidthandheight.format: "svg"is only valid when the input is already SVG.qualitymust be between1and100, and only applies to lossy output formats.qualitycannot be combined withoptimize: "lossless".targetQualityaccepts values such asssim:0.98orpsnr:42.targetQualityrequiresoptimize: "auto"oroptimize: "lossy".ssim:*targets must be greater than0.0and at most1.0.psnr:*targets must be greater than0.backgroundisRRGGBBorRRGGBBAA.cropisx,y,width,height.- Crop width and height must be greater than zero.
blurandsharpenmust each be between0.1and100.0.keepMetadataandpreserveExifare mutually exclusive.autoOrientdefaults totrue.
Transform semantics are the same as the CLI pipeline described in the main README.
On failure, the exported WASM functions throw a JsValue whose string form is a JSON payload:
type WasmErrorPayload = {
kind:
| "invalidInput"
| "invalidOptions"
| "unsupportedInputMediaType"
| "unsupportedOutputMediaType"
| "decodeFailed"
| "encodeFailed"
| "capabilityMissing"
| "limitExceeded";
message: string;
};Typical cases:
kind |
Meaning |
|---|---|
invalidInput |
Declared type conflicts with detected bytes |
invalidOptions |
Options JSON is malformed or contains invalid values |
unsupportedInputMediaType |
Input bytes are not a supported image format |
unsupportedOutputMediaType |
Requested output format is impossible, such as raster to SVG |
decodeFailed |
The image is structurally invalid |
encodeFailed |
Output encoding failed |
capabilityMissing |
The build excluded a requested feature |
limitExceeded |
Input, output, or watermark size exceeded a safety limit |
Frontend note:
- The documented
kindvalues come fromtrussitself. - Your app may still see unrelated runtime exceptions from its own JS glue or browser APIs. The demo UI treats those as a separate fallback category.
- The WASM adapter accepts bytes only. It does not fetch remote URLs and does not use storage backends.
- Raster input cannot be converted into SVG output.
- Watermarks must be raster images. SVG watermark input is rejected.
- AVIF encode/decode requires the
aviffeature. - Lossy WebP output requires the
webp-lossyfeature. - Metadata retention is not implemented for AVIF output.
- Lossy WebP optimization cannot preserve metadata.
- The WASM adapter does not inject a transform deadline. Browser apps should own their own UX for cancellation, progress, and timeouts.
- A browser may fail to preview a valid transformed artifact even when the conversion succeeded. In that case the output bytes are still usable for download.
These limits come from the shared core and apply to browser builds too:
| Limit | Value |
|---|---|
| Max decoded input pixels | 100000000 |
| Max output pixels | 67108864 |
| Max watermark pixels | 4000000 |
The demo UI adds one more browser-side check:
- Watermark uploads larger than 10 MB are rejected before calling into WASM.
If you build your own UI, that 10 MB byte-size check is optional. The shared Rust core still enforces pixel-based safety limits.
truss does not currently publish a formal browser version matrix for the WASM build.
Documented expectations today:
- JavaScript must be enabled.
- The runtime must support ES modules.
- The runtime must support WebAssembly.
- The examples in this repository assume a browser page with
File,Blob, andURL.createObjectURL.
Not currently documented or tested as first-class integration targets:
- SSR environments
- Node.js-only runtimes
- Web Workers
- Managed or legacy WebViews
The raw exported functions accept Uint8Array and strings, so worker-style integrations are plausible, but this repository does not currently document or test them as a supported path.
- The generated package targets
--target web, so it expects an ES module environment. - Ship the generated JS loader and
.wasmfile together. - If you host the files yourself, keep the import path to
truss.jsstable relative to the emitted.wasmasset.
For the demo-specific build flow, see the Development Guide.