inline rules-engine WASM binary at build time#123
Merged
Conversation
cbrady
previously approved these changes
May 22, 2026
bpapillon
added a commit
that referenced
this pull request
Jun 3, 2026
The wasm-bindgen-generated `dist/wasm/rulesengine.js` loads its `.wasm`
sibling at runtime via `fs.readFileSync(${__dirname}/rulesengine_bg.wasm)`.
That works in plain Node, but breaks the moment a downstream bundler
follows the require chain — webpack rewrites `__dirname` to point inside
the bundle output, where the `.wasm` sibling never gets copied. Symptom
in a Next.js consumer:
ENOENT: no such file or directory, open
'.next/dev/server/vendor-chunks/rulesengine_bg.wasm'
…and the SDK silently falls back to API-only checks, disabling DataStream
and credit-lease paths.
This adds a build step that reads the WASM binary, base64-encodes it,
and rewrites `dist/wasm/rulesengine.js` to instantiate from an inlined
`Buffer.from(BASE64, 'base64')` instead of touching the filesystem. The
standalone `.wasm` is then removed from `dist/` since nothing reads it
at runtime anymore.
Tarball delta is +138 KB (base64 overhead on the ~414 KB binary). For
consumers that use creditLeases / DataStream the net bundle size is
unchanged — the WASM bytes are in either form. For consumers that don't,
practical tree-shaking ends up the same regardless of inlining: the
require chain is reachable from the package's main entry point and the
WASM init runs as a module-level side effect. Materially reducing the
WASM cost for non-credit-lease consumers would require splitting credit
leases into a separate entry point — out of scope here.
If wasm-bindgen output ever stops matching the regex this script keys
on, the build throws with a clear pointer instead of silently shipping
a broken loader.
The verify-package step asserted on `dist/wasm/rulesengine_bg.wasm` presence, which the inlining step now intentionally removes. Replace those checks with a content-sentinel grep on the inlined comment so a future change that silently reverts to disk-based loading still fails CI, plus an explicit assertion that the standalone .wasm is gone.
4196c85 to
d8e475f
Compare
bpapillon
added a commit
that referenced
this pull request
Jun 3, 2026
cbrady
approved these changes
Jun 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The wasm-bindgen
dist/wasm/rulesengine.jsloads its.wasmsibling viafs.readFileSync(${__dirname}/...), which breaks under bundlers that rewrite__dirname(Next.js surfaces it asENOENTat SDK init, and the rules engine silently falls back to API-only) and in runtimes withoutfs.Two changes:
Inline the WASM at build time (
build.js) — base64-encode the binary into the loader (Buffer.from(...)), removing the filesystem read, and drop the standalone.wasm. Init becomes bundler/runtime-agnostic. Tarball +138 KB.Load the loader via dynamic
import()(src/rules-engine.ts) instead ofrequire, so bundlers code-split the wasm into its own async chunk. A consumer that only uses the HTTP API (never callsinitialize()) no longer ships the inlined binary in its entry bundle — measured HTTP-only entry bundle: webpack/Next 282 KB → 71 KB gz, Vite/Rollup/esbuild-splitting 283 KB → 77 KB gz. (esbuild single-file mode still re-inlines.)verify-packageCI updated to assert the inlined sentinel and the absence of the standalone.wasm.Follow-up SCH-6542: publish web/bundler wasm-bindgen targets for the size-optimal native WASM path on Cloudflare/edge.