1# @fixtures/sveltekit
2
3E2E fixture for `@alchemy.run/frontend-frameworks/sveltekit` — a SvelteKit app built and served
4without 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
42There 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
44target's in-memory Cloudflare adapter is injected by
45`@alchemy.run/frontend-frameworks/sveltekit` into the user's `sveltekit()` call, and the
46worker/preview config comes from `e2e.config.ts` via the harness's
47target-scoped carriage (`target.cloudflare.worker` for the worker config,
48`target.cloudflare.preview` for the miniflare preview); the deploy target
49itself defaults to `@alchemy.run/frontend-frameworks/sveltekit/cloudflare`.
50
51## Commands
52
53```sh
54bun run build # e2e build — kit build + in-memory adapt() + rolldown re-bundle -> dist/build.json
55bun run preview # e2e preview — miniflare over dist/build.json + .svelte-kit/cloudflare assets
56bun run dev # e2e dev --port 3103 — kit's own Vite dev server (Node SSR, proxy-backed platform)
57bun 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
61Windows CI only (runner-level socket exhaustion outside this repo — see the
62comment 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
74Both modes run the same Playwright suite in `test/smoke.test.ts`, which shares
75one worker-scoped server per mode (a single dev server / miniflare instance
76for the whole file — do not add per-test servers).
77
78## The dev platform seam
79
80Dev 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
83bindings 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
100Inherited proxy limitations (documented in
101`cloudflare-runtime/platform-proxy`): binding methods returning rich class
102instances (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
108In 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
112cached and served from cache unless the request says `no-cache`;
113`Set-Cookie` responses are marked `private=Set-Cookie`. Handlers also receive
114the real Cache API via `platform.caches` (what `/api/cache` uses). Endpoint
115responses without `cache-control` (e.g. kit's `json(...)`) are untouched by
116the pragma-cache.