Skip to content

Commit 123b5cb

Browse files
authored
chore: simpler <svelte:element> hydration (#11773)
* chore: simpler <svelte:element> hydration * avoid hydrate_anchor inside <svelte:element> * tweak * changeset
1 parent 6655f2c commit 123b5cb

File tree

9 files changed

+53
-32
lines changed

9 files changed

+53
-32
lines changed

.changeset/fast-donkeys-pay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: simpler `<svelte:element> hydration

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
DOMBooleanAttributes,
2929
ELEMENT_IS_NAMESPACED,
3030
ELEMENT_PRESERVE_ATTRIBUTE_CASE,
31+
HYDRATION_ANCHOR,
3132
HYDRATION_END,
3233
HYDRATION_START
3334
} from '../../../../constants.js';
@@ -38,6 +39,7 @@ import { filename, locator } from '../../../state.js';
3839

3940
export const block_open = t_string(`<!--${HYDRATION_START}-->`);
4041
export const block_close = t_string(`<!--${HYDRATION_END}-->`);
42+
export const block_anchor = t_string(`<!--${HYDRATION_ANCHOR}-->`);
4143

4244
/**
4345
* @param {string} value
@@ -1477,8 +1479,6 @@ const template_visitors = {
14771479
}
14781480
};
14791481

1480-
context.state.template.push(block_open);
1481-
14821482
const main = /** @type {import('estree').BlockStatement} */ (
14831483
context.visit(node.fragment, {
14841484
...context.state,
@@ -1515,7 +1515,7 @@ const template_visitors = {
15151515
)
15161516
)
15171517
),
1518-
block_close
1518+
block_anchor
15191519
);
15201520
if (context.state.options.dev) {
15211521
context.state.template.push(t_statement(b.stmt(b.call('$.pop_element'))));

packages/svelte/src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
2121

2222
export const HYDRATION_START = '[';
2323
export const HYDRATION_END = ']';
24+
export const HYDRATION_ANCHOR = '';
2425
export const HYDRATION_END_ELSE = `${HYDRATION_END}!`; // used to indicate that an `{:else}...` block was rendered
2526
export const HYDRATION_ERROR = {};
2627

packages/svelte/src/internal/client/dom/blocks/svelte-element.js

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { namespace_svg } from '../../../../constants.js';
2-
import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js';
2+
import { hydrating, set_hydrate_nodes } from '../hydration.js';
33
import { empty } from '../operations.js';
44
import {
55
block,
@@ -37,15 +37,15 @@ function swap_block_dom(effect, from, to) {
3737
}
3838

3939
/**
40-
* @param {Comment} anchor
40+
* @param {Comment | Element} node
4141
* @param {() => string} get_tag
4242
* @param {boolean} is_svg
4343
* @param {undefined | ((element: Element, anchor: Node | null) => void)} render_fn,
4444
* @param {undefined | (() => string)} get_namespace
4545
* @param {undefined | [number, number]} location
4646
* @returns {void}
4747
*/
48-
export function element(anchor, get_tag, is_svg, render_fn, get_namespace, location) {
48+
export function element(node, get_tag, is_svg, render_fn, get_namespace, location) {
4949
const parent_effect = /** @type {import('#client').Effect} */ (current_effect);
5050
const filename = DEV && location && current_component_context?.function.filename;
5151

@@ -56,7 +56,9 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
5656
let current_tag;
5757

5858
/** @type {null | Element} */
59-
let element = null;
59+
let element = hydrating && node.nodeType === 1 ? /** @type {Element} */ (node) : null;
60+
61+
let anchor = /** @type {Comment} */ (hydrating && element ? element.nextSibling : node);
6062

6163
/** @type {import('#client').Effect | null} */
6264
let effect;
@@ -75,6 +77,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
7577
: is_svg || next_tag === 'svg'
7678
? namespace_svg
7779
: null;
80+
7881
// Assumption: Noone changes the namespace but not the tag (what would that even mean?)
7982
if (next_tag === tag) return;
8083

@@ -104,7 +107,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
104107
effect = branch(() => {
105108
const prev_element = element;
106109
element = hydrating
107-
? /** @type {Element} */ (hydrate_start)
110+
? /** @type {Element} */ (element)
108111
: ns
109112
? document.createElementNS(ns, next_tag)
110113
: document.createElement(next_tag);
@@ -123,9 +126,13 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
123126
if (render_fn) {
124127
// If hydrating, use the existing ssr comment as the anchor so that the
125128
// inner open and close methods can pick up the existing nodes correctly
126-
var child_anchor = hydrating
127-
? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild))
128-
: element.appendChild(empty());
129+
var child_anchor = hydrating ? element.lastChild : element.appendChild(empty());
130+
131+
if (hydrating && child_anchor) {
132+
set_hydrate_nodes(
133+
/** @type {import('#client').TemplateNode[]} */ ([...element.childNodes]).slice(0, -1)
134+
);
135+
}
129136

130137
// `child_anchor` is undefined if this is a void element, but we still
131138
// need to call `render_fn` in order to run actions etc. If the element
@@ -136,11 +143,13 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
136143

137144
anchor.before(element);
138145

139-
if (prev_element) {
140-
swap_block_dom(parent_effect, prev_element, element);
141-
prev_element.remove();
142-
} else if (!hydrating) {
143-
push_template_node(element, parent_effect);
146+
if (!hydrating) {
147+
if (prev_element) {
148+
swap_block_dom(parent_effect, prev_element, element);
149+
prev_element.remove();
150+
} else {
151+
push_template_node(element, parent_effect);
152+
}
144153
}
145154
});
146155
}

packages/svelte/src/internal/client/dom/operations.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { hydrate_anchor, hydrate_start, hydrating } from './hydration.js';
22
import { DEV } from 'esm-env';
33
import { init_array_prototype_warnings } from '../dev/equality.js';
44
import { current_effect } from '../runtime.js';
5+
import { HYDRATION_ANCHOR } from '../../../constants.js';
56

67
// export these for reference in the compiled code, making global name deduplication unnecessary
78
/** @type {Window} */
@@ -105,15 +106,21 @@ export function first_child(fragment, is_text) {
105106
*/
106107
/*#__NO_SIDE_EFFECTS__*/
107108
export function sibling(node, is_text = false) {
108-
const next_sibling = node.nextSibling;
109+
var next_sibling = /** @type {import('#client').TemplateNode} */ (node.nextSibling);
109110

110111
if (!hydrating) {
111112
return next_sibling;
112113
}
113114

115+
var type = next_sibling.nodeType;
116+
117+
if (type === 8 && /** @type {Comment} */ (next_sibling).data === HYDRATION_ANCHOR) {
118+
return sibling(next_sibling, is_text);
119+
}
120+
114121
// if a sibling {expression} is empty during SSR, there might be no
115122
// text node to hydrate — we must therefore create one
116-
if (is_text && next_sibling?.nodeType !== 3) {
123+
if (is_text && type !== 3) {
117124
var text = empty();
118125
var dom = /** @type {import('#client').TemplateNode[]} */ (
119126
/** @type {import('#client').Effect} */ (current_effect).dom
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import { HYDRATION_END, HYDRATION_END_ELSE, HYDRATION_START } from '../../constants.js';
1+
import {
2+
HYDRATION_ANCHOR,
3+
HYDRATION_END,
4+
HYDRATION_END_ELSE,
5+
HYDRATION_START
6+
} from '../../constants.js';
27

38
export const BLOCK_OPEN = `<!--${HYDRATION_START}-->`;
49
export const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`;
10+
export const BLOCK_ANCHOR = `<!--${HYDRATION_ANCHOR}-->`;
511
export const BLOCK_CLOSE_ELSE = `<!--${HYDRATION_END_ELSE}-->`;

packages/svelte/src/internal/server/index.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { escape_html } from '../../escaping.js';
1111
import { DEV } from 'esm-env';
1212
import { current_component, pop, push } from './context.js';
13-
import { BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
13+
import { BLOCK_ANCHOR, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
1414
import { validate_store } from '../shared/validate.js';
1515

1616
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
@@ -78,12 +78,9 @@ export function element(payload, tag, attributes_fn, children_fn) {
7878
payload.out += `>`;
7979

8080
if (!VoidElements.has(tag)) {
81-
if (!RawTextElements.includes(tag)) {
82-
payload.out += BLOCK_OPEN;
83-
}
8481
children_fn();
8582
if (!RawTextElements.includes(tag)) {
86-
payload.out += BLOCK_CLOSE;
83+
payload.out += BLOCK_ANCHOR;
8784
}
8885
payload.out += `</${tag}>`;
8986
}
Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
<!--[-->
2-
<!--[-->
32
<title>lorem</title>
4-
<!--]-->
5-
<!--[-->
3+
<!---->
64
<style>
75
.ipsum {
86
display: block;
97
}
108
</style>
11-
<!--]-->
12-
<!--[-->
9+
<!---->
1310
<script>
1411
console.log(true);
1512
</script>
16-
<!--]--><!--]-->
13+
<!----><!--]-->

packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as $ from "svelte/internal/server";
33
export default function Svelte_element($$payload, $$props) {
44
let { tag = 'hr' } = $$props;
55

6-
$$payload.out += `<!--[-->`;
76
if (tag) $.element($$payload, tag, () => {}, () => {});
8-
$$payload.out += `<!--]-->`;
7+
$$payload.out += `<!---->`;
98
}

0 commit comments

Comments
 (0)