Skip to content

Commit

Permalink
replace setup with hooks (#670)
Browse files Browse the repository at this point in the history
* replace setup with hooks (#334)

* changeset

* lint

* docs and types

* rename empty setup module

* tweak wording

* fix realworld example

* lets just make it async

* explain resolve_entry

* reduce indentation
  • Loading branch information
Rich Harris authored Mar 25, 2021
1 parent 5514245 commit d881b7e
Show file tree
Hide file tree
Showing 37 changed files with 373 additions and 340 deletions.
5 changes: 5 additions & 0 deletions .changeset/young-beds-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Replace setup with hooks
17 changes: 10 additions & 7 deletions documentation/docs/01-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,29 @@ Dynamic parameters are encoded using `[brackets]`. For example, a blog post migh

### Endpoints

Endpoints are modules written in `.js` (or `.ts`) files that export functions corresponding to HTTP methods. Each function receives HTTP `request` and `context` objects as arguments. For example our hypothetical blog page, `/blog/cool-article`, might request data from `/blog/cool-article.json`, which could be represented by a `src/routes/blog/[slug].json.js` endpoint:
Endpoints are modules written in `.js` (or `.ts`) files that export functions corresponding to HTTP methods. For example our hypothetical blog page, `/blog/cool-article`, might request data from `/blog/cool-article.json`, which could be represented by a `src/routes/blog/[slug].json.js` endpoint:

```ts
type Request = {
type Request<Context = any> = {
host: string;
method: 'GET';
headers: Record<string, string>;
path: string;
params: Record<string, string | string[]>;
query: URLSearchParams;
body: string | Buffer | ReadOnlyFormData;
context: Context; // see getContext, below
};

type Response = {
status?: number;
headers?: Record<string, string>;
body?: any;
};

type RequestHandler<Context = any> = {
(request: Request<Context>) => Response | Promise<Response>;
}
```
```js
Expand All @@ -65,10 +70,10 @@ import db from '$lib/database';
/**
* @type {import('@sveltejs/kit').RequestHandler}
*/
export async function get(request, context) {
export async function get({ params }) {
// the `slug` parameter is available because this file
// is called [slug].json.js
const { slug } = request.params;
const { slug } = params;

const article = await db.get(slug);

Expand All @@ -86,14 +91,12 @@ export async function get(request, context) {
Because this module only runs on the server (or when you build your site, if [prerendering](#prerendering)), you can freely access things like databases. (Don't worry about `$lib`, we'll get to that [later](#$lib).)

The second argument, `context`, is something you define during [setup](#setup), if necessary.

The job of this function is to return a `{status, headers, body}` object representing the response. If the returned `body` is an object, and no `content-type` header is returned, it will automatically be turned into a JSON response.

For endpoints that handle other HTTP methods, like POST, export the corresponding function:

```js
export function post(request, context) {...}
export function post(request) {...}
```

Since `delete` is a reserved word in JavaScript, DELETE requests are handled with a `del` function.
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/03-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ So if the example above was `src/routes/blog/[slug].svelte` and the URL was `/bl
#### session

`session` can be used to pass data from the server related to the current request, e.g. the current user. By default it is `undefined`. See [Setup](#setup) to learn how to use it.
`session` can be used to pass data from the server related to the current request, e.g. the current user. By default it is `undefined`. See [`getSession`](#hooks-getsession) to learn how to use it.

#### context

Expand Down
117 changes: 117 additions & 0 deletions documentation/docs/04-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
title: Hooks
---

An optional `src/hooks.js` (or `src/hooks.ts`, or `src/hooks/index.js`) file exports three functions, all optional, that run on the server — **getContext**, **getSession** and **handle**.

> The location of this file can be [configured](#configuration) as `config.kit.files.hooks`
### getContext

This function runs on every incoming request. It generates the `context` object that is available to [endpoint handlers](#routing-endpoints) as `request.context`, and used to derive the [`session`](#hooks-getsession) object available in the browser.

If unimplemented, context is `{}`.

```ts
type Incoming = {
method: string;
host: string;
headers: Headers;
path: string;
query: URLSearchParams;
body: string | Buffer | ReadOnlyFormData;
};

type GetContext<Context = any> = {
(incoming: Incoming): Context;
};
```

```js
import * as cookie from 'cookie';
import db from '$lib/db';

/** @type {import('@sveltejs/kit').GetContext} */
export async function getContext({ headers }) {
const cookies = cookie.parse(headers.cookie || '');

return {
user: (await db.get_user(cookies.session_id)) || { guest: true }
};
}
```

### getSession

This function takes the [`context`](#hooks-getcontext) object and returns a `session` object that is safe to expose to the browser. It runs whenever SvelteKit renders a page.

If unimplemented, session is `{}`.

```ts
type GetSession<Context = any, Session = any> = {
({ context }: { context: Context }): Session | Promise<Session>;
};
```

```js
/** @type {import('@sveltejs/kit').GetSession} */
export function getSession({ context }) {
return {
user: {
// only include properties needed client-side —
// exclude anything else attached to the user
// like access tokens etc
name: context.user?.name,
email: context.user?.email,
avatar: context.user?.avatar
}
};
}
```
> `session` must be serializable, which means it must not contain things like functions or custom classes, just built-in JavaScript data types
### handle
This function runs on every request, and determines the response. The second argument, `render`, calls SvelteKit's default renderer. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing endpoints programmatically, for example).
If unimplemented, defaults to `(request, render) => render(request)`.
```ts
type Request<Context = any> = {
method: string;
host: string;
headers: Headers;
path: string;
params: Record<string, string>;
query: URLSearchParams;
body: string | Buffer | ReadOnlyFormData;
context: Context;
};

type Response = {
status?: number;
headers?: Headers;
body?: any;
};

type Handle<Context = any> = (
request: Request<Context>,
render: (request: Request<Context>) => Response | Promise<Response>
) => Response | Promise<Response>;
```
```js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle(request, render) {
const response = await render(request);

return {
...response,
headers: {
...response.headers,
'x-custom-header': 'potato'
}
};
}
```
104 changes: 0 additions & 104 deletions documentation/docs/04-setup.md

This file was deleted.

31 changes: 15 additions & 16 deletions documentation/docs/05-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ SvelteKit makes a number of modules available to your application.
import { amp, browser, dev } from '$app/env';
```

* `amp` is `true` or `false` depending on the corresponding value in your [project configuration](#configuration)
* `browser` is `true` or `false` depending on whether the app is running in the browser or on the server
* `dev` is `true` in development mode, `false` in production
- `amp` is `true` or `false` depending on the corresponding value in your [project configuration](#configuration)
- `browser` is `true` or `false` depending on whether the app is running in the browser or on the server
- `dev` is `true` in development mode, `false` in production

### $app/navigation

```js
import { goto, prefetch, prefetchRoutes } from '$app/navigation';
```

* `goto(href, { replaceState, noscroll })` returns a `Promise` that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `href`. The second argument is optional. If `replaceState` is true, a new history entry won't be created. If `noscroll` is true, the browser won't scroll to the top of the page after navigation.
* `prefetch(href)` programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's `load` function with the appropriate options. This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with [sveltekit:prefetch](docs#anchor-options-sveltekit-prefetch). If the next navigation is to `href`, the values returned from `load` will be used, making navigation instantaneous. Returns a `Promise` that resolves when the prefetch is complete.
* `prefetchRoutes(routes)` — programmatically prefetches the code for routes that haven't yet been fetched. Typically, you might call this to speed up subsequent navigation. If no argument is given, all routes will be fetched, otherwise you can specify routes by any matching pathname such as `/about` (to match `src/routes/about.svelte`) or `/blog/*` (to match `src/routes/blog/[slug].svelte`). Unlike `prefetch`, this won't call `preload` for individual pages. Returns a `Promise` that resolves when the routes have been prefetched.
- `goto(href, { replaceState, noscroll })` returns a `Promise` that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `href`. The second argument is optional. If `replaceState` is true, a new history entry won't be created. If `noscroll` is true, the browser won't scroll to the top of the page after navigation.
- `prefetch(href)` programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's `load` function with the appropriate options. This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with [sveltekit:prefetch](docs#anchor-options-sveltekit-prefetch). If the next navigation is to `href`, the values returned from `load` will be used, making navigation instantaneous. Returns a `Promise` that resolves when the prefetch is complete.
- `prefetchRoutes(routes)` — programmatically prefetches the code for routes that haven't yet been fetched. Typically, you might call this to speed up subsequent navigation. If no argument is given, all routes will be fetched, otherwise you can specify routes by any matching pathname such as `/about` (to match `src/routes/about.svelte`) or `/blog/*` (to match `src/routes/blog/[slug].svelte`). Unlike `prefetch`, this won't call `preload` for individual pages. Returns a `Promise` that resolves when the routes have been prefetched.

### $app/paths

```js
import { base, assets } from '$app/paths';
```

* `base` — a root-relative (i.e. begins with a `/`) string that matches `config.kit.files.base` in your [project configuration](#configuration)
* `assets` — a root-relative or absolute path that matches `config.kit.files.assets` (after it has been resolved against `base`)
- `base` — a root-relative (i.e. begins with a `/`) string that matches `config.kit.files.base` in your [project configuration](#configuration)
- `assets` — a root-relative or absolute path that matches `config.kit.files.assets` (after it has been resolved against `base`)

### $app/stores

Expand All @@ -43,14 +43,13 @@ Stores are _contextual_ — they are added to the [context](https://svelte.dev/t

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 }`. Most of the time, you won't need to use it.
- `getStores` is a convenience function around `getContext` that returns `{ navigating, page, session }`. Most of the time, you won't need to use it.

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.

* `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 `host`, `path`, `params` and `query`
* `session` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is whatever was returned from [`getSession`](#setup-getsession). It can be written to, but this will _not_ cause changes to persist on the server — this is something you must implement yourself.

- `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 `host`, `path`, `params` and `query`
- `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 All @@ -64,6 +63,6 @@ This module is only available to [service workers](#service-workers).
import { build, files, timestamp } from '$service-worker';
```

* `build` is an array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`
* `files` is an array of URL strings representing the files in your `static` directory, or whatever directory is specified by [`config.kit.files.assets`](#configuration)
* `timestamp` is the result of calling `Date.now()` at build time. It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches
- `build` is an array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`
- `files` is an array of URL strings representing the files in your `static` directory, or whatever directory is specified by [`config.kit.files.assets`](#configuration)
- `timestamp` is the result of calling `Date.now()` at build time. It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches
4 changes: 2 additions & 2 deletions documentation/docs/13-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ module.exports = {
appDir: '_app',
files: {
assets: 'static',
hooks: 'src/hooks',
lib: 'src/lib',
routes: 'src/routes',
serviceWorker: 'src/service-worker',
setup: 'src/setup',
template: 'src/app.html'
},
host: null,
Expand Down Expand Up @@ -66,7 +66,7 @@ An object containing zero or more of the following `string` values:
- `lib` — your app's internal library, accessible throughout the codebase as `$lib`
- `routes` — the files that define the structure of your app (see [Routing](#routing))
- `serviceWorker` — the location of your service worker's entry point (see [Service workers](#service-workers))
- `setup` — the location of your setup file (see [Setup](#setup))
- `hooks` — the location of your hooks module (see [Hooks](#hooks))
- `template` — the location of the template for HTML responses

#### host
Expand Down
Loading

1 comment on commit d881b7e

@vercel
Copy link

@vercel vercel bot commented on d881b7e Mar 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.