Upgrade jq-wasm to 3.0.0-jq-1.8.2: lean wasm loading on both jq paths#362
Merged
Conversation
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.
There was a problem hiding this comment.
💡 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".
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
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.
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.
Summary
jq-wasm v3 replaces the module-level singleton with a
loadJqfactory returning a synchronous handle, and exposes the wasm binary as an addressablejq-wasm/jq.wasmsubpath export. This PR consumes both to slim the two jq execution paths:/api/jqpiscina worker):loadJq({ wasmBinary: readFileSync(require.resolve('jq-wasm/jq.wasm')) }), loaded once per worker thread with synchronousraw()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 copiesoutputFileTracingIncludesglobs 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.jq-wasm/inline(wasm embedded as base64 in the JS bundle) forloadJq({ wasmURL }), fed by a webpackasset/resourcerule that emitsjq.wasmas 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 thatnew URL(..., import.meta.url)cannot.jq-wasm/jq.wasmasset import to a shim exporting the on-disk wasm path, so worker tests keep executing real jq under Node.Verification
tsc --noEmit,eslint .— cleanvitest 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.jsonlistsworker.cjs+ the three jq-wasm closure files, so the route's trace is self-sufficient (no reliance on other routes' imports)POST /api/jqwith.a | addover{"a":[1,2,3]}→6(200,x-execution-timeheader present)-rraw output →hello world; invalid JSON input → jq parse error surfaced via stderr; GET query-param form → worksGET /(SSR at request time) → 2006,GET /_next/static/wasm/2041218713e511a5.wasm→ 200, zero console errors