Skip to content

Commit

Permalink
merge master -> page-stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Jan 10, 2022
2 parents 2ea3c93 + bcedf8c commit e4ef0d3
Show file tree
Hide file tree
Showing 27 changed files with 132 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .changeset/purple-cycles-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Fallthrough is now explicit and layout components now also support fallthrough
13 changes: 8 additions & 5 deletions documentation/docs/01-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,18 @@ export interface EndpointOutput<Body extends DefaultBody = DefaultBody> {
body?: Body;
}

export type MaybePromise<T> = T | Promise<T>;

export interface Fallthrough {
fallthrough?: true;
}

export interface RequestHandler<
Locals = Record<string, any>,
Input = unknown,
Output extends DefaultBody = DefaultBody
> {
(request: Request<Locals, Input>):
| void
| EndpointOutput<Output>
| Promise<void | EndpointOutput<Output>>;
(request: Request<Locals, Input>): MaybePromise<Fallthrough | EndpointOutput<Output>>;
}
```

Expand Down Expand Up @@ -125,7 +128,7 @@ The job of this function is to return a `{ status, headers, body }` object repre

If the returned `body` is an object, and no `content-type` header is returned, it will automatically be turned into a JSON response. (Don't worry about `$lib`, we'll get to that [later](#modules-$lib).)

> Returning nothing is equivalent to an explicit 404 response.
> If `{fallthrough: true}` is returned SvelteKit will [fall through](#routing-advanced-fallthrough-routes) to other routes until something responds, or will respond with a generic 404.
For endpoints that handle other HTTP methods, like POST, export the corresponding function:

Expand Down
10 changes: 7 additions & 3 deletions documentation/docs/03-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ A component that defines a page or a layout can export a `load` function that ru
// Declaration types for Loading
// * declarations that are not exported are for internal use

export interface Fallthrough {
fallthrough?: true;
}

export interface LoadInput<
PageParams extends Record<string, string> = Record<string, string>,
Stuff extends Record<string, any> = Record<string, any>,
Expand All @@ -20,7 +24,7 @@ export interface LoadInput<
stuff: Stuff;
}

export interface LoadOutput<
export type LoadOutput<
Props extends Record<string, any> = Record<string, any>,
Stuff extends Record<string, any> = Record<string, any>
> {
Expand All @@ -30,7 +34,7 @@ export interface LoadOutput<
props?: Props;
stuff?: Stuff;
maxage?: number;
}
} | Fallthrough
```

Our example blog page might contain a `load` function like the following:
Expand Down Expand Up @@ -62,7 +66,7 @@ Our example blog page might contain a `load` function like the following:
`load` is similar to `getStaticProps` or `getServerSideProps` in Next.js, except that it runs on both the server and the client.

If `load` returns nothing, SvelteKit will [fall through](#routing-advanced-fallthrough-routes) to other routes until something responds, or will respond with a generic 404.
If `load` returns `{fallthrough: true}`, SvelteKit will [fall through](#routing-advanced-fallthrough-routes) to other routes until something responds, or will respond with a generic 404.

SvelteKit's `load` receives an implementation of `fetch`, which has the following special properties:

Expand Down
14 changes: 6 additions & 8 deletions packages/kit/src/runtime/client/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -590,8 +590,9 @@ export class Renderer {

const loaded = await module.load.call(null, load_input);

// if the page component returns nothing from load, fall through
if (!loaded) return;
if (!loaded) {
throw new Error('load function must return a value');
}

node.loaded = normalize(loaded);
if (node.loaded.stuff) node.stuff = node.loaded.stuff;
Expand Down Expand Up @@ -668,9 +669,10 @@ export class Renderer {
stuff
});

const is_leaf = i === a.length - 1;

if (node && node.loaded) {
if (node.loaded.fallthrough) {
return;
}
if (node.loaded.error) {
status = node.loaded.status;
error = node.loaded.error;
Expand All @@ -687,10 +689,6 @@ export class Renderer {
if (node.loaded.stuff) {
stuff_changed = true;
}
} else if (is_leaf && module.load) {
// if the leaf node has a `load` function
// that returns nothing, fall through
return;
}
} else {
node = previous;
Expand Down
7 changes: 4 additions & 3 deletions packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@ export async function render_endpoint(request, route, match) {
const response = await handler(request);
const preface = `Invalid response from route ${request.url.pathname}`;

if (!response) {
return;
}
if (typeof response !== 'object') {
return error(`${preface}: expected an object, got ${typeof response}`);
}

if (response.fallthrough) {
return;
}

let { status = 200, body, headers = {} } = response;

headers = lowercase_keys(headers);
Expand Down
14 changes: 6 additions & 8 deletions packages/kit/src/runtime/server/page/load_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { is_root_relative, resolve } from '../../../utils/url.js';
* $session: any;
* stuff: Record<string, any>;
* prerender_enabled: boolean;
* is_leaf: boolean;
* is_error: boolean;
* status?: number;
* error?: Error;
Expand All @@ -34,7 +33,6 @@ export async function load_node({
$session,
stuff,
prerender_enabled,
is_leaf,
is_error,
status,
error
Expand Down Expand Up @@ -294,16 +292,16 @@ export async function load_node({
}

loaded = await module.load.call(null, load_input);

if (!loaded) {
throw new Error(`load function must return a value${options.dev ? ` (${node.entry})` : ''}`);
}
} else {
loaded = {};
}

// if leaf node (i.e. page component) has a load function
// that returns nothing, we fall through to the next one
if (!loaded && is_leaf && !is_error) return;

if (!loaded) {
throw new Error(`${node.entry} - load must return a value except for page fall through`);
if (loaded.fallthrough && !is_error) {
return;
}

return {
Expand Down
2 changes: 0 additions & 2 deletions packages/kit/src/runtime/server/page/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export async function respond(opts) {
node,
stuff,
prerender_enabled: is_prerender_enabled(options, node, state),
is_leaf: i === nodes.length - 1,
is_error: false
});

Expand Down Expand Up @@ -147,7 +146,6 @@ export async function respond(opts) {
node: error_node,
stuff: node_loaded.stuff,
prerender_enabled: is_prerender_enabled(options, error_node, state),
is_leaf: false,
is_error: true,
status,
error
Expand Down
2 changes: 0 additions & 2 deletions packages/kit/src/runtime/server/page/respond_with_error.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export async function respond_with_error({ request, options, state, $session, st
$session,
stuff: {},
prerender_enabled: is_prerender_enabled(options, default_error, state),
is_leaf: false,
is_error: false
})
);
Expand All @@ -56,7 +55,6 @@ export async function respond_with_error({ request, options, state, $session, st
$session,
stuff: layout_loaded ? layout_loaded.stuff : {},
prerender_enabled: is_prerender_enabled(options, default_error, state),
is_leaf: false,
is_error: true,
status,
error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ export function get() {
}

/** @type {import('@sveltejs/kit').RequestHandler} */
export function del() {}
export function del() {
return { fallthrough: true };
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export function get({ params }) {
body: { type: 'animal' }
};
}
return { fallthrough: true };
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
};
}
}
return { fallthrough: true };
}
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export function get({ params }) {
body: { type: 'mineral' }
};
}
return { fallthrough: true };
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
};
}
}
return { fallthrough: true };
}
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export function get({ params }) {
body: { type: 'vegetable' }
};
}
return { fallthrough: true };
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
};
}
}
return { fallthrough: true };
}
</script>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script context="module">
/** @type {import("@sveltejs/kit").Load} */
export async function load({ params }) {
if (params.foo !== 'okay') {
return { fallthrough: true };
}
return {};
}
</script>

