Skip to content

Commit d17f459

Browse files
replace request.origin/path/query with request.url (#3126)
* replace request.origin/path/query with request.url * fix some stuff * typo * lint * fix test * fix xss vuln * fix test * gah * fixes * simplify * use pathname+search as key * all tests passing * lint * tidy up * update types in docs * update docs * more docs * more docs * more docs * i would be lost without conduitry * update template app * throw useful error when trying to access path/query/origin/page * $params -> $route.params, only use getters in dev, remove the word "deprecate" * update docs * finish updating load input section * update * make this section less verbose * move url into $route * update some docs * rename route store back to page * throw errors on accessing $page.path etc * fix types * fix some docs * fix migrating docs * decode path, strip prefix * tidy up * remove comment * lint * Update documentation/docs/05-modules.md Co-authored-by: Ben McCann <[email protected]> * Update documentation/migrating/04-pages.md Co-authored-by: Ben McCann <[email protected]> Co-authored-by: Ben McCann <[email protected]>
1 parent d138efe commit d17f459

File tree

74 files changed

+464
-473
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+464
-473
lines changed

documentation/docs/01-routing.md

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,19 @@ type ResponseHeaders = Record<string, string | string[]>;
5757
type RequestHeaders = Record<string, string>;
5858

5959
export type RawBody = null | Uint8Array;
60-
export interface IncomingRequest {
61-
method: string;
62-
path: string;
63-
query: URLSearchParams;
64-
headers: RequestHeaders;
65-
rawBody: RawBody;
66-
}
6760

6861
type ParameterizedBody<Body = unknown> = Body extends FormData
6962
? ReadOnlyFormData
7063
: (string | RawBody | ReadOnlyFormData) & Body;
71-
// ServerRequest is exported as Request
72-
export interface ServerRequest<Locals = Record<string, any>, Body = unknown>
73-
extends IncomingRequest {
74-
origin: string;
64+
65+
export interface Request<Locals = Record<string, any>, Body = unknown> {
66+
url: URL;
67+
method: string;
68+
headers: RequestHeaders;
69+
rawBody: RawBody;
7570
params: Record<string, string>;
7671
body: ParameterizedBody<Body>;
77-
locals: Locals; // populated by hooks handle
72+
locals: Locals;
7873
}
7974

8075
type DefaultBody = JSONResponse | Uint8Array;
@@ -89,7 +84,7 @@ export interface RequestHandler<
8984
Input = unknown,
9085
Output extends DefaultBody = DefaultBody
9186
> {
92-
(request: ServerRequest<Locals, Input>):
87+
(request: Request<Locals, Input>):
9388
| void
9489
| EndpointOutput<Output>
9590
| Promise<void | EndpointOutput<Output>>;

documentation/docs/03-loading.md

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,8 @@ export interface LoadInput<
1313
Stuff extends Record<string, any> = Record<string, any>,
1414
Session = any
1515
> {
16-
page: {
17-
origin: string;
18-
path: string;
19-
params: PageParams;
20-
query: URLSearchParams;
21-
};
16+
url: URL;
17+
params: PageParams;
2218
fetch(info: RequestInfo, init?: RequestInit): Promise<Response>;
2319
session: Session;
2420
stuff: Stuff;
@@ -42,8 +38,8 @@ Our example blog page might contain a `load` function like the following:
4238
```html
4339
<script context="module">
4440
/** @type {import('@sveltejs/kit').Load} */
45-
export async function load({ page, fetch, session, stuff }) {
46-
const url = `/blog/${page.params.slug}.json`;
41+
export async function load({ params, fetch, session, stuff }) {
42+
const url = `/blog/${params.slug}.json`;
4743
const res = await fetch(url);
4844
4945
if (res.ok) {
@@ -88,20 +84,28 @@ It is recommended that you not store pre-request state in global variables, but
8884
8985
### Input
9086

91-
The `load` function receives an object containing four fields — `page`, `fetch`, `session` and `stuff`. The `load` function is reactive, and will re-run when its parameters change, but only if they are used in the function. Specifically, if `page.query`, `page.path`, `session`, or `stuff` are used in the function, they will be re-run whenever their value changes. Note that destructuring parameters in the function declaration is enough to count as using them. In the example above, the `load({ page, fetch, session, stuff })` function will re-run every time `session` or `stuff` is changed, even though they are not used in the body of the function. If it was re-written as `load({ page, fetch })`, then it would only re-run when `page.params.slug` changes. The same reactivity applies to `page.params`, but only to the params actually used in the function. If `page.params.foo` changes, the example above would not re-run, because it did not access `page.params.foo`, only `page.params.slug`.
87+
The `load` function receives an object containing five fields — `url`, `params`, `fetch`, `session` and `stuff`. The `load` function is reactive, and will re-run when its parameters change, but only if they are used in the function. Specifically, if `url`, `session` or `stuff` are used in the function, they will be re-run whenever their value changes, and likewise for the individual properties of `params`.
9288

93-
#### page
89+
> Note that destructuring parameters in the function declaration is enough to count as using them.
9490
95-
`page` is an `{ origin, path, params, query }` object where `origin` is the URL's origin, `path` is its pathname, `params` is derived from `path` and the route filename, and `query` is an instance of [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams). Mutating `page` does not update the current URL; you should instead navigate using [`goto`](#modules-$app-navigation).
91+
#### url
9692

97-
So if the example above was `src/routes/blog/[slug].svelte` and the URL was `https://example.com/blog/some-post?foo=bar&baz&bizz=a&bizz=b`, the following would be true:
93+
`url` is an instance of [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), containing properties like the `origin`, `hostname`, `pathname` and `searchParams`.
9894

99-
- `page.origin === 'https://example.com'`
100-
- `page.path === '/blog/some-post'`
101-
- `page.params.slug === 'some-post'`
102-
- `page.query.get('foo') === 'bar'`
103-
- `page.query.has('baz')`
104-
- `page.query.getAll('bizz') === ['a', 'b']`
95+
> In some environments this is derived from request headers, which you [may need to configure](#configuration-headers), during server-side rendering
96+
97+
#### params
98+
99+
`params` is derived from `url.pathname` and the route filename.
100+
101+
For a route filename example like `src/routes/a/[b]/[...c]` and a `url.pathname` of `/a/x/y/z`, the `params` object would look like this:
102+
103+
```js
104+
{
105+
"b": "x",
106+
"c": "y/z"
107+
}
108+
```
105109

106110
#### fetch
107111

documentation/docs/04-hooks.md

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,39 +24,34 @@ type ResponseHeaders = Record<string, string | string[]>;
2424
type RequestHeaders = Record<string, string>;
2525

2626
export type RawBody = null | Uint8Array;
27-
export interface IncomingRequest {
28-
method: string;
29-
path: string;
30-
query: URLSearchParams;
31-
headers: RequestHeaders;
32-
rawBody: RawBody;
33-
}
3427

3528
type ParameterizedBody<Body = unknown> = Body extends FormData
3629
? ReadOnlyFormData
3730
: (string | RawBody | ReadOnlyFormData) & Body;
38-
// ServerRequest is exported as Request
39-
export interface ServerRequest<Locals = Record<string, any>, Body = unknown>
40-
extends IncomingRequest {
41-
origin: string;
31+
32+
export interface Request<Locals = Record<string, any>, Body = unknown> {
33+
url: URL;
34+
method: string;
35+
headers: RequestHeaders;
36+
rawBody: RawBody;
4237
params: Record<string, string>;
4338
body: ParameterizedBody<Body>;
44-
locals: Locals; // populated by hooks handle
39+
locals: Locals;
4540
}
4641

4742
type StrictBody = string | Uint8Array;
48-
// ServerResponse is exported as Response
49-
export interface ServerResponse {
43+
44+
export interface Response {
5045
status: number;
5146
headers: ResponseHeaders;
5247
body?: StrictBody;
5348
}
5449

5550
export interface Handle<Locals = Record<string, any>, Body = unknown> {
5651
(input: {
57-
request: ServerRequest<Locals, Body>;
58-
resolve(request: ServerRequest<Locals, Body>): ServerResponse | Promise<ServerResponse>;
59-
}): ServerResponse | Promise<ServerResponse>;
52+
request: Request<Locals, Body>;
53+
resolve(request: Request<Locals, Body>): Response | Promise<Response>;
54+
}): Response | Promise<Response>;
6055
}
6156
```
6257

@@ -91,9 +86,8 @@ If unimplemented, SvelteKit will log the error with default formatting.
9186

9287
```ts
9388
// Declaration types for handleError hook
94-
9589
export interface HandleError<Locals = Record<string, any>, Body = unknown> {
96-
(input: { error: Error & { frame?: string }; request: ServerRequest<Locals, Body> }): void;
90+
(input: { error: Error & { frame?: string }; request: Request<Locals, Body> }): void;
9791
}
9892
```
9993

@@ -115,9 +109,8 @@ If unimplemented, session is `{}`.
115109

116110
```ts
117111
// Declaration types for getSession hook
118-
119112
export interface GetSession<Locals = Record<string, any>, Body = unknown, Session = any> {
120-
(request: ServerRequest<Locals, Body>): Session | Promise<Session>;
113+
(request: Request<Locals, Body>): Session | Promise<Session>;
121114
}
122115
```
123116

documentation/docs/05-modules.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ Because of that, the stores are not free-floating objects: they must be accessed
5454

5555
- `getStores` is a convenience function around `getContext` that returns `{ navigating, page, session }`. This needs to be called at the top-level or synchronously during component or page initialisation.
5656

57-
The stores themselves attach to the correct context at the point of subscription, which means you can import and use them directly in components without boilerplate. However, it still needs to be called synchronously on component or page initialisation when `$`-prefix isn't used. Use `getStores` to safely `.subscribe` asynchronously instead.
57+
The stores themselves attach to the correct context at the point of subscription, which means you can import and use them directly in components without boilerplate. However, it still needs to be called synchronously on component or page initialisation when the `$`-prefix isn't used. Use `getStores` to safely `.subscribe` asynchronously instead.
5858

59-
- `navigating` is a [readable store](https://svelte.dev/tutorial/readable-stores). When navigating starts, its value is `{ from, to }`, where `from` and `to` both mirror the `page` store value. When navigating finishes, its value reverts to `null`.
60-
- `page` is a readable store whose value reflects the object passed to `load` functions — it contains `origin`, `path`, `params` and `query`. See the [`page` section](#loading-input-page) above for more details.
59+
- `navigating` is a [readable store](https://svelte.dev/tutorial/readable-stores). When navigating starts, its value is `{ from, to }`, where `from` and `to` are both [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instances. When navigating finishes, its value reverts to `null`.
60+
- `page` contains an object with the current [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL) and [`params`](#loading-input-params).
6161
- `session` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is whatever was returned from [`getSession`](#hooks-getsession). It can be written to, but this will _not_ cause changes to persist on the server — this is something you must implement yourself.
6262

6363
### $lib

documentation/docs/14-configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Permissions-Policy: interest-cohort=()
109109
110110
### headers
111111

112-
The [`page.origin`] property is derived from the request protocol (normally `https`) and the host, which is taken from the `Host` header by default.
112+
The current page or endpoint's `url` is, in some environments, derived from the request protocol (normally `https`) and the host, which is taken from the `Host` header by default.
113113

114114
If your app is behind a reverse proxy (think load balancers and CDNs) then the `Host` header will be incorrect. In most cases, the underlying protocol and host are exposed via the [`X-Forwarded-Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) and [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) headers, which can be specified in your config:
115115

documentation/migrating/04-pages.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { stores } from '@sapper/app';
3333
const { preloading, page, session } = stores();
3434
```
3535

36-
The `page` and `session` stores still exist; `preloading` has been replaced with a `navigating` store that contains `from` and `to` properties.
36+
The `page` and `session` stores still exist; `preloading` has been replaced with a `navigating` store that contains `from` and `to` properties. `page` now has `url` and `params` properties, but no `path` or `query`.
3737

3838
You access them differently in SvelteKit. `stores` is now `getStores`, but in most cases it is unnecessary since you can import `navigating`, `page` and `session` directly from [`$app/stores`](/docs#modules-$app-stores).
3939

examples/hn.svelte.dev/src/routes/[list]/[page].svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<script context="module">
22
const valid_lists = new Set(['news', 'newest', 'show', 'ask', 'jobs']);
33
4-
export async function load({ page: { params }, fetch }) {
4+
/** @type {import('@sveltejs/kit').Load} */
5+
export async function load({ params, fetch }) {
56
const list = params.list === 'top' ? 'news' : params.list === 'new' ? 'newest' : params.list;
67
78
if (!valid_lists.has(list)) {

examples/hn.svelte.dev/src/routes/__layout.svelte

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,20 @@
55
import ThemeToggler from '$lib/ThemeToggler.svelte';
66
import '../app.css';
77
8-
9-
$: section = $page.path.split('/')[1];
8+
$: section = $page.url.pathname.split('/')[1];
109
</script>
1110

12-
<Nav {section}/>
11+
<Nav {section} />
1312

1413
{#if $navigating}
15-
<PreloadingIndicator/>
14+
<PreloadingIndicator />
1615
{/if}
1716

1817
<main>
19-
<slot></slot>
18+
<slot />
2019
</main>
2120

22-
<ThemeToggler/>
21+
<ThemeToggler />
2322

2423
<style>
2524
main {
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
<script context="module">
2-
export function load({ page }) {
3-
let host = page.host;
4-
const i = host.indexOf(':');
5-
if (i >= 0) {
6-
host = host.substring(0, i);
7-
}
2+
/** @type {import('@sveltejs/kit').Load} */
3+
export function load({ url }) {
84
return {
95
redirect: '/top/1',
10-
status: host === 'localhost' || host === '127.0.0.1' ? 302 : 301
6+
status: url.hostname === 'localhost' || url.hostname === '127.0.0.1' ? 302 : 301
117
};
128
}
139
</script>

examples/hn.svelte.dev/src/routes/item/[id].svelte

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script context="module">
2-
export async function load({ page, fetch }) {
3-
const res = await fetch(`https://api.hnpwa.com/v0/item/${page.params.id}.json`);
2+
/** @type {import('@sveltejs/kit').Load} */
3+
export async function load({ params, fetch }) {
4+
const res = await fetch(`https://api.hnpwa.com/v0/item/${params.id}.json`);
45
const item = await res.json();
56
67
return { props: { item } };
@@ -24,7 +25,10 @@
2425
{#if item.domain}<small>{item.domain}</small>{/if}
2526
</a>
2627

27-
<p class="meta">{item.points} points by <a href="/user/{item.user}">{item.user}</a> {item.time_ago}</p>
28+
<p class="meta">
29+
{item.points} points by <a href="/user/{item.user}">{item.user}</a>
30+
{item.time_ago}
31+
</p>
2832

2933
{#if item.content}
3034
{@html item.content}
@@ -33,7 +37,7 @@
3337

3438
<div class="comments">
3539
{#each item.comments as comment}
36-
<Comment comment='{comment}'/>
40+
<Comment {comment} />
3741
{/each}
3842
</div>
3943
</div>
@@ -44,13 +48,13 @@
4448
}
4549
4650
.item {
47-
border-bottom: 1em solid rgba(0,0,0,0.1);
51+
border-bottom: 1em solid rgba(0, 0, 0, 0.1);
4852
margin: 0 -2em 2em -2em;
4953
padding: 0 2em 2em 2em;
5054
}
5155
5256
:global(html).dark .item {
53-
border-bottom: 1em solid rgba(255,255,255,0.1);;
57+
border-bottom: 1em solid rgba(255, 255, 255, 0.1);
5458
}
5559
5660
.main-link {
@@ -72,4 +76,4 @@
7276
.comments > :global(.comment):first-child {
7377
border-top: none;
7478
}
75-
</style>
79+
</style>

0 commit comments

Comments
 (0)