Skip to content

Commit 69b6384

Browse files
fix: deserialise streamed custom types (#14261)
* add test * add test for single apps * wrap statement in app import * add test for inlined apps * format * changeset * fix lint errors * fix * leaner payloads * fix * tweak --------- Co-authored-by: Rich Harris <[email protected]>
1 parent f984396 commit 69b6384

File tree

25 files changed

+253
-5
lines changed

25 files changed

+253
-5
lines changed

.changeset/brown-seals-rhyme.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: correctly decode custom types streamed from a server load function

packages/kit/src/runtime/app/forms.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function deserialize(result) {
3333

3434
if (parsed.data) {
3535
// the decoders should never be initialised at the top-level because `app`
36-
// will not initialised yet if `kit.output.bundleStrategy` is 'single' or 'inline'
36+
// will not be initialised yet if `kit.output.bundleStrategy` is 'single' or 'inline'
3737
parsed.data = devalue.parse(parsed.data, BROWSER ? client_app.decoders : server_app.decoders);
3838
}
3939

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ import * as app from '__sveltekit/manifest';
1313
export function start(element, options) {
1414
void kit.start(app, element, options);
1515
}
16+
17+
export { app };

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,28 @@ export async function render_response({
379379
deferred.set(id, { fulfil, reject });
380380
})`);
381381

382+
let app_declaration = '';
383+
384+
if (Object.keys(options.hooks.transport).length > 0) {
385+
if (client.inline) {
386+
app_declaration = `const app = __sveltekit_${options.version_hash}.app.app;`;
387+
} else if (client.app) {
388+
app_declaration = `const app = await import(${s(prefixed(client.app))});`;
389+
} else {
390+
app_declaration = `const { app } = await import(${s(prefixed(client.start))});`;
391+
}
392+
}
393+
394+
const prelude = app_declaration
395+
? `${app_declaration}
396+
const [data, error] = fn(app);`
397+
: `const [data, error] = fn();`;
398+
382399
// When resolving, the id might not yet be available due to the data
383400
// be evaluated upon init of kit, so we use a timeout to retry
384-
properties.push(`resolve: ({ id, data, error }) => {
401+
properties.push(`resolve: async (id, fn) => {
402+
${prelude}
403+
385404
const try_to_resolve = () => {
386405
if (!deferred.has(id)) {
387406
setTimeout(try_to_resolve, 0);
@@ -665,7 +684,7 @@ function get_data(event, event_state, options, nodes, csp, global) {
665684

666685
let str;
667686
try {
668-
str = devalue.uneval({ id, data, error }, replacer);
687+
str = devalue.uneval(error ? [, error] : [data], replacer);
669688
} catch {
670689
error = await handle_error_and_jsonify(
671690
event,
@@ -674,11 +693,13 @@ function get_data(event, event_state, options, nodes, csp, global) {
674693
new Error(`Failed to serialize promise while rendering ${event.route.id}`)
675694
);
676695
data = undefined;
677-
str = devalue.uneval({ id, data, error }, replacer);
696+
str = devalue.uneval([, error], replacer);
678697
}
679698

680699
const nonce = csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : '';
681-
push(`<script${nonce}>${global}.resolve(${str})</script>\n`);
700+
push(
701+
`<script${nonce}>${global}.resolve(${id}, ${str.includes('app.decode') ? `(app) => ${str}` : `() => ${str}`})</script>\n`
702+
);
682703
if (count === 0) done();
683704
}
684705
);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Foo } from '$lib';
2+
3+
export const load = () => {
4+
return {
5+
foo: Promise.resolve(new Foo('It works'))
6+
};
7+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
export let data;
3+
</script>
4+
5+
<h1>
6+
{#await data.foo}
7+
Loading...
8+
{:then result}
9+
{result.bar()}
10+
{/await}
11+
</h1>

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,13 @@ test.describe('Serialization', () => {
15651565
await page.click('button');
15661566
await expect(page.locator('h1')).toHaveText('It works!');
15671567
});
1568+
1569+
test('works with streaming', async ({ page, javaScriptEnabled }) => {
1570+
test.skip(!javaScriptEnabled, 'skip when JavaScript is disabled');
1571+
1572+
await page.goto('/serialization-stream');
1573+
await expect(page.locator('h1', { hasText: 'It works!' })).toBeVisible();
1574+
});
15681575
});
15691576

15701577
test.describe('getRequestEvent', () => {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Foo } from '$lib';
2+
3+
/** @type {import("@sveltejs/kit").Transport} */
4+
export const transport = {
5+
Foo: {
6+
encode: (value) => value instanceof Foo && [value.message],
7+
decode: ([message]) => new Foo(message)
8+
}
9+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export class Foo {
2+
constructor(message) {
3+
this.message = message;
4+
}
5+
6+
bar() {
7+
return this.message + '!';
8+
}
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Foo } from '$lib';
2+
3+
export const load = () => {
4+
return {
5+
foo: Promise.resolve(new Foo('It works'))
6+
};
7+
};

0 commit comments

Comments
 (0)