diff --git a/.changeset/deep-points-peel.md b/.changeset/deep-points-peel.md new file mode 100644 index 000000000000..b93f6d332117 --- /dev/null +++ b/.changeset/deep-points-peel.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: allow remote functions to return custom types serialized with `transport` hooks diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 88424188f2dd..f655bef49ecd 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -190,10 +190,7 @@ let target; export let app; /** @type {Record} */ -// we have to conditionally access the properties of `__SVELTEKIT_PAYLOAD__` -// because it will be `undefined` when users import the exports from this module. -// It's only defined when the server renders a page. -export const remote_responses = __SVELTEKIT_PAYLOAD__?.data ?? {}; +export let remote_responses = {}; /** @type {Array<((url: URL) => boolean)>} */ const invalidated = []; @@ -294,6 +291,10 @@ export async function start(_app, _target, hydrate) { ); } + if (__SVELTEKIT_PAYLOAD__.data) { + remote_responses = __SVELTEKIT_PAYLOAD__?.data; + } + // detect basic auth credentials in the current URL // https://github.com/sveltejs/kit/pull/11179 // if so, refresh the page without credentials diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 2ad4908508d0..b9a9ffc4669c 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -408,29 +408,6 @@ export async function render_response({ }`); } - const { remote_data } = event_state; - - if (remote_data) { - /** @type {Record} */ - const remote = {}; - - for (const key in remote_data) { - remote[key] = await remote_data[key]; - } - - // TODO this is repeated in a few places — dedupe it - const replacer = (/** @type {any} */ thing) => { - for (const key in options.hooks.transport) { - const encoded = options.hooks.transport[key].encode(thing); - if (encoded) { - return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`; - } - } - }; - - properties.push(`data: ${devalue.uneval(remote, replacer)}`); - } - // create this before declaring `data`, which may contain references to `${global}` blocks.push(`${global} = { ${properties.join(',\n\t\t\t\t\t\t')} @@ -482,20 +459,45 @@ export async function render_response({ args.push(`{\n${indent}\t${hydrate.join(`,\n${indent}\t`)}\n${indent}}`); } + const { remote_data } = event_state; + + let serialized_remote_data = ''; + + if (remote_data) { + /** @type {Record} */ + const remote = {}; + + for (const key in remote_data) { + remote[key] = await remote_data[key]; + } + + // TODO this is repeated in a few places — dedupe it + const replacer = (/** @type {any} */ thing) => { + for (const key in options.hooks.transport) { + const encoded = options.hooks.transport[key].encode(thing); + if (encoded) { + return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`; + } + } + }; + + serialized_remote_data = `${global}.data = ${devalue.uneval(remote, replacer)};\n\n\t\t\t\t\t\t`; + } + // `client.app` is a proxy for `bundleStrategy === 'split'` const boot = client.inline ? `${client.inline.script} - __sveltekit_${options.version_hash}.app.start(${args.join(', ')});` + ${serialized_remote_data}${global}.app.start(${args.join(', ')});` : client.app ? `Promise.all([ import(${s(prefixed(client.start))}), import(${s(prefixed(client.app))}) ]).then(([kit, app]) => { - kit.start(app, ${args.join(', ')}); + ${serialized_remote_data}kit.start(app, ${args.join(', ')}); });` : `import(${s(prefixed(client.start))}).then((app) => { - app.start(${args.join(', ')}) + ${serialized_remote_data}app.start(${args.join(', ')}) });`; if (load_env_eagerly) { diff --git a/packages/kit/test/apps/basics/src/routes/remote/transport/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/transport/+page.svelte new file mode 100644 index 000000000000..023c95be9909 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/transport/+page.svelte @@ -0,0 +1,8 @@ + + + +{#await greeting() then x} +

{x.bar()}

+{/await} diff --git a/packages/kit/test/apps/basics/src/routes/remote/transport/data.remote.ts b/packages/kit/test/apps/basics/src/routes/remote/transport/data.remote.ts new file mode 100644 index 000000000000..78261104279d --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/transport/data.remote.ts @@ -0,0 +1,4 @@ +import { query } from '$app/server'; +import { Foo } from '$lib'; + +export const greeting = query(() => new Foo('hello from remote function')); diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index 0d1c52235818..b5d9554e27ad 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -2093,4 +2093,11 @@ test.describe('remote functions', () => { await page.waitForTimeout(100); // allow all requests to finish expect(request_count).toBe(1); }); + + // TODO ditto + test('query works with transport', async ({ page }) => { + await page.goto('/remote/transport'); + + await expect(page.locator('h1')).toHaveText('hello from remote function!'); + }); });