diff --git a/.changeset/tidy-wasps-shave.md b/.changeset/tidy-wasps-shave.md new file mode 100644 index 000000000000..c05e2d48eab1 --- /dev/null +++ b/.changeset/tidy-wasps-shave.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Remove getContext in favour of request.locals diff --git a/documentation/docs/01-routing.md b/documentation/docs/01-routing.md index 9849b19ae86e..89d06ece62fd 100644 --- a/documentation/docs/01-routing.md +++ b/documentation/docs/01-routing.md @@ -55,7 +55,7 @@ type Request = { query: URLSearchParams; rawBody: string | ArrayBuffer; body: string | ArrayBuffer | ReadOnlyFormData | any; - context: Context; // see getContext, below + locals: Record; // see below }; type Response = { diff --git a/documentation/docs/04-hooks.md b/documentation/docs/04-hooks.md index f225c3167a8e..d780e0305183 100644 --- a/documentation/docs/04-hooks.md +++ b/documentation/docs/04-hooks.md @@ -2,117 +2,86 @@ 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**. +An optional `src/hooks.js` (or `src/hooks.ts`, or `src/hooks/index.js`) file exports two functions, both optional, that run on the server — **handle** and **getSession**. > The location of this file can be [configured](#configuration) as `config.kit.files.hooks` -### getContext +### handle + +This function runs on every request, and determines the response. It receives the `request` object and `render` method, which calls SvelteKit's default renderer. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing endpoints programmatically, for example). -This function runs on every incoming request. It generates the `context` object that is available to [endpoint handlers](#routing-endpoints) as `request.context` and that is used to derive the [`session`](#hooks-getsession) object available in the browser. +If unimplemented, defaults to `({ request, render }) => render(request)`. -If unimplemented, context is `{}`. +To add custom data to the request, which is passed to endpoints, populate the `request.locals` object, as shown below. ```ts -type Incoming = { +type Request> = { method: string; host: string; headers: Headers; path: string; + params: Record; query: URLSearchParams; - body: string | Buffer | ReadOnlyFormData; + rawBody: string | ArrayBuffer; + body: string | ArrayBuffer | ReadOnlyFormData | any; + locals: Locals; }; -type GetContext = { - (incoming: Incoming): Context; +type Response = { + status?: number; + headers?: Headers; + body?: any; }; + +type Handle> = ({ + request: Request, + render: (request: Request) => Promise +}) => Response | Promise; ``` ```js -import * as cookie from 'cookie'; -import db from '$lib/db'; +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ request, render }) { + request.locals.user = await getUserInformation(request.headers.cookie); -/** @type {import('@sveltejs/kit').GetContext} */ -export async function getContext({ headers }) { - const cookies = cookie.parse(headers.cookie || ''); + const response = await render(request); return { - user: (await db.get_user(cookies.session_id)) || { guest: true } + ...response, + headers: { + ...response.headers, + 'x-custom-header': 'potato' + } }; } ``` ### getSession -This function takes the [`context`](#hooks-getcontext) object and returns a `session` object that is [accessible on the client](#modules-$app-stores) and therefore must be safe to expose to users. It runs whenever SvelteKit server-renders a page. +This function takes the `request` object and returns a `session` object that is [accessible on the client](#modules-$app-stores) and therefore must be safe to expose to users. It runs whenever SvelteKit server-renders a page. If unimplemented, session is `{}`. ```ts -type GetSession = { - ({ context }: { context: Context }): Session | Promise; +type GetSession, Session = any> = { + (request: Request): Session | Promise; }; ``` ```js /** @type {import('@sveltejs/kit').GetSession} */ -export function getSession({ context }) { +export function getSession(request) { 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 + name: request.locals.user?.name, + email: request.locals.user?.email, + avatar: request.locals.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. It receives the `request` object and `render` method, which 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 = { - method: string; - host: string; - headers: Headers; - path: string; - params: Record; - query: URLSearchParams; - rawBody: string | ArrayBuffer; - body: string | ArrayBuffer | ReadOnlyFormData | any; - context: Context; -}; - -type Response = { - status?: number; - headers?: Headers; - body?: any; -}; - -type Handle = ({ - request: Request, - render: (request: Request) => Promise -}) => Response | Promise; -``` - -```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' - } - }; -} -``` diff --git a/packages/create-svelte/templates/default/netlify.toml b/packages/create-svelte/templates/default/netlify.toml index cf3422d8da01..de95fe2fb619 100644 --- a/packages/create-svelte/templates/default/netlify.toml +++ b/packages/create-svelte/templates/default/netlify.toml @@ -1,4 +1,4 @@ [build] command = "npm run build" publish = ".netlify/build/" - functions = ".netlify/functions/" \ No newline at end of file + functions = ".netlify/functions/" diff --git a/packages/create-svelte/templates/default/src/hooks.ts b/packages/create-svelte/templates/default/src/hooks.ts index c0d316d87cdd..6fc783eb225d 100644 --- a/packages/create-svelte/templates/default/src/hooks.ts +++ b/packages/create-svelte/templates/default/src/hooks.ts @@ -1,35 +1,22 @@ import cookie from 'cookie'; import { v4 as uuid } from '@lukeed/uuid'; -import type { GetContext, Handle } from '@sveltejs/kit'; +import type { Handle } from '@sveltejs/kit'; -export const getContext: GetContext = (request) => { +export const handle: Handle = async ({ request, render }) => { const cookies = cookie.parse(request.headers.cookie || ''); + request.locals.userid = cookies.userid || uuid(); - return { - is_new: !cookies.userid, - userid: cookies.userid || uuid() - }; -}; - -export const handle: Handle = async ({ request, render }) => { // TODO https://github.com/sveltejs/kit/issues/1046 - const response = await render({ - ...request, - method: (request.query.get('_method') || request.method).toUpperCase() - }); + if (request.query.has('_method')) { + request.method = request.query.get('_method').toUpperCase(); + } - const { is_new, userid } = request.context; + const response = await render(request); - if (is_new) { + if (!cookies.userid) { // if this is the first time the user has visited this app, // set a cookie so that we recognise them when they return - return { - ...response, - headers: { - ...response.headers, - 'set-cookie': `userid=${userid}; Path=/; HttpOnly` - } - }; + response.headers['set-cookie'] = `userid=${request.locals.userid}; Path=/; HttpOnly`; } return response; diff --git a/packages/create-svelte/templates/default/svelte.config.js b/packages/create-svelte/templates/default/svelte.config.js index 6cb2528d152c..3315cb538d64 100644 --- a/packages/create-svelte/templates/default/svelte.config.js +++ b/packages/create-svelte/templates/default/svelte.config.js @@ -16,7 +16,7 @@ const config = { }; if (adapter) { - config.kit.adapter = require(adapter)(options); + config.kit.adapter = (await import(adapter)).default(options); } export default config; diff --git a/packages/kit/src/core/build/index.js b/packages/kit/src/core/build/index.js index 8ffe3e740b84..0bfb2fb8e605 100644 --- a/packages/kit/src/core/build/index.js +++ b/packages/kit/src/core/build/index.js @@ -360,7 +360,6 @@ async function build_server( // this looks redundant, but the indirection allows us to access // named imports without triggering Rollup's missing import detection const get_hooks = hooks => ({ - getContext: hooks.getContext || (() => ({})), getSession: hooks.getSession || (() => ({})), handle: hooks.handle || (({ request, render }) => render(request)) }); diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index 5223cfa654dc..64112bd659a5 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -142,6 +142,13 @@ class Watcher extends EventEmitter { ? await this.vite.ssrLoadModule(`/${this.config.kit.files.hooks}`) : {}; + if (/** @type {any} */ (hooks).getContext) { + // TODO remove this for 1.0 + throw new Error( + 'The getContext hook has been removed. See https://kit.svelte.dev/docs#hooks' + ); + } + const root = (await this.vite.ssrLoadModule(`/${this.dir}/generated/root.svelte`)) .default; @@ -178,7 +185,6 @@ class Watcher extends EventEmitter { console.error(colors.gray(error.stack)); }, hooks: { - getContext: hooks.getContext || (() => ({})), getSession: hooks.getSession || (() => ({})), handle: hooks.handle || (({ request, render }) => render(request)) }, diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 2fac5d9a446e..2f887e72b0b4 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -21,26 +21,20 @@ export async function respond(incoming, options, state = {}) { }; } - const incoming_with_body = { - ...incoming, - headers: lowercase_keys(incoming.headers), - body: parse_body(incoming) - }; - - const context = (await options.hooks.getContext(incoming_with_body)) || {}; - try { return await options.hooks.handle({ request: { - ...incoming_with_body, + ...incoming, + headers: lowercase_keys(incoming.headers), + body: parse_body(incoming), params: null, - context + locals: {} }, render: async (request) => { if (state.prerender && state.prerender.fallback) { return await render_response({ options, - $session: await options.hooks.getSession({ context }), + $session: await options.hooks.getSession(request), page_config: { ssr: false, router: true, hydrate: true }, status: 200, error: null, diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index 850e5dc17cff..8356f8b96c1d 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -18,7 +18,7 @@ export default async function render_page(request, route, options, state) { }; } - const $session = await options.hooks.getSession({ context: request.context }); + const $session = await options.hooks.getSession(request); if (route) { const response = await respond({ diff --git a/packages/kit/test/apps/basics/src/hooks.js b/packages/kit/test/apps/basics/src/hooks.js index 03a3189dc2b6..ca5af57cf86f 100644 --- a/packages/kit/test/apps/basics/src/hooks.js +++ b/packages/kit/test/apps/basics/src/hooks.js @@ -1,22 +1,17 @@ import cookie from 'cookie'; -/** @type {import('@sveltejs/kit').GetContext} */ -export function getContext(request) { - const cookies = cookie.parse(request.headers.cookie || ''); - - return { - answer: 42, - name: cookies.name - }; -} - /** @type {import('@sveltejs/kit').GetSession} */ -export function getSession({ context }) { - return context; +export function getSession(request) { + return request.locals; } /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ request, render }) { + const cookies = cookie.parse(request.headers.cookie || ''); + + request.locals.answer = 42; + request.locals.name = cookies.name; + const response = await render(request); if (response) { diff --git a/packages/kit/test/apps/basics/src/routes/load/fetch-credentialed.json.js b/packages/kit/test/apps/basics/src/routes/load/fetch-credentialed.json.js index f818833852d2..6f763610eb52 100644 --- a/packages/kit/test/apps/basics/src/routes/load/fetch-credentialed.json.js +++ b/packages/kit/test/apps/basics/src/routes/load/fetch-credentialed.json.js @@ -2,7 +2,7 @@ export function get(request) { return { body: { - name: request.context.name + name: request.locals.name } }; } diff --git a/packages/kit/types/endpoint.d.ts b/packages/kit/types/endpoint.d.ts index 6b80bedd0fa8..68260e08ed90 100644 --- a/packages/kit/types/endpoint.d.ts +++ b/packages/kit/types/endpoint.d.ts @@ -1,6 +1,6 @@ import { Headers, ParameterizedBody } from './helper'; -export type ServerRequest = { +export type ServerRequest, Body = unknown> = { method: string; host: string; headers: Headers; @@ -9,7 +9,7 @@ export type ServerRequest = { query: URLSearchParams; rawBody: string | ArrayBuffer; body: ParameterizedBody; - context: Context; + locals: Locals; }; export type ServerResponse = { @@ -18,6 +18,6 @@ export type ServerResponse = { body?: any; }; -export type RequestHandler = ( - request: ServerRequest +export type RequestHandler, Body = unknown> = ( + request: ServerRequest ) => void | ServerResponse | Promise; diff --git a/packages/kit/types/hooks.d.ts b/packages/kit/types/hooks.d.ts index a6ceb1d425db..23f776c81904 100644 --- a/packages/kit/types/hooks.d.ts +++ b/packages/kit/types/hooks.d.ts @@ -11,13 +11,11 @@ export type Incoming = { body?: BaseBody; }; -export type GetContext = (incoming: Incoming) => Context; - -export type GetSession = { - ({ context }: { context: Context }): Session | Promise; +export type GetSession, Session = any> = { + (request: ServerRequest): Session | Promise; }; -export type Handle = (input: { - request: ServerRequest; - render: (request: ServerRequest) => ServerResponse | Promise; +export type Handle> = (input: { + request: ServerRequest; + render: (request: ServerRequest) => ServerResponse | Promise; }) => ServerResponse | Promise; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index bfda352efdf2..64a276229c7c 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2,5 +2,5 @@ import './ambient-modules'; export { Adapter, AdapterUtils, Config } from './config'; export { ErrorLoad, Load, Page } from './page'; -export { Incoming, GetContext, GetSession, Handle } from './hooks'; +export { Incoming, GetSession, Handle } from './hooks'; export { ServerRequest as Request, ServerResponse as Response, RequestHandler } from './endpoint'; diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index a8daba8699c3..e20616ef9463 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -1,5 +1,5 @@ import { Load } from './page'; -import { Incoming, GetContext, GetSession, Handle } from './hooks'; +import { Incoming, GetSession, Handle } from './hooks'; import { RequestHandler, ServerResponse } from './endpoint'; declare global { @@ -112,7 +112,6 @@ export type SSRManifest = { }; export type Hooks = { - getContext?: GetContext; getSession?: GetSession; handle?: Handle; };