Skip to content

feat: add swipl-web-no-data build variant (external wasm, no data) - #1203

Draft
jeswr wants to merge 1 commit into
masterfrom
feat/swipl-web-no-data
Draft

feat: add swipl-web-no-data build variant (external wasm, no data)#1203
jeswr wants to merge 1 commit into
masterfrom
feat/swipl-web-no-data

Conversation

@jeswr

@jeswr jeswr commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

What

Adds the missing cell of the build matrix: swipl-web-no-data — an external, streamable .wasm (like swipl-web) with no .data file and no base64 embedding (like swipl-bundle-no-data).

variant wasm delivery .data (1.6 MB)
swipl-web external .wasm (streamable) always fetched
swipl-bundle base64 inside the JS embedded
swipl-bundle-no-data base64 inside the JS omitted
swipl-web-no-data (this PR) external .wasm (streamable) omitted

Like swipl-bundle-no-data, the new variant ships no Prolog library, so it must boot from a saved state (-x image.pvm, see "Generating an Image" in the README). For embedders that already boot from an image (e.g. eye-js, see eyereasoner/eye-js#1957), this is the smallest and best-cacheable way to deliver SWI-Prolog: the browser can compile the wasm with WebAssembly.instantiateStreaming while it downloads, and cache it independently of application JS.

How

The three existing variants are link targets defined in swipl-devel's cmake/EmscriptenTargets.cmake; this repo's Docker build just compiles them and docker cps the artifacts out. The new target is the cross product of flags that already exist there: WASM_DIST_LINK_FLAGS without --preload-file (drops the .data and the file-packager glue) and without -s SINGLE_FILE (keeps the wasm external).

  • docker/swipl-web-no-data.cmake (new): the link target, reusing WASM_DIST_LINK_FLAGS/SWIPL_SRC/PREJS/POSTJS from EmscriptenTargets.cmake. Guarded with if(NOT TARGET ...) so it becomes a no-op if swipl-devel ships the target upstream one day (happy to file it there as a follow-up).
  • docker/Dockerfile: appends the snippet to swipl-devel's EmscriptenTargets.cmake after checkout. Appending (rather than a context patch) keeps working across SWIPL_COMMIT bumps from the update workflow.
  • package.json: two new build:wasm-docker:extract:* entries copy swipl-web-no-data.js / swipl-web-no-data.wasm out of the container (picked up automatically by run-s build:wasm-docker:extract:*).
  • dist/swipl/swipl-web-no-data.d.ts, dist/loadImageWeb.ts: typings + a loadImageDefault-style helper bound to the new variant.
  • tests/node.js: boots the new variant from a qsave_program image and asserts the answer is produced, that only the .wasm is located (never a .data), and that the glue contains no data-preload reference.
  • tests/browser.js + examples/browser-no-data.html: same end-to-end in Puppeteer, asserting via the network log that swipl-web-no-data.wasm is fetched and no .data request is ever made.
  • .github/workflows/nodejs.yml: the build cache key now also hashes docker/*. This is needed for this PR (otherwise CI restores the cached dist/swipl and never builds the new artifacts) and fixes a latent issue where Dockerfile changes did not invalidate the artifact cache.
  • README: documents the variant.

Wire sizes

Measured on the published swipl-wasm 8.0.3 artifacts (brotli -q 11, via Node's zlib):

artifact raw brotli
swipl-web.wasm 2.09 MB 0.60 MB
swipl-web.js 188 KB 42 KB
swipl-web.data 1.57 MB 0.96 MB
swipl-bundle-no-data.js 2.47 MB 0.65 MB
swipl-bundle.js 5.92 MB 1.40 MB

Expected for swipl-web-no-data (estimate — the artifacts are produced by CI, see below): the .wasm links from the same objects with the same codegen flags, so ~2.09 MB raw / ~0.60 MB brotli; the glue is the swipl-web glue minus the file-packager block, ~146 KB raw / ~33 KB brotli (measured on an emulation, see below). Total ~0.64 MB brotli + the application's own .pvm image, versus ~1.60 MB for swipl-web today (which unconditionally drags the .data) and 1.40 MB for swipl-bundle.

Against swipl-bundle-no-data (~0.65 MB) the byte win is small — the real difference is delivery quality: streaming wasm compile instead of parsing 2.5 MB of JavaScript and base64-decoding the wasm out of it, plus the .wasm being separately cacheable.

One cost to be aware of: the npm tarball grows by roughly the new swipl-web-no-data.js + swipl-web-no-data.wasm (~2.3 MB raw). The new .wasm should be functionally identical to swipl-web.wasm (only the packaging flags differ), so if tarball size matters we could later dedupe by shipping only the glue and defaulting locateFile to swipl-web.wasm — kept out of this PR to avoid coupling the variants.

Validation status

Build config authored but NOT validated in the author's environment — needs CI to produce/verify the artifacts. I could not run the emsdk Docker build on the machine this was authored on (2-core box, insufficient disk headroom). What was validated locally:

  • Runtime semantics, by emulation: taking the published 8.0.3 swipl-web.js glue and removing the file-packager IIFE (precisely the code that --preload-file adds) yields a glue that boots -x image.pvm from a qsave_program/1 image with only swipl-web.wasm ever located — no .data request — and answers queries correctly (current_prolog_flag(version, 100110)). This is the exact shape of glue Emscripten emits without --preload-file.
  • The new node/browser tests were run locally against those emulated artifacts (glue with the wasm reference renamed + the published wasm), so the test code itself is exercised; CI re-runs them against the real built artifacts.
  • npm run tsc and npm run lint pass.

Because the cache key changes, this PR's CI run performs a full Docker build and then runs the new tests against the freshly built artifacts — that run is the authoritative validation. Note the full-build path failed transiently on 2 July (zlib.net download flaked on the fix/update-emsdk-v6.0.2 branch); the URL responds again, so a re-run should suffice if it recurs. If it keeps flaking, switching the Dockerfile to https://github.com/madler/zlib/releases/download/v$ZLIB_VERSION/zlib-$ZLIB_VERSION.tar.gz would be a robust follow-up.

Review timing: This draft was prepared with Claude; I (@jeswr) will personally review it before it progresses. I'm currently batching a lot of work in flight, so expect active review Wednesday–Friday (8–10 July).

Adds the missing cell of the build matrix: an external, streamable
.wasm (like swipl-web) with no .data file and no base64 embedding
(like swipl-bundle-no-data).  Boots from a saved state only.

- docker/swipl-web-no-data.cmake: new Emscripten link target appended
  to swipl-devel's EmscriptenTargets.cmake at image build time;
  guarded so it no-ops if swipl-devel ships the target upstream.
- package.json: extract the new artifacts from the build container.
- dist/loadImageWeb.ts: loadImageDefault-style helper for the variant.
- tests: node + puppeteer smoke tests that boot from a qsave image and
  assert only the .wasm is fetched (never a .data).
- nodejs.yml: cache key now also hashes docker/*, so Docker build
  changes (including this one) invalidate cached artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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