src/
├── index.ts # Adapter entry point (build-time)
├── adapter/ # Build-time: runs during `adapt()`
│ ├── build-function.ts # Bundles mini-workers for API routes
│ ├── detect-cookies.ts # Static analysis: does endpoint use cookies?
│ ├── detect-locals.ts # Static analysis: does endpoint use locals?
│ └── generate-params.ts# Generates route param extractors from SvelteKit patterns
├── runtime/ # Runtime: runs inside OpenWorkers
│ ├── worker.ts # Main SSR worker (uses server.respond())
│ ├── function-worker.ts# Mini-worker template for API routes
│ ├── cookies.ts # Cookie helpers (wraps SvelteKit internals)
│ └── routing.ts # Route matching (re-exports SvelteKit's parseRouteId)
└── shims/ # Polyfills for missing Node APIs
├── async-hooks.ts # Stub for node:async_hooks (SvelteKit imports it)
├── node-path.ts # Minimal path module (nodeCompat option)
├── node-fs.ts # Stub fs module (nodeCompat option)
└── node-url.ts # Minimal url module (nodeCompat option)
Runs on the developer's machine during vite build. The adapter:
- Writes client assets and prerendered pages to
build/assets/ - Generates a manifest module with the route table
- Bundles the main SSR worker with esbuild (
runtime/worker.ts+ app server) - Optionally generates separate mini-workers for each
+server.tsendpoint
Runs inside OpenWorkers when a request arrives.
worker.ts(main worker): Initializes the SvelteKitServer, serves static assets via theASSETSbinding, and delegates dynamic requests toserver.respond(). Hooks (hooks.server.ts) run here.function-worker.ts(mini-workers): Lightweight handler that calls endpoint handlers (GET,POST, ...) directly withoutserver.respond(). This means hooks do NOT run andlocalsis always{}.cookies.ts: Re-exports SvelteKit's internal cookie functions. Used at runtime by function workers via thelib:cookiesalias (resolves todist/runtime/cookies.js).routing.ts: Re-exports SvelteKit's internal routing functions. Used at build-time only bygenerate-params.ts(forparseRouteId). The builtdist/runtime/routing.jsis dead code — no worker ever imports it. It lives inruntime/because it wraps SvelteKit runtime internals, but it could arguably live inadapter/.
Polyfills injected via esbuild aliases to replace Node built-ins that don't exist in the OpenWorkers runtime:
node:async_hooksis always shimmed (SvelteKit importsAsyncLocalStorage)path,fs,urlare shimmed only whennodeCompat: true
The adapter uses esbuild's alias option to resolve virtual imports at bundle time:
| Import | Resolves to | Phase |
|---|---|---|
SERVER / MANIFEST |
Generated manifest module | Main worker build |
ENDPOINT |
The actual +server.ts file |
Function worker build |
lib:cookies |
dist/runtime/cookies.js |
Function worker build |
lib:routing |
Generated params extractor (temp file) | Function worker build |
lib:hooks |
Compiled hooks.server.js from project |
Function worker build (when endpoint uses locals) |
sveltekit:cookie |
SvelteKit's internal cookie module | Adapter + function build |
sveltekit:routing |
SvelteKit's internal routing module | Adapter + function build |
node:async_hooks |
dist/shims/async-hooks.js |
Main worker build |
Both files are thin re-exports of SvelteKit internals, but they work very differently:
cookies.ts is a true runtime module. function-worker.ts declares lib:cookies as an external import, and at per-endpoint build time, build-function.ts aliases it to dist/runtime/cookies.js. The cookie code ends up in the final worker bundle.
routing.ts is build-time only. generate-params.ts imports parseRouteId from it to generate a params extractor module (a temp .js file with the route's regex baked in). That generated module imports exec from sveltekit:routing directly — it never goes through routing.ts. At per-endpoint build time, lib:routing aliases to the generated temp file, not to dist/runtime/routing.js. So dist/runtime/routing.js is built but never imported by anything.
vite build
└─ adapter.adapt()
├─ writeClient() + writePrerendered() → build/assets/
├─ generate manifest.js (routes, prerendered set)
├─ esbuild: worker.ts + manifest → build/_worker.js
└─ (if functions: true)
└─ for each +server.ts:
└─ esbuild: function-worker.ts + endpoint → build/functions/<name>.js
The main worker runs server.respond() which executes the full SvelteKit request lifecycle, including hooks.server.ts where event.locals is typically populated.
Function workers call endpoint handlers directly without server.respond(). By default, locals is {}. However, when an endpoint actually uses locals (detected via static analysis, same pattern as WITH_COOKIES / WITH_PARAMS), and the project has a hooks.server.ts, the adapter sets WITH_HOOKS: true and aliases lib:hooks to the compiled hooks file. At runtime, the function worker wraps the handler call with the project's handle() hook, which populates locals before the handler runs.
This keeps function workers lightweight: endpoints that don't use locals pay no cost, while those that do get the hook pipeline automatically.
SvelteKit's redirect() and error() throw class instances. When bundled separately, the function-worker gets its own copy of these classes, so instanceof checks fail.
Instead, function-worker uses duck-typing:
- Redirect:
error.status >= 300 && error.status < 400 && error.location - HttpError:
error.status && error.body
This works regardless of how the error class was instantiated.