Skip to content

Commit f0ca244

Browse files
committed
fix hydration with HMR (hopefuly)
1 parent 4b9c4e1 commit f0ca244

File tree

1 file changed

+67
-13
lines changed
  • packages/svelte/src/internal/client

1 file changed

+67
-13
lines changed

packages/svelte/src/internal/client/hmr.js

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,44 @@
1-
import { key } from './render.js';
1+
import { key, comment } from './render.js';
22
import { source, set, get } from './runtime.js';
3+
import { current_hydration_fragment } from './hydration.js';
4+
import { child_frag } from './operations.js';
5+
6+
function find_surrounding_ssr_commments() {
7+
if (!current_hydration_fragment?.[0]) return null;
8+
9+
/** @type {Comment | undefined} */
10+
let before;
11+
/** @type {Comment | undefined} */
12+
let after;
13+
/** @type {Node | null | undefined} */
14+
let node;
15+
16+
node = current_hydration_fragment[0].previousSibling;
17+
while (node) {
18+
const comment = /** @type {Comment} */ (node);
19+
if (node.nodeType === 8 && comment.data.startsWith('ssr:')) {
20+
before = comment;
21+
break;
22+
}
23+
node = node.previousSibling;
24+
}
25+
26+
node = current_hydration_fragment.at(-1)?.nextSibling;
27+
while (node) {
28+
const comment = /** @type {Comment} */ (node);
29+
if (node.nodeType === 8 && comment.data.startsWith('ssr:')) {
30+
after = comment;
31+
break;
32+
}
33+
node = node.nextSibling;
34+
}
35+
36+
if (before && after && before.data === after.data) {
37+
return [before, after];
38+
}
39+
40+
return null;
41+
}
342

443
/**
544
* @template {any[]} ComponentArgs
@@ -10,38 +49,53 @@ import { source, set, get } from './runtime.js';
1049
* component_signal: ReturnType<typeof source<Component>>,
1150
* proxy?: (...args: ComponentArgs) => ComponentReturn
1251
* }} hot_data
13-
* @param {Component} component
52+
* @param {Component} new_component
1453
*/
15-
export function hmr(hot_data, component) {
54+
export function hmr(hot_data, new_component) {
1655
if (hot_data.proxy) {
17-
set(hot_data.component_signal, component);
56+
set(hot_data.component_signal, new_component);
1857
} else {
19-
const component_signal = (hot_data.component_signal = source(component));
58+
const component_signal = source(new_component);
59+
60+
hot_data.component_signal = component_signal;
2061

2162
// @ts-ignore
22-
hot_data.proxy = function (target, ...args) {
23-
const accessors = source(/** @type {ComponentReturn} */ ({}));
63+
hot_data.proxy = function ($$anchor, ...args) {
64+
let accessors = /** @type {ComponentReturn} */ ({});
65+
66+
// During hydration the root component will receive a null $$anchor. The
67+
// following is a hack to get our `key` a node to render to, all while
68+
// avoiding it to "consume" the SSR marker.
69+
// TODO better get the eyes of someone with understanding of hydration on this
70+
if (!$$anchor && current_hydration_fragment?.[0]) {
71+
const ssr0 = find_surrounding_ssr_commments();
72+
if (ssr0) {
73+
const [before, after] = ssr0;
74+
current_hydration_fragment.unshift(before);
75+
current_hydration_fragment.push(after);
76+
$$anchor = child_frag(current_hydration_fragment);
77+
}
78+
}
2479

2580
key(
26-
target,
81+
$$anchor,
2782
() => get(component_signal),
2883
($$anchor) => {
29-
const current_component = get(component_signal);
84+
const component = get(component_signal);
3085
// @ts-ignore
31-
const new_accessors = current_component($$anchor, ...args);
32-
set(accessors, new_accessors);
86+
accessors = component($$anchor, ...args);
3387
}
3488
);
3589

3690
return new Proxy(
3791
{},
3892
{
3993
get(_, p) {
40-
return get(accessors)?.[p];
94+
return accessors?.[p];
4195
},
4296
set(_, p, value) {
4397
// @ts-ignore (we actually want to crash on undefined, like non HMR code would do)
44-
get(accessors)[p] = value;
98+
accessors[p] = value;
4599
return true;
46100
}
47101
}

0 commit comments

Comments
 (0)