From a031df8c0d66f0fa426102cc9eb3f876004f1256 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 11 Dec 2024 18:18:15 -0500 Subject: [PATCH 1/4] alternative approach to #14624 --- .../src/internal/client/dom/blocks/svelte-element.js | 6 ++++++ packages/svelte/src/internal/server/index.js | 7 ++----- packages/svelte/src/utils.js | 8 ++++++++ .../svelte/tests/hydration/samples/script/_config.js | 11 +++++++++++ .../tests/hydration/samples/script/_expected.html | 1 + .../svelte/tests/hydration/samples/script/main.svelte | 1 + 6 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 packages/svelte/tests/hydration/samples/script/_config.js create mode 100644 packages/svelte/tests/hydration/samples/script/_expected.html create mode 100644 packages/svelte/tests/hydration/samples/script/main.svelte diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index 823b9a436253..35d2f223aed5 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -21,6 +21,7 @@ import { component_context, active_effect } from '../../runtime.js'; import { DEV } from 'esm-env'; import { EFFECT_TRANSPARENT } from '../../constants.js'; import { assign_nodes } from '../template.js'; +import { is_raw_text_element } from '../../../../utils.js'; /** * @param {Comment | Element} node @@ -116,6 +117,11 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio assign_nodes(element, element); if (render_fn) { + if (hydrating && is_raw_text_element(next_tag)) { + // prevent hydration glitches + element.append(document.createComment('')); + } + // 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 = /** @type {TemplateNode} */ ( diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index b944c602b884..52404215e47f 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -16,7 +16,7 @@ import { DEV } from 'esm-env'; import { current_component, pop, push } from './context.js'; import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; import { validate_store } from '../shared/validate.js'; -import { is_boolean_attribute, is_void } from '../../utils.js'; +import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js'; import { reset_elements } from './dev.js'; // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 @@ -24,9 +24,6 @@ import { reset_elements } from './dev.js'; const INVALID_ATTR_NAME_CHAR_REGEX = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u; -/** List of elements that require raw contents and should not have SSR comments put in them */ -const RAW_TEXT_ELEMENTS = ['textarea', 'script', 'style', 'title']; - /** * @param {Payload} to_copy * @returns {Payload} @@ -70,7 +67,7 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop) if (!is_void(tag)) { children_fn(); - if (!RAW_TEXT_ELEMENTS.includes(tag)) { + if (!is_raw_text_element(tag)) { payload.out += EMPTY_COMMENT; } payload.out += ``; diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index 75171c17865a..bdd4422decc5 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -441,3 +441,11 @@ const RUNES = /** @type {const} */ ([ export function is_rune(name) { return RUNES.includes(/** @type {RUNES[number]} */ (name)); } + +/** List of elements that require raw contents and should not have SSR comments put in them */ +const RAW_TEXT_ELEMENTS = /** @type {const} */ (['textarea', 'script', 'style', 'title']); + +/** @param {string} name */ +export function is_raw_text_element(name) { + return RAW_TEXT_ELEMENTS.includes(name); +} diff --git a/packages/svelte/tests/hydration/samples/script/_config.js b/packages/svelte/tests/hydration/samples/script/_config.js new file mode 100644 index 000000000000..4723e4e454bc --- /dev/null +++ b/packages/svelte/tests/hydration/samples/script/_config.js @@ -0,0 +1,11 @@ +import { test } from '../../test'; + +export default test({ + snapshot(target) { + const script = target.querySelector('script'); + + return { + script + }; + } +}); diff --git a/packages/svelte/tests/hydration/samples/script/_expected.html b/packages/svelte/tests/hydration/samples/script/_expected.html new file mode 100644 index 000000000000..b3a4d922196b --- /dev/null +++ b/packages/svelte/tests/hydration/samples/script/_expected.html @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/hydration/samples/script/main.svelte b/packages/svelte/tests/hydration/samples/script/main.svelte new file mode 100644 index 000000000000..3904d47f730f --- /dev/null +++ b/packages/svelte/tests/hydration/samples/script/main.svelte @@ -0,0 +1 @@ +{"{}"} From af3a8861bb883a3cd3ad2217e226284030667878 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 11 Dec 2024 18:18:37 -0500 Subject: [PATCH 2/4] changeset --- .changeset/rotten-yaks-nail.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rotten-yaks-nail.md diff --git a/.changeset/rotten-yaks-nail.md b/.changeset/rotten-yaks-nail.md new file mode 100644 index 000000000000..bbe9b777ae81 --- /dev/null +++ b/.changeset/rotten-yaks-nail.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: better handle hydration of script/style elements From 7166c2deadbbb0bdb58edf67426b8edc52b0f8ce Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 11 Dec 2024 18:19:04 -0500 Subject: [PATCH 3/4] fix --- packages/svelte/src/internal/server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 52404215e47f..b8371b7e008f 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -61,7 +61,7 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop) payload.out += ''; if (tag) { - payload.out += `<${tag} `; + payload.out += `<${tag}`; attributes_fn(); payload.out += `>`; From fa34d3dc42583eb7441d625d0a8d81f07cfb8b47 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 11 Dec 2024 18:47:04 -0500 Subject: [PATCH 4/4] lint --- packages/svelte/src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index bdd4422decc5..932440800795 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -447,5 +447,5 @@ const RAW_TEXT_ELEMENTS = /** @type {const} */ (['textarea', 'script', 'style', /** @param {string} name */ export function is_raw_text_element(name) { - return RAW_TEXT_ELEMENTS.includes(name); + return RAW_TEXT_ELEMENTS.includes(/** @type {RAW_TEXT_ELEMENTS[number]} */ (name)); }