Skip to content

Commit bab711e

Browse files
feat: dev/preview platform emulation (#11730)
* introduce devPlatform kit configuration * Revert "introduce devPlatform kit configuration" This reverts commit 4a847e4. * add emulatPlatform method to adapters * use latest wrangler release * make wrangler a peer dependency of the cloudflare adapters * add emulate function to adapter API * regenerate types * fix * move cloudflare adapter changes into separate PR * lockfile * emulate platform during prerender * fix types * test * docs * regenerate types * reset pnpm-lock.yaml * prettier * goddammit * Create chatty-walls-warn.md --------- Co-authored-by: Dario Piotrowicz <[email protected]> Co-authored-by: Rich Harris <[email protected]>
1 parent 6161351 commit bab711e

File tree

16 files changed

+149
-8
lines changed

16 files changed

+149
-8
lines changed

.changeset/chatty-walls-warn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sveltejs/kit": minor
3+
---
4+
5+
feat: dev/preview/prerender platform emulation

documentation/docs/25-build-and-deploy/99-writing-adapters.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: Writing adapters
44

55
If an adapter for your preferred environment doesn't yet exist, you can build your own. We recommend [looking at the source for an adapter](https://github.com/sveltejs/kit/tree/main/packages) to a platform similar to yours and copying it as a starting point.
66

7-
Adapters packages must implement the following API, which creates an `Adapter`:
7+
Adapter packages implement the following API, which creates an `Adapter`:
88

99
```js
1010
// @errors: 2322
@@ -21,6 +21,14 @@ export default function (options) {
2121
async adapt(builder) {
2222
// adapter implementation
2323
},
24+
async emulate() {
25+
return {
26+
async platform({ config, prerender }) {
27+
// the returned object becomes `event.platform` during dev, build and
28+
// preview. Its shape is that of `App.Platform`
29+
}
30+
}
31+
},
2432
supports: {
2533
read: ({ config, route }) => {
2634
// Return `true` if the route with the given `config` can use `read`
@@ -34,6 +42,8 @@ export default function (options) {
3442
}
3543
```
3644

45+
Of these, `name` and `adapt` are required. `emulate` and `supports` are optional.
46+
3747
Within the `adapt` method, there are a number of things that an adapter should do:
3848

3949
- Clear out the build directory

packages/kit/src/core/postbuild/prerender.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
9292
/** @type {import('types').ValidatedKitConfig} */
9393
const config = (await load_config()).kit;
9494

95+
const emulator = await config.adapter?.emulate?.();
96+
9597
/** @type {import('types').Logger} */
9698
const log = logger({ verbose });
9799

@@ -211,7 +213,8 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
211213

212214
// stuff in `static`
213215
return readFileSync(join(config.files.assets, file));
214-
}
216+
},
217+
emulator
215218
});
216219

217220
const encoded_id = response.headers.get('x-sveltekit-routeid');

packages/kit/src/exports/public.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export interface Adapter {
4545
*/
4646
read?: (details: { config: any; route: { id: string } }) => boolean;
4747
};
48+
/**
49+
* Creates an `Emulator`, which allows the adapter to influence the environment
50+
* during dev, build and prerendering
51+
*/
52+
emulate?(): MaybePromise<Emulator>;
4853
}
4954

5055
export type LoadProperties<input extends Record<string, any> | void> = input extends void
@@ -260,6 +265,17 @@ export interface Cookies {
260265
): string;
261266
}
262267

