elliott/hydratable
Elliott Johnson 19 hours ago
parent 90dd32b6e2
commit 3f3ad1a2e6

@ -7,9 +7,6 @@ A `hydratable` value with key `%key%` was created, but at least part of it was n
The `hydratable` was initialized in: The `hydratable` was initialized in:
%stack% %stack%
The unresolved data is:
%unresolved_data%
``` ```
The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing

@ -4,9 +4,6 @@
> >
> The `hydratable` was initialized in: > The `hydratable` was initialized in:
> %stack% > %stack%
>
> The unresolved data is:
> %unresolved_data%
The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing
the result inside a `svelte:boundary` with a `pending` snippet: the result inside a `svelte:boundary` with a `pending` snippet:

@ -21,11 +21,8 @@ export function hydratable(key, fn) {
} }
const store = window.__svelte?.h; const store = window.__svelte?.h;
const unused_keys = window.__svelte?.uh;
if (!store?.has(key)) { if (!store?.has(key)) {
if (!unused_keys?.has(key)) { hydratable_missing_but_expected(key);
hydratable_missing_but_expected(key);
}
return fn(); return fn();
} }

@ -7,8 +7,6 @@ declare global {
__svelte?: { __svelte?: {
/** hydratables */ /** hydratables */
h?: Map<string, unknown>; h?: Map<string, unknown>;
/** unused hydratable keys */
uh?: Set<string>;
}; };
} }
} }

@ -10,7 +10,6 @@ import { BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
import { attributes } from './index.js'; import { attributes } from './index.js';
import { get_render_context, with_render_context, init_render_context } from './render-context.js'; import { get_render_context, with_render_context, init_render_context } from './render-context.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { get_stack } from './dev.js';
/** @typedef {'head' | 'body'} RendererType */ /** @typedef {'head' | 'body'} RendererType */
/** @typedef {{ [key in RendererType]: string }} AccumulatedContent */ /** @typedef {{ [key in RendererType]: string }} AccumulatedContent */
@ -578,14 +577,10 @@ export class Renderer {
async #collect_hydratables() { async #collect_hydratables() {
const ctx = get_render_context().hydratable; const ctx = get_render_context().hydratable;
for (const [promise, key] of ctx.unresolved_promises) { 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 // 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. // serialize it, so we're blocking the response on useless content.
w.unresolved_hydratable( w.unresolved_hydratable(key, ctx.lookup.get(key)?.dev?.stack ?? '<missing stack trace>');
key,
ctx.lookup.get(key)?.dev?.stack ?? '<missing stack trace>',
await promise
);
} }
for (const comparison of ctx.comparisons) { for (const comparison of ctx.comparisons) {
@ -593,7 +588,7 @@ export class Renderer {
await comparison; await comparison;
} }
return await Renderer.#hydratable_block(ctx, []); return await Renderer.#hydratable_block(ctx);
} }
/** /**
@ -652,10 +647,9 @@ export class Renderer {
/** /**
* @param {HydratableContext} ctx * @param {HydratableContext} ctx
* @param {string[]} unused_keys
*/ */
static async #hydratable_block(ctx, unused_keys) { static async #hydratable_block(ctx) {
if (ctx.lookup.size === 0 && unused_keys.length === 0) { if (ctx.lookup.size === 0) {
return null; return null;
} }
@ -672,11 +666,11 @@ export class Renderer {
{ {
const r = (v) => Promise.resolve(v); const r = (v) => Promise.resolve(v);
const v = [${values.join(',')}]; const v = [${values.join(',')}];
function d(i) { function d(i) {
const value = v[i]; const value = v[i];
return typeof value === 'function' ? value() : value; return typeof value === 'function' ? value() : value;
}; };
const sv = window.__svelte ??= {};${Renderer.#used_hydratables(ctx.lookup)}${Renderer.#unused_hydratables(unused_keys)} const sv = window.__svelte ??= {};${Renderer.#used_hydratables(ctx.lookup)}
} }
</script>`; </script>`;
} }
@ -693,16 +687,6 @@ export class Renderer {
store.set(k, d(i)); store.set(k, d(i));
}`; }`;
} }
/** @param {string[]} unused_keys */
static #unused_hydratables(unused_keys) {
if (unused_keys.length === 0) return '';
return `
const unused = sv.uh ??= new Set();
for (const k of ${JSON.stringify(unused_keys)}) {
unused.add(k);
}`;
}
} }
export class SSRState { export class SSRState {

@ -2,7 +2,6 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest';
import { Renderer, SSRState } from './renderer.js'; import { Renderer, SSRState } from './renderer.js';
import type { Component } from 'svelte'; import type { Component } from 'svelte';
import { disable_async_mode_flag, enable_async_mode_flag } from '../flags/index.js'; import { disable_async_mode_flag, enable_async_mode_flag } from '../flags/index.js';
import { uneval } from 'devalue';
test('collects synchronous body content by default', () => { test('collects synchronous body content by default', () => {
const component = (renderer: Renderer) => { const component = (renderer: Renderer) => {

@ -10,23 +10,16 @@ var normal = 'font-weight: normal';
* *
* The `hydratable` was initialized in: * The `hydratable` was initialized in:
* %stack% * %stack%
*
* The unresolved data is:
* %unresolved_data%
* @param {string} key * @param {string} key
* @param {string} stack * @param {string} stack
* @param {string} unresolved_data
*/ */
export function unresolved_hydratable(key, stack, unresolved_data) { export function unresolved_hydratable(key, stack) {
if (DEV) { if (DEV) {
console.warn( console.warn(
`%c[svelte] unresolved_hydratable\n%cA \`hydratable\` value with key \`${key}\` was created, but at least part of it was not used during the render. `%c[svelte] unresolved_hydratable\n%cA \`hydratable\` value with key \`${key}\` was created, but at least part of it was not used during the render.
The \`hydratable\` was initialized in: The \`hydratable\` was initialized in:
${stack} ${stack}\nhttps://svelte.dev/e/unresolved_hydratable`,
The unresolved data is:
${unresolved_data}\nhttps://svelte.dev/e/unresolved_hydratable`,
bold, bold,
normal normal
); );

@ -107,7 +107,6 @@ declare global {
var __svelte: var __svelte:
| { | {
h?: Map<string, unknown>; h?: Map<string, unknown>;
uh?: Set<string>;
} }
| undefined; | undefined;
} }
@ -127,7 +126,6 @@ beforeAll(() => {
beforeEach(() => { beforeEach(() => {
delete globalThis?.__svelte?.h; delete globalThis?.__svelte?.h;
delete globalThis?.__svelte?.uh;
}); });
afterAll(() => { afterAll(() => {

@ -15,7 +15,7 @@ export default test({
}, },
async test({ assert, target }) { async test({ assert, target }) {
// let it hydrate and resolve the promise on the client // make sure the hydratable promise on the client has a chance to run and reject (it shouldn't, because the server data should be used)
await tick(); await tick();
assert.htmlEqual( assert.htmlEqual(

Loading…
Cancel
Save