sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.7k stars 1.94k forks source link

proposal: handlePageData hook #12940

Open konstantinov90 opened 2 days ago

konstantinov90 commented 2 days ago

Describe the problem

Sveltekit allows to implement multilayered data loading, which is great!

Now suppose you have some complex logic inside your load-functions, which you would like to test. While you might use unit tests, or you might use something like playwright, sometimes you just want to get that $page.data prop to test it

In my case we have a very complex inverted data loading scheme (in our organisation we have a convention to use NodeJs ONLY for rendering - so no backend calls from nodejs), where GO "frontend" calls svelte-kit service twice - first to get the "data-loading schema" (a bunch of graphql-queries), then loading this data, and second - POST request to svelte-kit again with that very data to render the page

Making svelte-kit to render page on POST-request and feeding it data on load-functions is not a problem, we use our adapter and async_hooks for it, but getting "data-loading schema" from load-functions turned out to be quite difficult. New hook solves this problem

Describe the proposed solution

I propose a new handlePageData, which should be called inside render_page function just before rendering

Patch might look just something like this

diff --git a/node_modules/@sveltejs/kit/src/exports/public.d.ts b/node_modules/@sveltejs/kit/src/exports/public.d.ts
index 861aff3..d7e7852 100644
--- a/node_modules/@sveltejs/kit/src/exports/public.d.ts
+++ b/node_modules/@sveltejs/kit/src/exports/public.d.ts
@@ -687,6 +687,13 @@ export type Handle = (input: {
    resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
 }) => MaybePromise<Response>;

+
+/** docs */
+export type HandlePageData = (input: {
+   event: RequestEvent;
+   pageData: unknown;
+}) => MaybePromise<void | Response>;
+
 /**
  * The server-side [`handleError`](https://kit.svelte.dev/docs/hooks#shared-hooks-handleerror) hook runs when an unexpected error is thrown while responding to a request.
  *
diff --git a/node_modules/@sveltejs/kit/src/runtime/server/index.js b/node_modules/@sveltejs/kit/src/runtime/server/index.js
index 36cbd04..0b6f00e 100644
--- a/node_modules/@sveltejs/kit/src/runtime/server/index.js
+++ b/node_modules/@sveltejs/kit/src/runtime/server/index.js
@@ -69,6 +69,7 @@ export class Server {

                this.#options.hooks = {
                    handle: module.handle || (({ event, resolve }) => resolve(event)),
+                   handlePageData: module.handlePageData || (({ event, resolve }) => resolve(event)),
                    handleError: module.handleError || (({ error }) => console.error(error)),
                    handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)),
                    reroute: module.reroute || (() => {})
diff --git a/node_modules/@sveltejs/kit/src/runtime/server/page/index.js b/node_modules/@sveltejs/kit/src/runtime/server/page/index.js
index 3424c5f..1633ae4 100644
--- a/node_modules/@sveltejs/kit/src/runtime/server/page/index.js
+++ b/node_modules/@sveltejs/kit/src/runtime/server/page/index.js
@@ -281,6 +281,10 @@ export async function render_page(event, page, options, manifest, state, resolve
            });
        }

+       const hookResponse = options.hooks.handlePageData({event, pageData: compact(branch.map(b => b.data))});
+
+       if (hookResponse instanceof Response) return hookResponse;
+       
        const ssr = get_option(nodes, 'ssr') ?? true;

        return await render_response({
diff --git a/node_modules/@sveltejs/kit/src/types/internal.d.ts b/node_modules/@sveltejs/kit/src/types/internal.d.ts
index c9dbb51..cd4dce3 100644
--- a/node_modules/@sveltejs/kit/src/types/internal.d.ts
+++ b/node_modules/@sveltejs/kit/src/types/internal.d.ts
@@ -107,6 +107,7 @@ export type GetParams = (match: RegExpExecArray) => Record<string, string>;
 export interface ServerHooks {
    handleFetch: HandleFetch;
    handle: Handle;
+   handlePageData: HandlePageData;
    handleError: HandleServerError;
    reroute: Reroute;
 }

Then hook might look like this

import { type HandlePageData } from '@sveltejs/kit'

export const handlePageData: HandlePageData = function({event, pageData}) {
    if (event.request.headers.get('x-e2e-testing')) {
        return new Response(JSON.stringify(pageData), {
            headers: {
                'x-e2e-testing': 'PAGE_DATA'
            }
        });
    }
}

Alternatives considered

I considered using /__data.json route to get page data, but it only calls +page.server.ts load functions, which does not fully represents page data on render

Regarding the hook signature - first I tried to implement something like the handle hook, ((pageData: unknown, resolve: (pageData: unknown) => Response) => Response), but isolating just the pageData for this resolve function turned out to be too big of a deal

Importance

would make my life easier

Additional Information

No response

Kapsonfire-DE commented 2 days ago

you can use the normal handle hook and inject your data in event.locals create a global +layout.server.ts and put it into the data

konstantinov90 commented 2 days ago

No, I cannot, the problem is how to extract the data, not to inject it