Skip to content

Upgrade jq-wasm to 3.0.0-jq-1.8.2: lean wasm loading on both jq paths#362

Merged
owenthereal merged 2 commits into
mainfrom
jq-wasm-v3
Jul 5, 2026
Merged

Upgrade jq-wasm to 3.0.0-jq-1.8.2: lean wasm loading on both jq paths#362
owenthereal merged 2 commits into
mainfrom
jq-wasm-v3

Conversation

@owenthereal

@owenthereal owenthereal commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

jq-wasm v3 replaces the module-level singleton with a loadJq factory returning a synchronous handle, and exposes the wasm binary as an addressable jq-wasm/jq.wasm subpath export. This PR consumes both to slim the two jq execution paths:

  • Server (/api/jq piscina worker): loadJq({ wasmBinary: readFileSync(require.resolve('jq-wasm/jq.wasm')) }), loaded once per worker thread with synchronous raw() per task. The v2-era force-include of the whole package is replaced by an explicit per-file include of the worker's runtime closure (package.json, dist/index.cjs, dist/build/jq.wasm) — Next copies outputFileTracingIncludes globs without tracing through them, so the closure is listed rather than inferred. The standalone image ships 1.2 MB of jq-wasm (960 KB of it the wasm binary) instead of the entire package including two ~1.3 MB inline builds that were never executed.
  • Browser (web worker): drops jq-wasm/inline (wasm embedded as base64 in the JS bundle) for loadJq({ wasmURL }), fed by a webpack asset/resource rule that emits jq.wasm as a hashed static asset. The wasm leaves the JS bundle and becomes a single immutable-cacheable fetch (/_next/static/wasm/<hash>.wasm), which also crosses the nested-worker-chunk boundary that new URL(..., import.meta.url) cannot.
  • Tests: vitest aliases the jq-wasm/jq.wasm asset import to a shim exporting the on-disk wasm path, so worker tests keep executing real jq under Node.

Verification

  • tsc --noEmit, eslint . — clean
  • vitest run — 7 files, 89 tests passed (real jq through both worker entry points)
  • npm run build — clean; .next/server/app/api/jq/route.js.nft.json lists worker.cjs + the three jq-wasm closure files, so the route's trace is self-sufficient (no reliance on other routes' imports)
  • Standalone server (deploy layout per Dockerfile: standalone + static + public):
    • POST /api/jq with .a | add over {"a":[1,2,3]}6 (200, x-execution-time header present)
    • -r raw output → hello world; invalid JSON input → jq parse error surfaced via stderr; GET query-param form → works
    • GET / (SSR at request time) → 200
  • Browser against the standalone server (Playwright): query executed in the web worker, output editor shows 6, GET /_next/static/wasm/2041218713e511a5.wasm → 200, zero console errors

Server (piscina worker): switch to the v3 loadJq factory with wasmBinary
read via require.resolve('jq-wasm/jq.wasm'). The resolve call is
statically traceable, so Next's standalone output now ships jq-wasm's
4-file closure (1.2 MB, mostly the wasm itself) instead of
force-including the entire package through outputFileTracingIncludes.

Browser (web worker): replace jq-wasm/inline (wasm embedded as base64 in
the JS bundle) with loadJq({ wasmURL }) fed by a webpack asset/resource
rule; jq.wasm becomes a hashed, immutably-cacheable static asset fetched
once by the worker.

Tests: alias the jq-wasm/jq.wasm asset import to a shim exporting the
on-disk wasm path so vitest keeps executing real jq under Node.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96503f7f2f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread next.config.mjs Outdated
Next copies outputFileTracingIncludes globs verbatim without tracing the
included file's own require() graph, so worker.cjs's jq-wasm dependency
never entered the /api/jq manifest — the standalone deploy only worked
because the homepage's SSR graph (shared/jq.ts via serverExternalPackages)
happened to trace the same files. List the worker's exact closure
(package.json, dist/index.cjs, dist/build/jq.wasm) for /api/jq so the
route no longer depends on an unrelated page's imports, and correct the
comments that claimed require.resolve() was traced. Verified via
.next/server/app/api/jq/route.js.nft.json and a standalone-layout run.
@owenthereal owenthereal merged commit d62707e into main Jul 5, 2026
1 check passed
@owenthereal owenthereal deleted the jq-wasm-v3 branch July 5, 2026 04:16
owenthereal added a commit to owenthereal/jq-wasm that referenced this pull request Jul 5, 2026
…ecipe (closes #6) (#16)

* Document verified browser CDN usage; correct the standalone tracing recipe (closes #6)

CDN section: v3's browser-condition entry (dist/browser.mjs) has no Node
builtin imports, so CDN bundlers no longer refuse the package. Verified
in a real browser against jsDelivr (esm.run importmap and raw files),
unpkg, and ga.jspm.io — default sibling-wasm streaming works on all
three. Skypack and dev.jspm.io fail CDN-side (unmaintained / deprecated
in favor of ga.jspm.io).

Standalone recipe: qualify the require.resolve claim — Next traces it
only when the importing module is part of a traced route. Files pulled
in via outputFileTracingIncludes globs are copied, not traced, so
out-of-bundle workers (piscina/worker_threads) must list the jq-wasm
closure explicitly. Matches what jqlang/playground#362 hit in practice.

* Note the ESM entry in the out-of-bundle worker closure

An ESM worker resolves jq-wasm through the node.import condition to
dist/index.mjs, which the CJS-shaped include list would not copy.
Both entries are self-contained (no chunk imports on the Node builds)
and the jq-wasm/jq.wasm subpath maps to the same wasm file under both
conditions, so the closure differs by exactly that one entry.
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.

1 participant