diff --git a/.changeset/fifty-snakes-listen.md b/.changeset/fifty-snakes-listen.md new file mode 100644 index 000000000000..fc782c80dc0d --- /dev/null +++ b/.changeset/fifty-snakes-listen.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +add `handleLoad` and `handleServerLoad` hook support diff --git a/packages/kit/src/core/sync/write_client_manifest.js b/packages/kit/src/core/sync/write_client_manifest.js index df98c8cd7fa2..388b284b4221 100644 --- a/packages/kit/src/core/sync/write_client_manifest.js +++ b/packages/kit/src/core/sync/write_client_manifest.js @@ -127,6 +127,10 @@ export function write_client_manifest(kit, manifest_data, output, metadata) { handleError: ${ hooks_file ? 'client_hooks.handleError || ' : '' }(({ error }) => { console.error(error) }), + + handleLoad: ${ + hooks_file ? 'client_hooks.handleLoad || ' : '' + }(({ event, resolve }) => { return resolve(event) }), }; export { default as root } from '../root.svelte'; diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index a049d2d3eeaf..ec637b5451f6 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -684,7 +684,9 @@ export function create_client(app, target) { if (DEV) { try { lock_fetch(); - data = (await node.universal.load.call(null, load_input)) ?? null; + data = + (await app.hooks.handleLoad({ event: load_input, resolve: node.universal.load })) ?? + null; if (data != null && Object.getPrototypeOf(data) !== Object.prototype) { throw new Error( `a load function related to route '${route.id}' returned ${ @@ -702,7 +704,8 @@ export function create_client(app, target) { unlock_fetch(); } } else { - data = (await node.universal.load.call(null, load_input)) ?? null; + data = + (await app.hooks.handleLoad({ event: load_input, resolve: node.universal.load })) ?? null; } data = data ? await unwrap_promises(data) : null; } diff --git a/packages/kit/src/runtime/server/ambient.d.ts b/packages/kit/src/runtime/server/ambient.d.ts index cc7b0f428d47..a57b96cdf259 100644 --- a/packages/kit/src/runtime/server/ambient.d.ts +++ b/packages/kit/src/runtime/server/ambient.d.ts @@ -4,5 +4,7 @@ declare module '__SERVER__/internal.js' { handle?: import('types').Handle; handleError?: import('types').HandleServerError; handleFetch?: import('types').HandleFetch; + handleLoad?: import('types').HandleLoad; + handleServerLoad?: import('types').HandleServerLoad; }>; } diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index 8d7b14851d83..25f47f0f8a68 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -78,7 +78,8 @@ export async function render_data( } } return data; - } + }, + handleServerLoad: options.hooks.handleServerLoad }); } catch (e) { aborted = true; diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index b9d218f9cbd8..5b89b057b24e 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -41,7 +41,9 @@ export class Server { handle: module.handle || (({ event, resolve }) => resolve(event)), // @ts-expect-error handleError: module.handleError || (({ error }) => console.error(error?.stack)), - handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)) + handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), + handleLoad: module.handleLoad || (({ event, resolve }) => resolve(event)), + handleServerLoad: module.handleServerLoad || (({ event, resolve }) => resolve(event)) }; } } diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index fdf7713be962..097064221bfb 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -150,7 +150,8 @@ export async function render_page(event, page, options, manifest, state, resolve if (parent) Object.assign(data, await parent.data); } return data; - } + }, + handleServerLoad: options.hooks.handleServerLoad }); } catch (e) { load_error = /** @type {Error} */ (e); @@ -180,7 +181,8 @@ export async function render_page(event, page, options, manifest, state, resolve resolve_opts, server_data_promise: server_promises[i], state, - csr + csr, + handleLoad: options.hooks.handleLoad }); } catch (e) { load_error = /** @type {Error} */ (e); diff --git a/packages/kit/src/runtime/server/page/load_data.js b/packages/kit/src/runtime/server/page/load_data.js index 5db45afdc673..c7f6d9a770c4 100644 --- a/packages/kit/src/runtime/server/page/load_data.js +++ b/packages/kit/src/runtime/server/page/load_data.js @@ -10,10 +10,11 @@ import { validate_depends } from '../../shared.js'; * state: import('types').SSRState; * node: import('types').SSRNode | undefined; * parent: () => Promise>; + * handleServerLoad: import('types').HandleServerLoad; * }} opts * @returns {Promise} */ -export async function load_server_data({ event, state, node, parent }) { +export async function load_server_data({ event, state, node, parent, handleServerLoad }) { if (!node?.server) return null; let done = false; @@ -40,9 +41,9 @@ export async function load_server_data({ event, state, node, parent }) { disable_search(url); } - const result = await node.server.load?.call(null, { + const load_event = { ...event, - fetch: (info, init) => { + fetch: (/** @type {URL | RequestInfo} */ info, /** @type {RequestInit | undefined} */ init) => { const url = new URL(info instanceof Request ? info.url : info, event.url); if (DEV && done && !uses.dependencies.has(url.href)) { @@ -112,7 +113,11 @@ export async function load_server_data({ event, state, node, parent }) { } }), url - }); + }; + + const result = node.server.load + ? await handleServerLoad({ event: load_event, resolve: node.server.load }) + : null; const data = result ? await unwrap_promises(result) : null; if (__SVELTEKIT_DEV__) { @@ -140,6 +145,7 @@ export async function load_server_data({ event, state, node, parent }) { * server_data_promise: Promise; * state: import('types').SSRState; * csr: boolean; + * handleLoad: import('types').HandleLoad; * }} opts * @returns {Promise> | null>} */ @@ -151,7 +157,8 @@ export async function load_data({ server_data_promise, state, resolve_opts, - csr + csr, + handleLoad }) { const server_data_node = await server_data_promise; @@ -159,7 +166,7 @@ export async function load_data({ return server_data_node?.data ?? null; } - const result = await node.universal.load.call(null, { + const load_event = { url: event.url, params: event.params, data: server_data_node?.data ?? null, @@ -168,7 +175,9 @@ export async function load_data({ setHeaders: event.setHeaders, depends: () => {}, parent - }); + }; + + const result = await handleLoad({ event: load_event, resolve: node.universal.load }); const data = result ? await unwrap_promises(result) : null; if (__SVELTEKIT_DEV__) { diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index 10607a09d8ad..82cf742d6c52 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -44,7 +44,8 @@ export async function respond_with_error({ event, state, node: default_layout, - parent: async () => ({}) + parent: async () => ({}), + handleServerLoad: options.hooks.handleServerLoad }); const server_data = await server_data_promise; @@ -57,7 +58,8 @@ export async function respond_with_error({ resolve_opts, server_data_promise, state, - csr + csr, + handleLoad: options.hooks.handleLoad }); branch.push( diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index c2019c4e27b0..690b2fc2d8b4 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -633,6 +633,23 @@ export interface HandleClientError { (input: { error: unknown; event: NavigationEvent }): MaybePromise; } +/** + * The `handleLoad` hook runs every time a universal `load` function (for example from +page.js) is called. + * This hook can be registered on the client as well as on the server side. + * This hook provides the load `event` and a `resolve` function to call the actual hook with the event. + */ +export interface HandleLoad { + (input: { event: LoadEvent; resolve: Load }): ReturnType; +} + +/** + * The `handleServerLoad` hook runs every time a server-only `load` function (for example from +page.server.js) is called on the server. + * This hook provides the server load `event` and a `resolve` function to call the actual hook with the event. + */ +export interface HandleServerLoad { + (input: { event: ServerLoadEvent; resolve: ServerLoad }): ReturnType; +} + /** * The [`handleFetch`](https://kit.svelte.dev/docs/hooks#server-hooks-handlefetch) hook allows you to modify (or replace) a `fetch` request that happens inside a `load` function that runs on the server (or during pre-rendering) */ diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 363be1f45397..c43bb317ecaa 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -13,7 +13,9 @@ import { SSRManifest, HandleFetch, Actions, - HandleClientError + HandleClientError, + HandleLoad, + HandleServerLoad } from './index.js'; import { HttpMethod, @@ -94,10 +96,13 @@ export interface ServerHooks { handleFetch: HandleFetch; handle: Handle; handleError: HandleServerError; + handleLoad: HandleLoad; + handleServerLoad: HandleServerLoad; } export interface ClientHooks { handleError: HandleClientError; + handleLoad: HandleLoad; } export interface Env {