Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace request.origin/path/query with request.url #3126

Merged
merged 40 commits into from
Dec 30, 2021
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
94b0939
replace request.origin/path/query with request.url
Rich-Harris Dec 29, 2021
f17bcbf
fix some stuff
Rich-Harris Dec 29, 2021
941b3b6
typo
Rich-Harris Dec 29, 2021
55fa638
lint
Rich-Harris Dec 29, 2021
92094b1
fix test
Rich-Harris Dec 29, 2021
5c15f7e
fix xss vuln
Rich-Harris Dec 29, 2021
2bc4cd5
fix test
Rich-Harris Dec 29, 2021
39f25b1
gah
Rich-Harris Dec 29, 2021
145aaae
fixes
Rich-Harris Dec 29, 2021
9f454bf
simplify
Rich-Harris Dec 29, 2021
0a6e6cf
use pathname+search as key
Rich-Harris Dec 29, 2021
4511c61
all tests passing
Rich-Harris Dec 29, 2021
bd496f3
lint
Rich-Harris Dec 29, 2021
c581a24
tidy up
Rich-Harris Dec 29, 2021
109108f
update types in docs
Rich-Harris Dec 29, 2021
c74cfe7
update docs
Rich-Harris Dec 29, 2021
c9a2794
more docs
Rich-Harris Dec 29, 2021
921e572
more docs
Rich-Harris Dec 29, 2021
a1ae42d
more docs
Rich-Harris Dec 29, 2021
0d70de6
i would be lost without conduitry
Rich-Harris Dec 29, 2021
28d5c1e
update template app
Rich-Harris Dec 29, 2021
b12931f
throw useful error when trying to access path/query/origin/page
Rich-Harris Dec 29, 2021
bc8c0ce
$params -> $route.params, only use getters in dev, remove the word "d…
Rich-Harris Dec 29, 2021
c83cad6
update docs
Rich-Harris Dec 29, 2021
f9e80fe
finish updating load input section
Rich-Harris Dec 29, 2021
efacc75
update
Rich-Harris Dec 29, 2021
310c10a
make this section less verbose
Rich-Harris Dec 29, 2021
3a104fa
move url into $route
Rich-Harris Dec 29, 2021
1ac14f2
update some docs
Rich-Harris Dec 29, 2021
3d6a30f
rename route store back to page
Rich-Harris Dec 29, 2021
0a53d92
throw errors on accessing $page.path etc
Rich-Harris Dec 29, 2021
761ab4c
fix types
Rich-Harris Dec 29, 2021
fc4caa1
fix some docs
Rich-Harris Dec 29, 2021
76376f4
fix migrating docs
Rich-Harris Dec 29, 2021
77030b3
decode path, strip prefix
Rich-Harris Dec 29, 2021
f0b6825
tidy up
Rich-Harris Dec 29, 2021
b1aa476
remove comment
Rich-Harris Dec 29, 2021
a12cfda
lint
Rich-Harris Dec 29, 2021
8f2c909
Update documentation/docs/05-modules.md
Rich-Harris Dec 30, 2021
2126c04
Update documentation/migrating/04-pages.md
Rich-Harris Dec 30, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions documentation/docs/01-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,19 @@ type ResponseHeaders = Record<string, string | string[]>;
type RequestHeaders = Record<string, string>;

export type RawBody = null | Uint8Array;
export interface IncomingRequest {
method: string;
path: string;
query: URLSearchParams;
headers: RequestHeaders;
rawBody: RawBody;
}

