Skip to content

fix: Support satori/standalone on Cloudflare Workers - #931

Open
levish0 wants to merge 1 commit into
vercel:mainfrom
levish0:fix/standalone-harfbuzz-workers
Open

levish0 wants to merge 1 commit into
vercel:mainfrom
levish0:fix/standalone-harfbuzz-workers

Conversation

@levish0

@levish0 levish0 commented Oct 15, 2026

Copy link
Copy Markdown

Fixes #878

satori/standalone has been broken on Cloudflare Workers since HarfBuzz landed in 0.33. Workers don't define self.location and won't compile wasm from bytes at runtime. HarfBuzz ran into that in three places:

  1. the Emscripten glue reads self.location.href as soon as harfbuzzjs is imported
  2. the inlined hb.wasm gets compiled at runtime
  3. hbjs registers its blob destroy callback with addFunction, which compiles a tiny wasm module on the fly

What this changes:

  • HarfBuzz starts on the first render instead of on import (the default build still preloads it)
  • init({ layout, harfbuzz }) takes a precompiled module and hands it to Emscripten through instantiateWasm. init(layout) works like before.
  • satori/harfbuzz.wasm is exported. It's copied from harfbuzzjs/hb.wasm at build time, so it's the same binary as the inlined one.
  • harfbuzzjs patch: self.location?.href, and blobs use HB_MEMORY_MODE_DUPLICATE so they don't need a destroy callback
  • test/workers-build.mjs imports the standalone build with Workers-like globals and renders once. It fails if any of the three fixes is reverted.

On Workers:

import satori, { init } from 'satori/standalone'
import layout from 'satori/layout.wasm'
import harfbuzz from 'satori/harfbuzz.wasm'

await init({ layout, harfbuzz })

Tested with the repro from #878 under wrangler dev. On main it fails with reading 'href'. On this branch it renders, with or without nodejs_compat and whether or not init is awaited. pnpm test passes, except the canvas tests, which I couldn't run locally (no native gl build).

Since HarfBuzz text shaping was added, `satori/standalone` fails on
runtimes that only run WebAssembly modules uploaded with the code:

- The Emscripten glue reads `self.location.href`, which Workers don't
  define, when `harfbuzzjs` is imported.
- The bundled HarfBuzz bytes are compiled at runtime.
- `hbjs` creates the blob destroy callback with `addFunction`, which
  compiles a small module at runtime.

Start HarfBuzz on the first render instead of on import, let `init` take
a precompiled `harfbuzz.wasm` module, and let HarfBuzz copy font data so
blobs need no destroy callback.

Fixes vercel#878
@levish0
levish0 requested a review from shuding as a code owner October 15, 2026 10:58
@vercel

vercel Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

@levish0 is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

satori/standalone fails on Cloudflare Workers since 0.33.0: harfbuzzjs reads self.location.href

1 participant