268+
/**
269+
* A collection of functions that influence the environment during dev, build and prerendering
270+
*/
271+
export class Emulator {
272+
/**
273+
* A function that is called with the current route `config` and `prerender` option
274+
* and returns an `App.Platform` object
275+
*/
276+
platform?(details: { config: any; prerender: PrerenderOption }): MaybePromise<App.Platform>;
277+
}
278+
263279
export interface KitConfig {
264280
/**
265281
* Your [adapter](https://kit.svelte.dev/docs/adapters) is run when executing `vite build`. It determines how the output is converted for different platforms.

packages/kit/src/exports/vite/dev/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ export async function dev(vite, vite_config, svelte_config) {
420420

421421
const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, '');
422422

423+
// TODO because of `RecursiveRequired`, TypeScript thinks this is guaranteed to exist, but it isn't
424+
const emulator = await svelte_config.kit.adapter?.emulate?.();
425+
423426
return () => {
424427
const serve_static_middleware = vite.middlewares.stack.find(
425428
(middleware) =>
@@ -529,7 +532,8 @@ export async function dev(vite, vite_config, svelte_config) {
529532
read: (file) => fs.readFileSync(path.join(svelte_config.kit.files.assets, file)),
530533
before_handle: (event, config, prerender) => {
531534
async_local_storage.enterWith({ event, config, prerender });
532-
}
535+
},
536+
emulator
533537
});
534538

535539
if (rendered.status === 404) {

packages/kit/src/exports/vite/preview/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ export async function preview(vite, vite_config, svelte_config) {
5151
read: (file) => createReadableStream(`${dir}/${file}`)
5252
});
5353

54+
// TODO because of `RecursiveRequired`, TypeScript thinks this is guaranteed to exist, but it isn't
55+
const emulator = await svelte_config.kit.adapter?.emulate?.();
56+
5457
return () => {
5558
// Remove the base middleware. It screws with the URL.
5659
// It also only lets through requests beginning with the base path, so that requests beginning
@@ -191,7 +194,8 @@ export async function preview(vite, vite_config, svelte_config) {
191194
if (remoteAddress) return remoteAddress;
192195
throw new Error('Could not determine clientAddress');
193196
},
194-
read: (file) => fs.readFileSync(join(svelte_config.kit.files.assets, file))
197+
read: (file) => fs.readFileSync(join(svelte_config.kit.files.assets, file)),
198+
emulator
195199
})
196200
);
197201
});

packages/kit/src/runtime/server/respond.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export async function respond(request, options, manifest, state) {
271271
}
272272
}
273273

274-
if (DEV && state.before_handle) {
274+
if (state.before_handle || state.emulator?.platform) {
275275
let config = {};
276276

277277
/** @type {import('types').PrerenderOption} */
@@ -283,11 +283,17 @@ export async function respond(request, options, manifest, state) {
283283
prerender = node.prerender ?? prerender;
284284
} else if (route.page) {
285285
const nodes = await load_page_nodes(route.page, manifest);
286-
config = get_page_config(nodes);
286+
config = get_page_config(nodes) ?? config;
287287
prerender = get_option(nodes, 'prerender') ?? false;
288288
}
289289

290-
state.before_handle(event, config, prerender);
290+
if (state.before_handle) {
291+
state.before_handle(event, config, prerender);
292+
}
293+
294+
if (state.emulator?.platform) {
295+
event.platform = await state.emulator.platform({ config, prerender });
296+
}
291297
}
292298
}
293299

packages/kit/src/types/internal.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
HandleClientError,
1616
Reroute,
1717
RequestEvent,
18-
SSRManifest
18+
SSRManifest,
19+
Emulator
1920
} from '@sveltejs/kit';
2021
import {
2122
HttpMethod,
@@ -128,6 +129,7 @@ export class InternalServer extends Server {
128129
read: (file: string) => Buffer;
129130
/** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated */
130131
before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void;
132+
emulator?: Emulator;
131133
}
132134
): Promise<Response>;
133135
}
@@ -418,6 +420,7 @@ export interface SSRState {
418420
prerender_default?: PrerenderOption;
419421
read?: (file: string) => Buffer;
420422
before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void;
423+
emulator?: Emulator;
421424
}
422425

423426
export type StrictBody = string | ArrayBufferView;

packages/kit/src/utils/route_config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ export function get_page_config(nodes) {
1616
};
1717
}
1818

19+
// TODO 3.0 always return `current`? then we can get rid of `?? {}` in other places
1920
return Object.keys(current).length ? current : undefined;
2021
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const prerender = false;
2+
3+
export const config = {
4+
message: 'hello from dynamic page'
5+
};
6+
7+
export function load({ platform }) {
8+
return {
9+
platform
10+
};
11+
}

0 commit comments

Comments
 (0)