Skip to content

Commit 181f8f7

Browse files
fix: allow remote functions to return custom types serialized with transport hooks (#14435)
closes #14423 --------- Co-authored-by: Simon H <[email protected]>
1 parent 8b9a19f commit 181f8f7

File tree

6 files changed

+57
-30
lines changed

6 files changed

+57
-30
lines changed

.changeset/deep-points-peel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: allow remote functions to return custom types serialized with `transport` hooks

packages/kit/src/runtime/client/client.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,7 @@ let target;
190190
export let app;
191191

192192
/** @type {Record<string, any>} */
193-
// we have to conditionally access the properties of `__SVELTEKIT_PAYLOAD__`
194-
// because it will be `undefined` when users import the exports from this module.
195-
// It's only defined when the server renders a page.
196-
export const remote_responses = __SVELTEKIT_PAYLOAD__?.data ?? {};
193+
export let remote_responses = {};
197194

198195
/** @type {Array<((url: URL) => boolean)>} */
199196
const invalidated = [];
@@ -294,6 +291,10 @@ export async function start(_app, _target, hydrate) {
294291
);
295292
}
296293

294+
if (__SVELTEKIT_PAYLOAD__.data) {
295+
remote_responses = __SVELTEKIT_PAYLOAD__?.data;
296+
}
297+
297298
// detect basic auth credentials in the current URL
298299
// https://github.com/sveltejs/kit/pull/11179
299300
// if so, refresh the page without credentials

packages/kit/src/runtime/server/page/render.js

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -408,29 +408,6 @@ export async function render_response({
408408
}`);
409409
}
410410

411-
const { remote_data } = event_state;
412-
413-
if (remote_data) {
414-
/** @type {Record<string, any>} */
415-
const remote = {};
416-
417-
for (const key in remote_data) {
418-
remote[key] = await remote_data[key];
419-
}
420-
421-
// TODO this is repeated in a few places — dedupe it
422-
const replacer = (/** @type {any} */ thing) => {
423-
for (const key in options.hooks.transport) {
424-
const encoded = options.hooks.transport[key].encode(thing);
425-
if (encoded) {
426-
return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`;
427-
}
428-
}
429-
};
430-
431-
properties.push(`data: ${devalue.uneval(remote, replacer)}`);
432-
}
433-
434411
// create this before declaring `data`, which may contain references to `${global}`
435412
blocks.push(`${global} = {
436413
${properties.join(',\n\t\t\t\t\t\t')}
@@ -482,20 +459,45 @@ export async function render_response({
482459
args.push(`{\n${indent}\t${hydrate.join(`,\n${indent}\t`)}\n${indent}}`);
483460
}
484461

462+
const { remote_data } = event_state;
463+
464+
let serialized_remote_data = '';
465+
466+
if (remote_data) {
467+
/** @type {Record<string, any>} */
468+
const remote = {};
469+
470+
for (const key in remote_data) {
471+
remote[key] = await remote_data[key];
472+
}
473+
474+
// TODO this is repeated in a few places — dedupe it
475+
const replacer = (/** @type {any} */ thing) => {
476+
for (const key in options.hooks.transport) {
477+
const encoded = options.hooks.transport[key].encode(thing);
478+
if (encoded) {
479+
return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`;
480+
}
481+
}
482+
};
483+
484+
serialized_remote_data = `${global}.data = ${devalue.uneval(remote, replacer)};\n\n\t\t\t\t\t\t`;
485+
}
486+
485487
// `client.app` is a proxy for `bundleStrategy === 'split'`
486488
const boot = client.inline
487489
? `${client.inline.script}
488490
489-
__sveltekit_${options.version_hash}.app.start(${args.join(', ')});`
491+
${serialized_remote_data}${global}.app.start(${args.join(', ')});`
490492
: client.app
491493
? `Promise.all([
492494
import(${s(prefixed(client.start))}),
493495
import(${s(prefixed(client.app))})
494496
]).then(([kit, app]) => {
495-
kit.start(app, ${args.join(', ')});
497+
${serialized_remote_data}kit.start(app, ${args.join(', ')});
496498
});`
497499
: `import(${s(prefixed(client.start))}).then((app) => {
498-
app.start(${args.join(', ')})
500+
${serialized_remote_data}app.start(${args.join(', ')})
499501
});`;
500502

501503
if (load_env_eagerly) {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import { greeting } from './data.remote.js';
3+
</script>
4+
5+
<!-- TODO use `await` expression once async SSR lands -->
6+
{#await greeting() then x}
7+
<h1>{x.bar()}</h1>
8+
{/await}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { query } from '$app/server';
2+
import { Foo } from '$lib';
3+
4+
export const greeting = query(() => new Foo('hello from remote function'));

packages/kit/test/apps/basics/test/client.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,4 +2093,11 @@ test.describe('remote functions', () => {
20932093
await page.waitForTimeout(100); // allow all requests to finish
20942094
expect(request_count).toBe(1);
20952095
});
2096+
2097+
// TODO ditto
2098+
test('query works with transport', async ({ page }) => {
2099+
await page.goto('/remote/transport');
2100+
2101+
await expect(page.locator('h1')).toHaveText('hello from remote function!');
2102+
});
20962103
});

0 commit comments

Comments
 (0)