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
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..b8371b7e008f 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}
@@ -64,13 +61,13 @@ 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 += `>`;
if (!is_void(tag)) {
children_fn();
- if (!RAW_TEXT_ELEMENTS.includes(tag)) {
+ if (!is_raw_text_element(tag)) {
payload.out += EMPTY_COMMENT;
}
payload.out += `${tag}>`;
diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js
index 75171c17865a..932440800795 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(/** @type {RAW_TEXT_ELEMENTS[number]} */ (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 @@
+{"{}"}