| 1 | # @fixtures/sveltekit-spa |
| 2 | |
| 3 | E2E fixture for `@alchemy.run/frontend-frameworks/sveltekit` exercising the **pure-SPA path**: |
| 4 | `ssr = false` in the root `+layout.ts`, the adapter's SPA fallback page, and — |
| 5 | the nuance under test — `+server.ts` endpoints that **still run server-side** |
| 6 | even though every page is client-rendered. |
| 7 | |
| 8 | Where `packages/frontend-frameworks/fixtures/sveltekit` is SSR-first, this fixture is the inverse: |
| 9 | |
| 10 | - `src/routes/+layout.ts` sets `ssr = false` / `prerender = false` for the |
| 11 | whole app. |
| 12 | - `notFoundHandling: "single-page-application"` in `e2e.config.ts` drives the |
| 13 | in-memory adapter's fallback generation (`builder.generateFallback` → |
| 14 | `index.html`) and must flow through to the deployed assets' |
| 15 | `not_found_handling` (mirrored in the miniflare preview's `assetConfig`). |
| 16 | - A **real user `vite.config.ts`** registers `sveltekit()` with a user alias |
| 17 | (`$spa` → `src/lib`) the widgets page imports through and a user |
| 18 | `preprocess` that rewrites a marker rendered by the home page — the file |
| 19 | must be loaded natively per the user-config principle. There is |
| 20 | deliberately NO `svelte.config.js`: kit v3 (`3.0.0-next.9`) hard-errors on |
| 21 | its presence ("svelte.config.js is no longer used") — ALL configuration, |
| 22 | including Svelte `preprocess`/`compilerOptions`, lives in the |
| 23 | `sveltekit(...)` call. |
| 24 | |
| 25 | ## Status: ENABLED |
| 26 | |
| 27 | The user-config wave landed (the real `vite.config.ts` below loads natively) |
| 28 | and the worker shim implements the SPA not-found deferral, so the suite runs |
| 29 | ungated: `bun run test` calls `playwright test` directly (12 tests, `live` + |
| 30 | `dev`). |
| 31 | |
| 32 | ## How the SPA fallback reaches the worker (the deferral contract) |
| 33 | |
| 34 | Upstream `@sveltejs/adapter-cloudflare`'s `worker.js` never defers a 404 to |
| 35 | the assets layer — kit's `server.respond` renders its own 404 error page for |
| 36 | unmatched routes, and the configured `assets.not_found_handling` only |
| 37 | applies to requests the worker never sees. Our shim makes the configured |
| 38 | `single-page-application` handling reachable (no new option — see |
| 39 | `packages/sveltekit/src/WorkerShim.ts`): when the adapter is built with |
| 40 | `notFoundHandling: "single-page-application"`, the shim defers to |
| 41 | `env.ASSETS.fetch(req)` — where the asset worker applies |
| 42 | `not_found_handling` and serves the generated `index.html` fallback — iff |
| 43 | |
| 44 | - `server.respond` returned **404**, and |
| 45 | - the request is **navigation-shaped**: `GET`/`HEAD` with `Accept` |
| 46 | containing `text/html`, and |
| 47 | - **no kit route pattern matches** the pathname — the most conservative |
| 48 | available signal that the 404 is kit's router-level "no route" error |
| 49 | (upstream has no deferral semantics to mirror, and kit marks router 404s |
| 50 | with no header). Endpoints only exist on matched routes, so intentional |
| 51 | endpoint 404s are never deferred; a pattern match whose param matchers |
| 52 | fail keeps kit's own 404 page. |
| 53 | |
| 54 | `notFoundHandling: "404-page"` and the default keep exact upstream behavior |
| 55 | (no deferral). Observable here: a navigation-shaped request for an unknown |
| 56 | path returns the `index.html` shell with **200** in live/preview, while a |
| 57 | non-navigation request (no `Accept: text/html`) still gets kit's 404. |
| 58 | |
| 59 | ## What the app exercises |
| 60 | |
| 61 | - **Client-side routing** between three routes (`/`, `/widgets`, `/about`) |
| 62 | with a planted `window` marker proving no full navigation occurs. |
| 63 | - **Deep links**: a direct request for `/widgets` returns the app shell (no |
| 64 | widget markup in the raw HTML — asserted via the `sveltekit-spa-shell` |
| 65 | marker in `app.html`) and hydrates into the correct view, in both live and |
| 66 | dev. |
| 67 | - **Universal load, server endpoint** (`/widgets` + `/api/widgets`): the |
| 68 | `+page.ts` load runs exclusively in the browser (`ssr = false`), while the |
| 69 | `+server.ts` endpoint it fetches runs server-side and reads a |
| 70 | `Text.local` binding (`FIXTURE_MESSAGE`) from `platform.env`. |
| 71 | - **SPA fallback / not_found_handling**: an unmatched path |
| 72 | (`/definitely/not/a/route`) serves the shell and hydrates into kit's |
| 73 | client-side 404 error view (`+error.svelte`). |
| 74 | - **Direct static asset** serve (`static/robots.txt`). |
| 75 | - **User config honored**: the `$spa` kit alias and the marker preprocessor |
| 76 | (both declared in the user's `vite.config.ts` `sveltekit(...)` call) are |
| 77 | observable in the rendered app. |
| 78 | |
| 79 | ## Commands |
| 80 | |
| 81 | ```sh |
| 82 | bun run dev # kit's Vite dev server via the harness (port 3108) |
| 83 | bun run build # kit build + in-memory adapt() + rolldown pass -> dist/build.json |
| 84 | bun run preview # miniflare over dist/build.json + .svelte-kit/cloudflare assets |
| 85 | bun run test # playwright e2e (live + dev) |
| 86 | ``` |