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, DOMBooleanAttributes,
ELEMENT_IS_NAMESPACED, ELEMENT_IS_NAMESPACED,
ELEMENT_PRESERVE_ATTRIBUTE_CASE, ELEMENT_PRESERVE_ATTRIBUTE_CASE,
HYDRATION_ANCHOR,
HYDRATION_END, HYDRATION_END,
HYDRATION_START HYDRATION_START
} from '../../../../constants.js'; } from '../../../../constants.js';
@ -38,6 +39,7 @@ import { filename, locator } from '../../../state.js';
export const block_open = t_string(`<!--${HYDRATION_START}-->`); export const block_open = t_string(`<!--${HYDRATION_START}-->`);
export const block_close = t_string(`<!--${HYDRATION_END}-->`); export const block_close = t_string(`<!--${HYDRATION_END}-->`);
export const block_anchor = t_string(`<!--${HYDRATION_ANCHOR}-->`);
/** /**
* @param {string} value * @param {string} value
@ -1477,8 +1479,6 @@ const template_visitors = {
} }
}; };
context.state.template.push(block_open);
const main = /** @type {import('estree').BlockStatement} */ ( const main = /** @type {import('estree').BlockStatement} */ (
context.visit(node.fragment, { context.visit(node.fragment, {
...context.state, ...context.state,
@ -1515,7 +1515,7 @@ const template_visitors = {
) )
) )
), ),
block_close block_anchor
); );
if (context.state.options.dev) { if (context.state.options.dev) {
context.state.template.push(t_statement(b.stmt(b.call('$.pop_element')))); 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_START = '[';
export const HYDRATION_END = ']'; 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_END_ELSE = `${HYDRATION_END}!`; // used to indicate that an `{:else}...` block was rendered
export const HYDRATION_ERROR = {}; export const HYDRATION_ERROR = {};

@ -1,5 +1,5 @@
import { namespace_svg } from '../../../../constants.js'; 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 { empty } from '../operations.js';
import { import {
block, block,
@ -37,7 +37,7 @@ function swap_block_dom(effect, from, to) {
} }
/** /**
* @param {Comment} anchor * @param {Comment | Element} node
* @param {() => string} get_tag * @param {() => string} get_tag
* @param {boolean} is_svg * @param {boolean} is_svg
* @param {undefined | ((element: Element, anchor: Node | null) => void)} render_fn, * @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 * @param {undefined | [number, number]} location
* @returns {void} * @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 parent_effect = /** @type {import('#client').Effect} */ (current_effect);
const filename = DEV && location && current_component_context?.function.filename; 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; let current_tag;
/** @type {null | Element} */ /** @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} */ /** @type {import('#client').Effect | null} */
let effect; let effect;
@ -75,6 +77,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
: is_svg || next_tag === 'svg' : is_svg || next_tag === 'svg'
? namespace_svg ? namespace_svg
: null; : null;
// Assumption: Noone changes the namespace but not the tag (what would that even mean?) // Assumption: Noone changes the namespace but not the tag (what would that even mean?)
if (next_tag === tag) return; if (next_tag === tag) return;
@ -104,7 +107,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat
effect = branch(() => { effect = branch(() => {
const prev_element = element; const prev_element = element;
element = hydrating element = hydrating
? /** @type {Element} */ (hydrate_start) ? /** @type {Element} */ (element)
: ns : ns
? document.createElementNS(ns, next_tag) ? document.createElementNS(ns, next_tag)
: document.createElement(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 (render_fn) {
// If hydrating, use the existing ssr comment as the anchor so that the // 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 // inner open and close methods can pick up the existing nodes correctly
var child_anchor = hydrating var child_anchor = hydrating ? element.lastChild : element.appendChild(empty());
? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild))
: 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 // `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 // 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); anchor.before(element);
if (!hydrating) {
if (prev_element) { if (prev_element) {
swap_block_dom(parent_effect, prev_element, element); swap_block_dom(parent_effect, prev_element, element);
prev_element.remove(); prev_element.remove();
} else if (!hydrating) { } else {
push_template_node(element, parent_effect); 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 { DEV } from 'esm-env';
import { init_array_prototype_warnings } from '../dev/equality.js'; import { init_array_prototype_warnings } from '../dev/equality.js';
import { current_effect } from '../runtime.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 // export these for reference in the compiled code, making global name deduplication unnecessary
/** @type {Window} */ /** @type {Window} */
@ -105,15 +106,21 @@ export function first_child(fragment, is_text) {
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function sibling(node, is_text = false) { export function sibling(node, is_text = false) {
const next_sibling = node.nextSibling; var next_sibling = /** @type {import('#client').TemplateNode} */ (node.nextSibling);
if (!hydrating) { if (!hydrating) {
return next_sibling; 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 // if a sibling {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one // 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 text = empty();
var dom = /** @type {import('#client').TemplateNode[]} */ ( var dom = /** @type {import('#client').TemplateNode[]} */ (
/** @type {import('#client').Effect} */ (current_effect).dom /** @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_OPEN = `<!--${HYDRATION_START}-->`;
export const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`; export const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`;
export const BLOCK_ANCHOR = `<!--${HYDRATION_ANCHOR}-->`;
export const BLOCK_CLOSE_ELSE = `<!--${HYDRATION_END_ELSE}-->`; export const BLOCK_CLOSE_ELSE = `<!--${HYDRATION_END_ELSE}-->`;

@ -10,7 +10,7 @@ import {
import { escape_html } from '../../escaping.js'; import { escape_html } from '../../escaping.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { current_component, pop, push } from './context.js'; 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'; import { validate_store } from '../shared/validate.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // 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 += `>`; payload.out += `>`;
if (!VoidElements.has(tag)) { if (!VoidElements.has(tag)) {
if (!RawTextElements.includes(tag)) {
payload.out += BLOCK_OPEN;
}
children_fn(); children_fn();
if (!RawTextElements.includes(tag)) { if (!RawTextElements.includes(tag)) {
payload.out += BLOCK_CLOSE; payload.out += BLOCK_ANCHOR;
} }
payload.out += `</${tag}>`; payload.out += `</${tag}>`;
} }

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

@ -3,7 +3,6 @@ import * as $ from "svelte/internal/server";
export default function Svelte_element($$payload, $$props) { export default function Svelte_element($$payload, $$props) {
let { tag = 'hr' } = $$props; let { tag = 'hr' } = $$props;
$$payload.out += `<!--[-->`;
if (tag) $.element($$payload, tag, () => {}, () => {}); if (tag) $.element($$payload, tag, () => {}, () => {});
$$payload.out += `<!--]-->`; $$payload.out += `<!---->`;
} }
Loading…
Cancel
Save