Skip to content

🚧 CDN pivot: stop building the webpack Pages bundle, serve eyereasoner from a CDN + redirect old Pages URLs - #1

Closed
jeswr wants to merge 1 commit into
mainfrom
fix/pages-cdn-redirect
Closed

🚧 CDN pivot: stop building the webpack Pages bundle, serve eyereasoner from a CDN + redirect old Pages URLs#1
jeswr wants to merge 1 commit into
mainfrom
fix/pages-cdn-redirect

Conversation

@jeswr

@jeswr jeswr commented Jul 4, 2026

Copy link
Copy Markdown
Owner

🚧 Stop building the webpack Pages bundle; serve eyereasoner from a CDN and redirect the old Pages URLs

Note: This PR was generated by an agent on @jeswr's behalf, following up on the triage in eyereasoner#1845. It is a draft opened against a fork for review — please review accordingly; nothing has been pushed to the upstream repo or the pages branch.

References eyereasoner#1845. Supersedes / re-frames eyereasoner#1955: rather than keep building the bundle and prune it every release, this stops building it at all and replaces it with CDN redirects.

The pivot

Today each release builds a ~4MB webpack bundle and publishes it to the pages branch, which has grown to ~7.5GB (~7× the 1GB Pages limit) and keeps breaking the Pages deployment. eyereasoner#1955 bounds that growth by pruning. This PR removes the cause instead:

  1. Docs & example → CDN. eyereasoner is already published to npm and is served, with no build step on our side, by public ESM CDNs. The README and examples/prebuilt/index.html now use esm.sh (with jsDelivr as a drop-in alternative) for both ESM import and classic <script> usage.
  2. Old Pages URLs → smart redirects. Two tiny, self-deriving stubs (redirects/index.js classic global, redirects/dynamic-import.js ESM) read their own URL, work out the requested version, and load it from the CDN. Broadcast byte-identically across every index.js/dynamic-import.js path they collapse (git blob dedup) to 2 unique blobs — so every existing URL keeps answering 200 + JS with ~0 per-release growth and zero per-version authoring. No "hundreds of redirect pages."
  3. Release workflow stops building. scripts/generate-redirects.ts replaces the bundle:webpack + post-webpack Pages emission (deleted scripts/post-webpack.ts) and writes .nojekyll; the @qiwi/semantic-release-gh-pages-plugin publish now ships the tiny stub tree (dotfiles: true so .nojekyll is included).

Verdict: VIABLE_WITH_CAVEATS

A CDN serves a fully working, actually-reasoning eyereasoner for ESM import — proven end-to-end, not just "the JS resolves."

The swipl-wasm gating risk does not apply to this package. eyereasoner uses the swipl-bundle-no-data variant: the SWI-Prolog WASM and the EYE .pvm image are inlined in the JavaScript. There is no runtime .wasm/.data asset to locate cross-origin (grep for locateFile/instantiateStreaming/new URL(...wasm)/swipl-web.wasm in the eyereasoner code path = 0 hits). Verified in node: dynamic-import() of https://esm.sh/eyereasoner@21.1.10 over the network booted the inline WASM and inferred :Socrates a :Mortal.; decoding the inline binary from both the local npm bundle and the esm.sh .mjs reconstructs a byte-identical WASM.

The one caveat (keeps this from a clean VIABLE): a classic <script src=…> that expects a synchronous window.eyereasoner global cannot be reproduced from the current npm package by any CDN — eyereasoner publishes CJS only (no UMD/IIFE), and CDNs emit ESM. The classic redirect stub therefore populates window.eyereasoner asynchronously and uses a Proxy so await eyereasoner.n3reasoner(…) keeps working (matches the documented usage). A consumer reading a non-callable property synchronously, or not awaiting, would see a behavioural change. Optional fix (Option B, not in this PR): add one esbuild --format=iife --global-name=eyereasoner artifact to the eyereasoner package so old index.js URLs can 1:1 redirect to a true synchronous global.

⚠️ The one browser check @jeswr should run (~5 min — this box has no browser)

Everything above was verified headless in node; the browser WASM path is the one thing that could not be exercised here. Open a page containing:

<script type="module">
  import { n3reasoner } from 'https://esm.sh/eyereasoner@21.1.10';
  console.log(await n3reasoner(':Socrates a :Man. {?s a :Man}=>{?s a :Mortal}.'));
  // expect: :Socrates a :Mortal.
</script>

Confirm (1) it prints :Socrates a :Mortal. and (2) DevTools → Network shows no 404 / opaque request for any swipl-web.wasm / swipl-web.data (there should be none — the WASM is inline). The browser WEB path instantiates the same byte-identical inline WASM via WebAssembly.instantiate(ArrayBuffer) with no fetch, so risk is low — but it is the one path not exercised here.

What's in this PR

