Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/deep-points-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: allow remote functions to return custom types serialized with `transport` hooks
9 changes: 5 additions & 4 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,7 @@ let target;
export let app;

/** @type {Record<string, any>} */
// 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 = [];
Expand Down Expand Up @@ -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
Expand Down
54 changes: 28 additions & 26 deletions packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,29 +408,6 @@ export async function render_response({
}`);
}

const { remote_data } = event_state;

if (remote_data) {
/** @type {Record<string, any>} */
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')}
Expand Down Expand Up @@ -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<string, any>} */
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
import { greeting } from './data.remote.js';
</script>

<!-- TODO use `await` expression once async SSR lands -->
{#await greeting() then x}
<h1>{x.bar()}</h1>
{/await}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { query } from '$app/server';
import { Foo } from '$lib';

export const greeting = query(() => new Foo('hello from remote function'));
7 changes: 7 additions & 0 deletions packages/kit/test/apps/basics/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1669,7 +1669,7 @@
expect(await page.textContent('pre')).toContain('"success": true');
});

test('reroute does not get applied to external URLs', async ({ page }) => {

Check warning on line 1672 in packages/kit/test/apps/basics/test/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit (18, ubuntu-latest, chromium)

flaky test: reroute does not get applied to external URLs

retries: 2
await page.goto('/reroute/external');
const current_url = new URL(page.url());

Expand Down Expand Up @@ -2093,4 +2093,11 @@
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!');
});
});
Loading