type ParameterizedBody<Body = unknown> = Body extends FormData
? ReadOnlyFormData
: (string | RawBody | ReadOnlyFormData) & Body;
// ServerRequest is exported as Request
export interface ServerRequest<Locals = Record<string, any>, Body = unknown>
extends IncomingRequest {
origin: string;

export interface Request<Locals = Record<string, any>, Body = unknown> {
url: URL;
method: string;
headers: RequestHeaders;
rawBody: RawBody;
params: Record<string, string>;
body: ParameterizedBody<Body>;
locals: Locals; // populated by hooks handle
locals: Locals;
}

type DefaultBody = JSONResponse | Uint8Array;
Expand All @@ -89,7 +84,7 @@ export interface RequestHandler<
Input = unknown,
Output extends DefaultBody = DefaultBody
> {
(request: ServerRequest<Locals, Input>):
(request: Request<Locals, Input>):
| void
| EndpointOutput<Output>
| Promise<void | EndpointOutput<Output>>;
Expand Down
40 changes: 22 additions & 18 deletions documentation/docs/03-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@ export interface LoadInput<
Stuff extends Record<string, any> = Record<string, any>,
Session = any
> {
page: {
origin: string;
path: string;
params: PageParams;
query: URLSearchParams;
};
url: URL;
params: PageParams;
Conduitry marked this conversation as resolved.
Show resolved Hide resolved
fetch(info: RequestInfo, init?: RequestInit): Promise<Response>;
session: Session;
stuff: Stuff;
Expand All @@ -42,8 +38,8 @@ Our example blog page might contain a `load` function like the following:
```html
<script context="module">
/** @type {import('@sveltejs/kit').Load} */
export async function load({ page, fetch, session, stuff }) {
const url = `/blog/${page.params.slug}.json`;
export async function load({ params, fetch, session, stuff }) {
const url = `/blog/${params.slug}.json`;
const res = await fetch(url);

if (res.ok) {
Expand Down Expand Up @@ -88,20 +84,28 @@ It is recommended that you not store pre-request state in global variables, but

### Input

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`.
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`.

#### page
> Note that destructuring parameters in the function declaration is enough to count as using them.

`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).
#### url

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:
`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`.

- `page.origin === 'https://example.com'`
- `page.path === '/blog/some-post'`
- `page.params.slug === 'some-post'`
- `page.query.get('foo') === 'bar'`
- `page.query.has('baz')`
- `page.query.getAll('bizz') === ['a', 'b']`
> In some environments this is derived from request headers, which you [may need to configure](#configuration-headers), during server-side rendering

#### params
Conduitry marked this conversation as resolved.
Show resolved Hide resolved

`params` is derived from `url.pathname` and the route filename.

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:

```js
{
"b": "x",
"c": "y/z"
}
```

#### fetch

Expand Down
35 changes: 14 additions & 21 deletions documentation/docs/04-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,34 @@ type ResponseHeaders = Record<string, string | string[]>;
type RequestHeaders = Record<string, string>;

export type RawBody = null | Uint8Array;
export interface IncomingRequest {
method: string;
path: string;
query: URLSearchParams;
headers: RequestHeaders;
rawBody: RawBody;
}

type ParameterizedBody<Body = unknown> = Body extends FormData
? ReadOnlyFormData
: (string | RawBody | ReadOnlyFormData) & Body;
// ServerRequest is exported as Request
export interface ServerRequest<Locals = Record<string, any>, Body = unknown>
extends IncomingRequest {
origin: string;

export interface Request<Locals = Record<string, any>, Body = unknown> {
url: URL;
method: string;
headers: RequestHeaders;
rawBody: RawBody;
params: Record<string, string>;
body: ParameterizedBody<Body>;
locals: Locals; // populated by hooks handle
locals: Locals;
}

type StrictBody = string | Uint8Array;
// ServerResponse is exported as Response
export interface ServerResponse {

export interface Response {
status: number;
headers: ResponseHeaders;
body?: StrictBody;
}

export interface Handle<Locals = Record<string, any>, Body = unknown> {
(input: {
request: ServerRequest<Locals, Body>;
resolve(request: ServerRequest<Locals, Body>): ServerResponse | Promise<ServerResponse>;
}): ServerResponse | Promise<ServerResponse>;
request: Request<Locals, Body>;
resolve(request: Request<Locals, Body>): Response | Promise<Response>;
}): Response | Promise<Response>;
}
```

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

```ts
// Declaration types for handleError hook

export interface HandleError<Locals = Record<string, any>, Body = unknown> {
(input: { error: Error & { frame?: string }; request: ServerRequest<Locals, Body> }): void;
(input: { error: Error & { frame?: string }; request: Request<Locals, Body> }): void;
}
```

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

```ts
// Declaration types for getSession hook

export interface GetSession<Locals = Record<string, any>, Body = unknown, Session = any> {
(request: ServerRequest<Locals, Body>): Session | Promise<Session>;
(request: Request<Locals, Body>): Session | Promise<Session>;
}
```

Expand Down
8 changes: 4 additions & 4 deletions documentation/docs/05-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,16 @@ import { base, assets } from '$app/paths';
import { getStores, navigating, page, session } from '$app/stores';
```

Stores are _contextual_ — they are added to the [context](https://svelte.dev/tutorial/context-api) of your root component. This means that `session` and `page` are unique to each request on the server, rather than shared between multiple requests handled by the same server simultaneously, which is what makes it safe to include user-specific data in `session`.
Stores are _contextual_ — they are added to the [context](https://svelte.dev/tutorial/context-api) of your root component. This means that `session` and `route` are unique to each request on the server, rather than shared between multiple requests handled by the same server simultaneously, which is what makes it safe to include user-specific data in `session`.
Rich-Harris marked this conversation as resolved.
Show resolved Hide resolved

Because of that, the stores are not free-floating objects: they must be accessed during component initialisation, like anything else that would be accessed with `getContext`.

- `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.

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.
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.

- `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`.
- `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.
- `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`.
- `page` contains an object with the current [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL) and [`params`](#loading-input-params).
- `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.

### $lib
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/14-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Permissions-Policy: interest-cohort=()

### headers

The [`page.origin`] property is derived from the request protocol (normally `https`) and the host, which is taken from the `Host` header by default.
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.

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:

Expand Down
4 changes: 2 additions & 2 deletions documentation/migrating/04-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ import { stores } from '@sapper/app';
const { preloading, page, session } = stores();
```

The `page` and `session` stores still exist; `preloading` has been replaced with a `navigating` store that contains `from` and `to` properties.
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`.

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).
You access them differently in SvelteKit. `stores` is now `getStores`, but in most cases it is unnecessary since you can import `navigating`, `route` and `session` directly from [`$app/stores`](/docs#modules-$app-stores).
Rich-Harris marked this conversation as resolved.
Show resolved Hide resolved

### Routing

Expand Down
3 changes: 2 additions & 1 deletion examples/hn.svelte.dev/src/routes/[list]/[page].svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script context="module">
const valid_lists = new Set(['news', 'newest', 'show', 'ask', 'jobs']);

export async function load({ page: { params }, fetch }) {
/** @type {import('@sveltejs/kit').Load} */
export async function load({ params, fetch }) {
const list = params.list === 'top' ? 'news' : params.list === 'new' ? 'newest' : params.list;

if (!valid_lists.has(list)) {
Expand Down
11 changes: 5 additions & 6 deletions examples/hn.svelte.dev/src/routes/__layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@
import ThemeToggler from '$lib/ThemeToggler.svelte';
import '../app.css';


$: section = $page.path.split('/')[1];
$: section = $page.url.pathname.split('/')[1];
</script>

<Nav {section}/>
<Nav {section} />

{#if $navigating}
<PreloadingIndicator/>
<PreloadingIndicator />
{/if}

<main>
<slot></slot>
<slot />
</main>

<ThemeToggler/>
<ThemeToggler />

<style>
main {
Expand Down
10 changes: 3 additions & 7 deletions examples/hn.svelte.dev/src/routes/index.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
<script context="module">
export function load({ page }) {
let host = page.host;
const i = host.indexOf(':');
if (i >= 0) {
host = host.substring(0, i);
}
/** @type {import('@sveltejs/kit').Load} */
export function load({ url }) {
return {
redirect: '/top/1',
status: host === 'localhost' || host === '127.0.0.1' ? 302 : 301
status: url.hostname === 'localhost' || url.hostname === '127.0.0.1' ? 302 : 301
};
}
</script>
18 changes: 11 additions & 7 deletions examples/hn.svelte.dev/src/routes/item/[id].svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script context="module">
export async function load({ page, fetch }) {
const res = await fetch(`https://api.hnpwa.com/v0/item/${page.params.id}.json`);
/** @type {import('@sveltejs/kit').Load} */
export async function load({ params, fetch }) {
const res = await fetch(`https://api.hnpwa.com/v0/item/${params.id}.json`);
const item = await res.json();

return { props: { item } };
Expand All @@ -24,7 +25,10 @@
{#if item.domain}<small>{item.domain}</small>{/if}
</a>

<p class="meta">{item.points} points by <a href="/user/{item.user}">{item.user}</a> {item.time_ago}</p>
<p class="meta">
{item.points} points by <a href="/user/{item.user}">{item.user}</a>
{item.time_ago}
</p>

{#if item.content}
{@html item.content}
Expand All @@ -33,7 +37,7 @@

<div class="comments">
{#each item.comments as comment}
<Comment comment='{comment}'/>
<Comment {comment} />
{/each}
</div>
</div>
Expand All @@ -44,13 +48,13 @@
}

.item {
border-bottom: 1em solid rgba(0,0,0,0.1);
border-bottom: 1em solid rgba(0, 0, 0, 0.1);
margin: 0 -2em 2em -2em;
padding: 0 2em 2em 2em;
}

:global(html).dark .item {
border-bottom: 1em solid rgba(255,255,255,0.1);;
border-bottom: 1em solid rgba(255, 255, 255, 0.1);
}

.main-link {
Expand All @@ -72,4 +76,4 @@
.comments > :global(.comment):first-child {
border-top: none;
}
</style>
</style>
11 changes: 6 additions & 5 deletions examples/hn.svelte.dev/src/routes/user/[name].svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<script context="module">
export const hydrate = false;

export async function load({ page, fetch }) {
const res = await fetch(`https://api.hnpwa.com/v0/user/${page.params.name}.json`);
/** @type {import('@sveltejs/kit').Load} */
export async function load({ params, fetch }) {
const res = await fetch(`https://api.hnpwa.com/v0/user/${params.name}.json`);
const user = await res.json();

return {
props: {
name: page.params.name,
name: params.name,
user
}
};
Expand Down Expand Up @@ -39,7 +40,7 @@

{#if user.about}
<div class="about">
{@html '<p>' + user.about }
{@html '<p>' + user.about}
</div>
{/if}
</div>
</div>
2 changes: 1 addition & 1 deletion packages/create-svelte/templates/default/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const handle: Handle = async ({ request, resolve }) => {
request.locals.userid = cookies.userid || uuid();

// TODO https://github.com/sveltejs/kit/issues/1046
const method = request.query.get('_method');
const method = request.url.searchParams.get('_method');
if (method) {
request.method = method.toUpperCase();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
</svg>
<ul>
<li class:active={$page.path === '/'}><a sveltekit:prefetch href="/">Home</a></li>
<li class:active={$page.path === '/about'}><a sveltekit:prefetch href="/about">About</a></li>
<li class:active={$page.path === '/todos'}><a sveltekit:prefetch href="/todos">Todos</a></li>
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
<li class:active={$page.url.pathname === '/about'}>
<a sveltekit:prefetch href="/about">About</a>
</li>
<li class:active={$page.url.pathname === '/todos'}>
<a sveltekit:prefetch href="/todos">Todos</a>
</li>
</ul>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
Expand Down
Loading