File Change
README.md Browser-usage sections rewritten to CDN (ESM import, classic global, dynamic import, migration note)
examples/prebuilt/index.html <script src=…pages…><script type="module">import { n3reasoner } from 'https://esm.sh/eyereasoner'
redirects/index.js Classic-global redirect stub (self-deriving, async Proxy global)
redirects/dynamic-import.js ESM redirect stub (self-deriving, re-exports eyereasoner)
scripts/generate-redirects.ts Writes the 2 stubs into a version's dirs + latest shortcuts + .nojekyll
scripts/post-webpack.ts Deleted (Pages bundle emission)
.github/workflows/nodejs.yml Release step now runs bundle:redirects instead of bundle:webpack + bundle:latest
package.json bundle:latestbundle:redirects; gh-pages plugin dotfiles: true

webpack.config.js / bundle:webpack are kept — still used by the browser E2E/memory tests (which are the in-browser WASM verification). See open question 1.

Validation (headless, gated flock+nice)

  • npm run lint (eslint, project config): 0 errors on touched TS.
  • tsc --noEmit on scripts/generate-redirects.ts: clean.
  • ts-node scripts/generate-redirects --name=v21.1.10 --out=…: produced exactly 21/1/10/, 21/1/latest/, 21/latest/, latest/ (each with index.js + dynamic-import.js) + .nojekyll; stubs byte-identical (md5) to the canonical redirects/*.js.
  • Both stubs node --check-parse; URL→CDN derivation unit-checked for latest, @M, @M.m, @M.m.p, root, and ?query shapes.
  • CDN resolution headless: esm.sh/eyereasoner, @21, @21.1, @21.1.10, and jsdelivr …/+esm all HTTP 200, content-type: application/javascript; charset=utf-8, access-control-allow-origin: *.

Follow-ups (need @jeswr sign-off — NOT in this PR)

  • The one-off 7.5GB prune. This PR only stops future growth; the branch is still ~7.5GB until pruned. Recommended: rewrite pages as a single orphan commit = the 2 stubs broadcast to all existing paths + .nojekyll + 404.html + example/ + dev/bench/, then force-push. This prunes 7.5GB → a few MB and installs the redirects for all historical URLs in one step. Force-push needs explicit approval — deliberately excluded here.
  • Downscope 🚧 fix: skip Jekyll pages build and prune superseded patch bundles on release eyereasoner/eye-js#1955 to just its .nojekyll marker (the patch-prune logic is moot once bundles aren't produced).

Open questions

  1. Keep webpack for tests, or rip it out entirely? The directive was "stop building the webpack bundle at all"; this PR stops the Pages build but keeps bundle:webpack for the browser E2E/memory tests (they provide the actual in-browser WASM check). Migrate those to the CDN/npm dist and delete webpack, or keep?
  2. Option A (async Proxy global, this PR) vs also Option B (add one IIFE artifact to the eyereasoner npm package for a true synchronous classic global). Recommendation: do both.
  3. esm.sh vs jsDelivr as primary, and: is a third-party CDN in downstream consumers' runtime load path acceptable (vs. the current self-hosted Pages bundle)? Any interest in self-hosting an esm.sh instance?
  4. Pin redirect targets or track latest? Stubs currently resolve latest/@M/@M.m live, so a bad future npm publish would propagate to old URLs. Acceptable, or pin to a known-good range?
  5. Sign-off on the 7.5GB orphan force-push, confirming that dropping undocumented deep-history exact-patch URLs is acceptable (all README-documented shapes + every npm dist/ pin survive).

🤖 Generated with Claude Code

…d of building the webpack bundle

Stop building and publishing the ~4MB webpack bundle to the `pages` branch on
every release (which grew the branch to ~7.5GB). Instead:

- README and the prebuilt example now use a public ESM CDN (esm.sh, with
  jsDelivr as a drop-in alternative) for both ESM `import` and classic
  `<script>` usage. The package is self-contained (WASM + EYE image inlined),
  so there are no separate .wasm/.data assets to host.
- Two tiny, self-deriving redirect stubs (redirects/index.js classic global,
  redirects/dynamic-import.js ESM) derive the requested version from their own
  URL and load it from the CDN. Broadcast byte-identically to every path they
  collapse to 2 unique blobs, so old Pages URLs keep working with ~0 growth.
- scripts/generate-redirects.ts replaces the webpack + post-webpack Pages
  emission in the release workflow and also writes the `.nojekyll` marker;
  the gh-pages publish now ships the stub tree (dotfiles enabled).

Refs eyereasoner#1845. Supersedes the build-and-prune approach of eyereasoner#1955.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jeswr

jeswr commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

Relocated to the canonical repo: eyereasoner#1957

@jeswr jeswr closed this Jul 4, 2026
@jeswr
jeswr deleted the fix/pages-cdn-redirect branch July 4, 2026 21:51
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