<slot />
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
import { page } from '$app/stores';
</script>

<h1>foo is {$page.params.foo}</h1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script context="module">
/** @type {import("@sveltejs/kit").Load} */
export async function load({ params }) {
if (params.xyz !== 'ok') {
return { fallthrough: true };
}
return {};
}
</script>

<slot />
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
import { page } from '$app/stores';
</script>

<h1>xyz is {$page.params.xyz}</h1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<a href="/routing/fallthrough-layout/okay">okay</a>
<a href="/routing/fallthrough-layout/ok">ok</a>
<a href="/routing/fallthrough-layout/notok">notok</a>

<slot />
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
return {
props: {}
};
} else {
return;
}
return { fallthrough: true };
};
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ let random = 0;
/** @type {import('@sveltejs/kit').RequestHandler<any, FormData>} */
export function post({ body }) {
random = +body.get('random');
return { fallthrough: true };
}

export function get() {
Expand Down
11 changes: 11 additions & 0 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,17 @@ test.describe.parallel('Routing', () => {
expect(await page.textContent('h1')).toBe('404');
});

test('dynamic fallthrough of layout', async ({ page, clicknav }) => {
await page.goto('/routing/fallthrough-layout/okay');
expect(await page.textContent('h1')).toBe('foo is okay');

await clicknav('[href="/routing/fallthrough-layout/ok"]');
expect(await page.textContent('h1')).toBe('xyz is ok');

await clicknav('[href="/routing/fallthrough-layout/notok"]');
expect(await page.textContent('h1')).toBe('404');
});

test('last parameter in a segment wins in cases of ambiguity', async ({ page, clicknav }) => {
await page.goto('/routing/split-params');
await clicknav('[href="/routing/split-params/x-y-z"]');
Expand Down
6 changes: 4 additions & 2 deletions packages/kit/types/endpoint.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ServerRequest } from './hooks';
import { JSONString, MaybePromise, ResponseHeaders } from './helper';
import { JSONString, MaybePromise, ResponseHeaders, Either, Fallthrough } from './helper';

type DefaultBody = JSONString | Uint8Array;

Expand All @@ -14,5 +14,7 @@ export interface RequestHandler<
Input = unknown,
Output extends DefaultBody = DefaultBody
> {
(request: ServerRequest<Locals, Input>): MaybePromise<void | EndpointOutput<Output>>;
(request: ServerRequest<Locals, Input>): MaybePromise<
Either<EndpointOutput<Output>, Fallthrough>
>;
}
12 changes: 12 additions & 0 deletions packages/kit/types/helper.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,15 @@ export type RecursiveRequired<T> = {
? Extract<T[K], Function> // only take the Function type.
: T[K]; // Use the exact type for everything else
};

type Only<T, U> = {
[P in keyof T]: T[P];
} & {
[P in keyof U]?: never;
};

export type Either<T, U> = Only<T, U> | Only<U, T>;

export interface Fallthrough {
fallthrough: true;
}
20 changes: 12 additions & 8 deletions packages/kit/types/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ServerResponse
} from './hooks';
import { Load } from './page';
import { Either, Fallthrough } from './helper';

type PageId = string;

Expand Down Expand Up @@ -219,13 +220,16 @@ export interface BuildData {
entries: string[];
}

export interface NormalizedLoadOutput {
status: number;
error?: Error;
redirect?: string;
props?: Record<string, any> | Promise<Record<string, any>>;
stuff?: Record<string, any>;
maxage?: number;
}
export type NormalizedLoadOutput = Either<
{
status: number;
error?: Error;
redirect?: string;
props?: Record<string, any> | Promise<Record<string, any>>;
stuff?: Record<string, any>;
maxage?: number;
},
Fallthrough
>;

export type TrailingSlash = 'never' | 'always' | 'ignore';
Loading

0 comments on commit e4ef0d3

Please sign in to comment.