WIP fix async hydration

pull/16797/head
Rich Harris 2 days ago
parent dc7da40369
commit ace342239b

@ -1,6 +1,14 @@
/** @import { TemplateNode, Value } from '#client' */ /** @import { TemplateNode, Value } from '#client' */
import { flatten } from '../../reactivity/async.js'; import { flatten } from '../../reactivity/async.js';
import { get } from '../../runtime.js'; import { get } from '../../runtime.js';
import {
hydrate_next,
hydrate_node,
hydrating,
set_hydrate_node,
set_hydrating,
remove_nodes
} from '../hydration.js';
import { get_boundary } from './boundary.js'; import { get_boundary } from './boundary.js';
/** /**
@ -13,7 +21,22 @@ export function async(node, expressions, fn) {
boundary.update_pending_count(1); boundary.update_pending_count(1);
var was_hydrating = hydrating;
if (was_hydrating) {
hydrate_next();
var previous_hydrate_node = hydrate_node;
var end = remove_nodes(false);
set_hydrate_node(end);
}
flatten([], expressions, (values) => { flatten([], expressions, (values) => {
if (was_hydrating) {
set_hydrating(true);
set_hydrate_node(previous_hydrate_node);
}
try { try {
// get values eagerly to avoid creating blocks if they reject // get values eagerly to avoid creating blocks if they reject
for (const d of values) get(d); for (const d of values) get(d);
@ -22,5 +45,9 @@ export function async(node, expressions, fn) {
} finally { } finally {
boundary.update_pending_count(-1); boundary.update_pending_count(-1);
} }
if (was_hydrating) {
set_hydrating(false);
}
}); });
} }

@ -81,9 +81,10 @@ export function next(count = 1) {
} }
/** /**
* Removes all nodes starting at `hydrate_node` up until the next hydration end comment * Skips or removes (depending on {@link remove}) all nodes starting at `hydrate_node` up until the next hydration end comment
* @param {boolean} remove
*/ */
export function remove_nodes() { export function remove_nodes(remove = true) {
var depth = 0; var depth = 0;
var node = hydrate_node; var node = hydrate_node;
@ -100,7 +101,7 @@ export function remove_nodes() {
} }
var next = /** @type {TemplateNode} */ (get_next_sibling(node)); var next = /** @type {TemplateNode} */ (get_next_sibling(node));
node.remove(); if (remove) node.remove();
node = next; node = next;
} }
} }

@ -98,7 +98,6 @@ export class Renderer {
*/ */
child(fn, type) { child(fn, type) {
const child = new Renderer(this.global, this, type); const child = new Renderer(this.global, this, type);
this.#out.push(child);
const parent = ssr_context; const parent = ssr_context;
@ -120,6 +119,12 @@ export class Renderer {
// just to avoid unhandled promise rejections -- we'll end up throwing in `collect_async` if something fails // just to avoid unhandled promise rejections -- we'll end up throwing in `collect_async` if something fails
result.catch(() => {}); result.catch(() => {});
child.promises.initial = result; child.promises.initial = result;
// add hydration boundaries that `$.async` handles in the client
this.#out.push(BLOCK_OPEN, child, BLOCK_CLOSE);
} else {
// TODO should we always put hydration boundaries around the child?
this.#out.push(child);
} }
return child; return child;

@ -0,0 +1,33 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['hydrate'],
server_props: {
browser: false
},
ssrHtml: `
<h1>hello from the server</h1>
<h2>hello from the server</h2>
<h3>hello from the server</h3>
`,
props: {
browser: true
},
async test({ assert, target }) {
await tick();
assert.htmlEqual(
target.innerHTML,
`
<h1>hello from the browser</h1>
<h2>hello from the browser</h2>
<h3>hello from the browser</h3>
`
);
}
});

@ -0,0 +1,10 @@
<script>
let { browser = typeof window !== 'undefined' } = $props();
</script>
{#if await true}
<h1>hello from the {browser ? 'browser' : 'server'}</h1>
{/if}
<h2>hello from the {browser ? 'browser' : 'server'}</h2>
<h3>hello from the {browser ? 'browser' : 'server'}</h3>
Loading…
Cancel
Save