| 1 | # @fixtures/sveltekit |
| 2 | |
| 3 | E2E fixture for `@alchemy.run/frontend-frameworks/sveltekit` — a SvelteKit app built and served |
| 4 | without wrangler: |
| 5 | |
| 6 | - **SSR** home page whose `+page.server.ts` load reads a `platform.env` value |
| 7 | supplied by a `Text.local` binding (and checks `platform.ctx.waitUntil`). |
| 8 | - **Form actions** (`/form`): a named action (`?/greet`) exercised both via |
| 9 | progressive enhancement (`use:enhance`, no full navigation) and as a plain |
| 10 | no-JavaScript POST (HTML re-render; kit's CSRF origin check included). |
| 11 | - **Cookies** (`/cookies`): a `+page.server.ts` load that reads and re-sets a |
| 12 | visit-counter cookie — `Set-Cookie` round-trips through the worker shim and |
| 13 | kit's dev SSR alike. |
| 14 | - **Binary endpoint** (`/api/binary`): a 256-byte octet-stream response |
| 15 | asserted byte-for-byte. |
| 16 | - **Route group** (`(marketing)/about`): group layout `+layout.server.ts` |
| 17 | data rendered by the group layout, with the group segment absent from the |
| 18 | URL (and the literal `/(marketing)/...` path a 404). |
| 19 | - **`platform.caches`** (`/api/cache`): a cache-aside endpoint over |
| 20 | `caches.default` — a real cache hit on the second request in both modes |
| 21 | (live: workerd's Cache API; dev: the platform proxy's in-memory store). |
| 22 | - **Platform bindings** (`/platform`): a `+page.server.ts` load exercising a |
| 23 | real `KvNamespace` binding (`put`/`get` round-trip through the dev |
| 24 | platform proxy), the dev `env` literal-override precedence |
| 25 | (`FIXTURE_OVERRIDE`), and `platform.cf`. |
| 26 | - **Server endpoint** `/api/hello` exercising `cookie` (v2), `uuid` |
| 27 | (browser/node conditional exports), and `node:crypto` under `nodejs_compat`. |
| 28 | - **Prerendered** page (`/prerendered`) served from static assets, alongside |
| 29 | SSR routes (prerendered + SSR mix). |
| 30 | - **Client-interactive** counter page (`/counter`) proving hydration. |
| 31 | - **Static asset** (`static/robots.txt`). |
| 32 | |
| 33 | - **User-owned `vite.config.ts`** (`/api/user-config`): the fixture carries a |
| 34 | real Vite config file, exactly like a normal SvelteKit v3 project — the |
| 35 | user registers `sveltekit(...)` themselves with a kit `alias` |
| 36 | (`$fixture`), adds their own Vite plugin (a `virtual:fixture-marker` |
| 37 | module), and even declares a user adapter whose `adapt()` **throws**. The |
| 38 | integration must load the file natively (alias + virtual module observable |
| 39 | in live and dev) while replacing the user adapter with the deploy target's |
| 40 | (a green live build is the proof; a warning is logged). |
| 41 | |
| 42 | There is no `svelte.config.js` or `wrangler.json` (kit v3 errors on a |
| 43 | `svelte.config.js`; all kit options live in `vite.config.ts`). The deploy |
| 44 | target's in-memory Cloudflare adapter is injected by |
| 45 | `@alchemy.run/frontend-frameworks/sveltekit` into the user's `sveltekit()` call, and the |
| 46 | worker/preview config comes from `e2e.config.ts` via the harness's |
| 47 | target-scoped carriage (`target.cloudflare.worker` for the worker config, |
| 48 | `target.cloudflare.preview` for the miniflare preview); the deploy target |
| 49 | itself defaults to `@alchemy.run/frontend-frameworks/sveltekit/cloudflare`. |
| 50 | |
| 51 | ## Commands |
| 52 | |
| 53 | ```sh |
| 54 | bun run build # e2e build — kit build + in-memory adapt() + rolldown re-bundle -> dist/build.json |
| 55 | bun run preview # e2e preview — miniflare over dist/build.json + .svelte-kit/cloudflare assets |
| 56 | bun run dev # e2e dev --port 3103 — kit's own Vite dev server (Node SSR, proxy-backed platform) |
| 57 | bun run test # playwright: the same suite against both `live` (miniflare) and `dev` |
| 58 | ``` |
| 59 | |
| 60 | `bun run test` goes through `scripts/e2e.mjs`, which skips the suite on |
| 61 | Windows CI only (runner-level socket exhaustion outside this repo — see the |
| 62 | comment in that file). |
| 63 | |
| 64 | ## Modes |
| 65 | |
| 66 | - **live** — the production path: `Framework.build` produces entry-first |
| 67 | workerd-ready server modules (the Cloudflare target's rolldown finishing |
| 68 | pass) and the `.svelte-kit/cloudflare` assets directory; the harness serves |
| 69 | them with miniflare (assets binding `ASSETS`, worker invoked behind the |
| 70 | assets router). |
| 71 | - **dev** — SvelteKit's own Vite dev server (Node SSR, full HMR). `platform` |
| 72 | comes from the deploy target's proxy-backed emulator (see below). |
| 73 | |
| 74 | Both modes run the same Playwright suite in `test/smoke.test.ts`, which shares |
| 75 | one worker-scoped server per mode (a single dev server / miniflare instance |
| 76 | for the whole file — do not add per-test servers). |
| 77 | |
| 78 | ## The dev platform seam |
| 79 | |
| 80 | Dev runs kit's Node SSR; the Cloudflare target's adapter `emulate()` serves |
| 81 | `platform` through **cloudflare-runtime's platform proxy** (our wrangler-free |
| 82 | `getPlatformProxy`): a workerd instance hosts the fixture's declared worker |
| 83 | bindings and opens lazily on the first SSR request. |
| 84 | |
| 85 | - `platform.env` — every declared binding, callable from Node. `Text`/`Json` |
| 86 | values are materialised; resource bindings (KV, D1, DO, ...) are lazy |
| 87 | stubs that round-trip real calls to workerd (`FIXTURE_KV.put/get` in |
| 88 | `/platform` exercises this). Framework-level `dev.env` literals override |
| 89 | same-named proxied values (`FIXTURE_OVERRIDE` asserts this). |
| 90 | - `platform.ctx` — wrangler-parity no-op `waitUntil` / |
| 91 | `passThroughOnException` mock. |
| 92 | - `platform.caches` — **actually round-trips** (in-memory store in the proxy |
| 93 | worker; wrangler's dev `caches` is a no-op). The suite asserts |
| 94 | `cached: true` on the second request in _both_ modes. |
| 95 | - `platform.cf` — the same mock object miniflare falls back to (`colo`, |
| 96 | `country`, ...). |
| 97 | - During prerendering, `platform.env` access **throws** (mirroring upstream): |
| 98 | prerenderable routes must not depend on request-time bindings. |
| 99 | |
| 100 | Inherited proxy limitations (documented in |
| 101 | `cloudflare-runtime/platform-proxy`): binding methods returning rich class |
| 102 | instances (e.g. `R2Object`) are unsupported, intermediate values like |
| 103 | `DurableObjectId` need an explicit `await` before synchronous use, and |
| 104 | `connect()` is unsupported. |
| 105 | |
| 106 | ## The caches wrapper (live) |
| 107 | |
| 108 | In live mode the worker entry is the generated shim from |
| 109 | `@alchemy.run/frontend-frameworks/sveltekit/cloudflare`, which replaces the upstream adapter's |
| 110 | `worktop/cfw.cache` dependency with an inline pragma-cache over |
| 111 | `caches.default`: GET/HEAD responses carrying a `cache-control` header are |
| 112 | cached and served from cache unless the request says `no-cache`; |
| 113 | `Set-Cookie` responses are marked `private=Set-Cookie`. Handlers also receive |
| 114 | the real Cache API via `platform.caches` (what `/api/cache` uses). Endpoint |
| 115 | responses without `cache-control` (e.g. kit's `json(...)`) are untouched by |
| 116 | the pragma-cache. |