Skip to content

chore: simpler <svelte:element> hydration #11773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fast-donkeys-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

feat: simpler `<svelte:element> hydration
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
DOMBooleanAttributes,
ELEMENT_IS_NAMESPACED,
ELEMENT_PRESERVE_ATTRIBUTE_CASE,
HYDRATION_ANCHOR,
HYDRATION_END,
HYDRATION_START
} from '../../../../constants.js';
Expand All @@ -38,6 +39,7 @@ import { filename, locator } from '../../../state.js';

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

/**
* @param {string} value
Expand Down Expand Up @@ -1477,8 +1479,6 @@ const template_visitors = {
}
};

context.state.template.push(block_open);

const main = /** @type {import('estree').BlockStatement} */ (
context.visit(node.fragment, {
...context.state,
Expand Down Expand Up @@ -1515,7 +1515,7 @@ const template_visitors = {
)
)
),
block_close
block_anchor
);
if (context.state.options.dev) {
context.state.template.push(t_statement(b.stmt(b.call('$.pop_element'))));
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;

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

Expand Down
35 changes: 22 additions & 13 deletions packages/svelte/src/internal/client/dom/blocks/svelte-element.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { namespace_svg } from '../../../../constants.js';
import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js';
import { hydrating, set_hydrate_nodes } from '../hydration.js';
import { empty } from '../operations.js';
import {
block,
Expand Down Expand Up @@ -37,15 +37,15 @@ function swap_block_dom(effect, from, to) {
}

/**
* @param {Comment} anchor
* @param {Comment | Element} node
* @param {() => string} get_tag
* @param {boolean} is_svg
* @param {undefined | ((element: Element, anchor: Node | null) => void)} render_fn,
* @param {undefined | (() => string)} get_namespace
* @param {undefined | [number, number]} location
* @returns {void}
*/
export function element(anchor, get_tag, is_svg, render_fn, get_namespace, location) {
export function element(node, get_tag, is_svg, render_fn, get_namespace, location) {
const parent_effect = /** @type {import('#client').Effect} */ (current_effect);
const filename = DEV && location && current_component_context?.function.filename;

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

/** @type {null | Element} */
let element = null;
let element = hydrating && node.nodeType === 1 ? /** @type {Element} */ (node) : null;

let anchor = /** @type {Comment} */ (hydrating && element ? element.nextSibling : node);

/** @type {import('#client').Effect | null} */
let effect;
Expand All @@ -75,6 +77,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
: is_svg || next_tag === 'svg'
? namespace_svg
: null;

// Assumption: Noone changes the namespace but not the tag (what would that even mean?)
if (next_tag === tag) return;

Expand Down Expand Up @@ -104,7 +107,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
effect = branch(() => {
const prev_element = element;
element = hydrating
? /** @type {Element} */ (hydrate_start)
? /** @type {Element} */ (element)
: ns
? document.createElementNS(ns, next_tag)
: document.createElement(next_tag);
Expand All @@ -123,9 +126,13 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
if (render_fn) {
// If hydrating, use the existing ssr comment as the anchor so that the
// inner open and close methods can pick up the existing nodes correctly
var child_anchor = hydrating
? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild))
: element.appendChild(empty());
var child_anchor = hydrating ? element.lastChild : element.appendChild(empty());

if (hydrating && child_anchor) {
set_hydrate_nodes(
/** @type {import('#client').TemplateNode[]} */ ([...element.childNodes]).slice(0, -1)
);
}

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

anchor.before(element);

if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
} else if (!hydrating) {
push_template_node(element, parent_effect);
if (!hydrating) {
if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
} else {
push_template_node(element, parent_effect);
}
}
});
}
Expand Down
11 changes: 9 additions & 2 deletions packages/svelte/src/internal/client/dom/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { hydrate_anchor, hydrate_start, hydrating } from './hydration.js';
import { DEV } from 'esm-env';
import { init_array_prototype_warnings } from '../dev/equality.js';
import { current_effect } from '../runtime.js';
import { HYDRATION_ANCHOR } from '../../../constants.js';

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

if (!hydrating) {
return next_sibling;
}

var type = next_sibling.nodeType;

if (type === 8 && /** @type {Comment} */ (next_sibling).data === HYDRATION_ANCHOR) {
return sibling(next_sibling, is_text);
}

// if a sibling {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one
if (is_text && next_sibling?.nodeType !== 3) {
if (is_text && type !== 3) {
var text = empty();
var dom = /** @type {import('#client').TemplateNode[]} */ (
/** @type {import('#client').Effect} */ (current_effect).dom
Expand Down
8 changes: 7 additions & 1 deletion packages/svelte/src/internal/server/hydration.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { HYDRATION_END, HYDRATION_END_ELSE, HYDRATION_START } from '../../constants.js';
import {
HYDRATION_ANCHOR,
HYDRATION_END,
HYDRATION_END_ELSE,
HYDRATION_START
} from '../../constants.js';

export const BLOCK_OPEN = `<!--${HYDRATION_START}-->`;
export const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`;
export const BLOCK_ANCHOR = `<!--${HYDRATION_ANCHOR}-->`;
export const BLOCK_CLOSE_ELSE = `<!--${HYDRATION_END_ELSE}-->`;
7 changes: 2 additions & 5 deletions packages/svelte/src/internal/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { escape_html } from '../../escaping.js';
import { DEV } from 'esm-env';
import { current_component, pop, push } from './context.js';
import { BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
import { BLOCK_ANCHOR, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
import { validate_store } from '../shared/validate.js';

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

if (!VoidElements.has(tag)) {
if (!RawTextElements.includes(tag)) {
payload.out += BLOCK_OPEN;
}
children_fn();
if (!RawTextElements.includes(tag)) {
payload.out += BLOCK_CLOSE;
payload.out += BLOCK_ANCHOR;
}
payload.out += `</${tag}>`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
<!--[-->
<!--[-->
<title>lorem</title>
<!--]-->
<!--[-->
<!---->
<style>
.ipsum {
display: block;
}
</style>
<!--]-->
<!--[-->
<!---->
<script>
console.log(true);
</script>
<!--]--><!--]-->
<!----><!--]-->
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as $ from "svelte/internal/server";
export default function Svelte_element($$payload, $$props) {
let { tag = 'hr' } = $$props;

$$payload.out += `<!--[-->`;
if (tag) $.element($$payload, tag, () => {}, () => {});
$$payload.out += `<!--]-->`;
$$payload.out += `<!---->`;
}
Loading