diff --git a/packages/svelte/src/internal/server/hydratable.js b/packages/svelte/src/internal/server/hydratable.js index d8979ad7f5..3beb5d50c2 100644 --- a/packages/svelte/src/internal/server/hydratable.js +++ b/packages/svelte/src/internal/server/hydratable.js @@ -24,7 +24,7 @@ export function hydratable(key, fn) { let entry = hydratable.lookup.get(key); if (entry !== undefined) { - if (DEV && entry.dev) { + if (DEV) { const comparison = compare(key, entry, encode(key, fn(), [])); comparison.catch(() => {}); hydratable.comparisons.push(comparison); @@ -52,11 +52,7 @@ function encode(key, value, values, unresolved) { const entry = { value, index: -1 }; if (DEV) { - entry.dev = { - serialized: undefined, - serialize_work: [], - stack: get_stack('hydratable')?.stack - }; + entry.stack = get_stack('hydratable')?.stack; } let needs_thunk = false; @@ -82,12 +78,11 @@ function encode(key, value, values, unresolved) { // of a given hydratable are identical with a simple string comparison const result = DEV ? `d("${index}")` : `d(${index})`; - if (DEV && entry.dev) { - const { dev } = entry; - dev.serialize_work.push( + if (DEV) { + (entry.serialize_work ??= []).push( serialize_promise.then((s) => { serialized = serialized.replace(result, s); - dev.serialized = serialized; + entry.serialized = serialized; }) ); } @@ -96,6 +91,7 @@ function encode(key, value, values, unresolved) { } }); + entry.serialized = serialized; entry.index = values.push(needs_thunk ? `()=>(${serialized})` : serialized) - 1; needs_thunk = false; @@ -111,19 +107,19 @@ async function compare(key, a, b) { // note: these need to be loops (as opposed to Promise.all) because // additional promises can get pushed to them while we're awaiting // an earlier one - for (const p of a.dev?.serialize_work ?? []) { + for (const p of a?.serialize_work ?? []) { await p; } - for (const p of b.dev?.serialize_work ?? []) { + for (const p of b?.serialize_work ?? []) { await p; } - if (a.dev?.serialized !== b.dev?.serialized) { + if (a?.serialized !== b?.serialized) { e.hydratable_clobbering( key, - a.dev?.stack ?? '', - b.dev?.stack ?? '' + a?.stack ?? '', + b?.stack ?? '' ); } } diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js index 53be330aa1..75a39b436f 100644 --- a/packages/svelte/src/internal/server/renderer.js +++ b/packages/svelte/src/internal/server/renderer.js @@ -580,7 +580,7 @@ export class Renderer { for (const [_, key] of ctx.unresolved_promises) { // this is a problem -- it means we've finished the render but we're still waiting on a promise to resolve so we can // serialize it, so we're blocking the response on useless content. - w.unresolved_hydratable(key, ctx.lookup.get(key)?.dev?.stack ?? ''); + w.unresolved_hydratable(key, ctx.lookup.get(key)?.stack ?? ''); } for (const comparison of ctx.comparisons) { diff --git a/packages/svelte/src/internal/server/types.d.ts b/packages/svelte/src/internal/server/types.d.ts index 6728d4752e..26bc09ea95 100644 --- a/packages/svelte/src/internal/server/types.d.ts +++ b/packages/svelte/src/internal/server/types.d.ts @@ -18,11 +18,12 @@ export interface SSRContext { export interface HydratableLookupEntry { value: unknown; index: number; - dev?: { - serialize_work: Array>; - serialized: string | undefined; - stack: string | undefined; - }; + /** dev-only */ + serialize_work?: Array>; + /** dev-only */ + serialized?: string; + /** dev-only */ + stack?: string; } export interface HydratableContext { diff --git a/packages/svelte/tests/runtime-runes/samples/hydratable-complex-nesting/_config.js b/packages/svelte/tests/runtime-runes/samples/hydratable-complex-nesting/_config.js new file mode 100644 index 0000000000..0ac5333c4a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hydratable-complex-nesting/_config.js @@ -0,0 +1,24 @@ +import { tick } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + skip_no_async: true, + skip_mode: ['server'], + + server_props: { environment: 'server' }, + ssrHtml: '

The current environment is: server

', + + props: { environment: 'browser' }, + + async test({ assert, target, variant }) { + // make sure hydration has a chance to finish + await tick(); + const p = target.querySelector('p'); + ok(p); + if (variant === 'hydrate') { + assert.htmlEqual(p.outerHTML, '

The current environment is: server

'); + } else { + assert.htmlEqual(p.outerHTML, '

The current environment is: browser

'); + } + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/hydratable-complex-nesting/main.svelte b/packages/svelte/tests/runtime-runes/samples/hydratable-complex-nesting/main.svelte new file mode 100644 index 0000000000..cd603a6e6b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hydratable-complex-nesting/main.svelte @@ -0,0 +1,13 @@ + + +

The current environment is: {await value.then(res => res.nested).then(res => res.environment)}

diff --git a/packages/svelte/tests/server-side-rendering/samples/hydratable-clobbering-complicated/_config.js b/packages/svelte/tests/server-side-rendering/samples/hydratable-clobbering-complicated/_config.js new file mode 100644 index 0000000000..404260cc66 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/hydratable-clobbering-complicated/_config.js @@ -0,0 +1,6 @@ +import { test } from '../../test'; + +export default test({ + mode: ['async'], + error: 'hydratable_clobbering' +}); diff --git a/packages/svelte/tests/server-side-rendering/samples/hydratable-clobbering-complicated/main.svelte b/packages/svelte/tests/server-side-rendering/samples/hydratable-clobbering-complicated/main.svelte new file mode 100644 index 0000000000..358488c3ac --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/hydratable-clobbering-complicated/main.svelte @@ -0,0 +1,16 @@ + \ No newline at end of file