chore: simpler `<svelte:element>` hydration (#11773)

* chore: simpler <svelte:element> hydration

* avoid hydrate_anchor inside <svelte:element>

* tweak

* changeset
pull/11950/head
Rich Harris 7 months ago committed by GitHub
parent 6655f2c5d2
commit 123b5cbecb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: simpler `<svelte:element> hydration

@ -28,6 +28,7 @@ import {
DOMBooleanAttributes,
ELEMENT_IS_NAMESPACED,
ELEMENT_PRESERVE_ATTRIBUTE_CASE,
HYDRATION_ANCHOR,
HYDRATION_END,
HYDRATION_START
} from '../../../../constants.js';
@ -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
@ -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,
@ -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'))));

@ -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 = {};

@ -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,
@ -37,7 +37,7 @@ 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,
@ -45,7 +45,7 @@ function swap_block_dom(effect, from, to) {
* @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;
@ -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;
@ -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;
@ -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);
@ -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
@ -136,12 +143,14 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
anchor.before(element);
if (!hydrating) {
if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
} else if (!hydrating) {
} else {
push_template_node(element, parent_effect);
}
}
});
}

@ -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} */
@ -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

@ -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}-->`;

@ -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
@ -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}>`;
}

@ -1,16 +1,13 @@
<!--[-->
<!--[-->
<title>lorem</title>
<!--]-->
<!--[-->
<!---->
<style>
.ipsum {
display: block;
}
</style>
<!--]-->
<!--[-->
<!---->
<script>
console.log(true);
</script>
<!--]--><!--]-->
<!----><!--]-->

@ -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…
Cancel
Save