diff --git a/.changeset/cuddly-points-tickle.md b/.changeset/cuddly-points-tickle.md
new file mode 100644
index 000000000000..4a75553b4213
--- /dev/null
+++ b/.changeset/cuddly-points-tickle.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: execute sole static script tag
diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js
index 827508b739fb..8e1f51c8eb1d 100644
--- a/packages/svelte/src/internal/client/dom/template.js
+++ b/packages/svelte/src/internal/client/dom/template.js
@@ -3,6 +3,7 @@ import { clone_node, empty } from './operations.js';
import { create_fragment_from_html } from './reconciler.js';
import { current_effect } from '../runtime.js';
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js';
+import { effect } from '../reactivity/effects.js';
/**
* @param {string} content
@@ -120,14 +121,29 @@ export function svg_template_with_script(content, flags) {
* @param {Element | DocumentFragment} node
*/
function run_scripts(node) {
- for (const script of node.querySelectorAll('script')) {
+ // scripts were SSR'd, in which case they will run
+ if (hydrating) return;
+
+ const scripts =
+ /** @type {HTMLElement} */ (node).tagName === 'SCRIPT'
+ ? [/** @type {HTMLScriptElement} */ (node)]
+ : node.querySelectorAll('script');
+ for (const script of scripts) {
var clone = document.createElement('script');
for (var attribute of script.attributes) {
clone.setAttribute(attribute.name, attribute.value);
}
clone.textContent = script.textContent;
- script.replaceWith(clone);
+ // If node === script tag, replaceWith will do nothing because there's no parent yet,
+ // waiting until that's the case using an effect solves this.
+ // Don't do it in other circumstances or we could accidentally execute scripts
+ // in an adjacent @html tag that was instantiated in the meantime.
+ if (script === node) {
+ effect(() => script.replaceWith(clone));
+ } else {
+ script.replaceWith(clone);
+ }
}
}
diff --git a/packages/svelte/tests/runtime-browser/driver-ssr.js b/packages/svelte/tests/runtime-browser/driver-ssr.js
index 71a8877a9ec3..f5f15b64934f 100644
--- a/packages/svelte/tests/runtime-browser/driver-ssr.js
+++ b/packages/svelte/tests/runtime-browser/driver-ssr.js
@@ -6,5 +6,5 @@ import config from '__CONFIG__';
import { render } from 'svelte/server';
export default function () {
- return render(SvelteComponent, { props: config.props || {} }).html;
+ return render(SvelteComponent, { props: config.props || {} });
}
diff --git a/packages/svelte/tests/runtime-legacy/samples/head-script/_config.js b/packages/svelte/tests/runtime-browser/samples/head-script/_config.js
similarity index 69%
rename from packages/svelte/tests/runtime-legacy/samples/head-script/_config.js
rename to packages/svelte/tests/runtime-browser/samples/head-script/_config.js
index 31acab66a39b..b40d4e22e250 100644
--- a/packages/svelte/tests/runtime-legacy/samples/head-script/_config.js
+++ b/packages/svelte/tests/runtime-browser/samples/head-script/_config.js
@@ -1,7 +1,7 @@
-import { test } from '../../test';
+import { test } from '../../assert';
export default test({
- test({ assert, component, window }) {
+ test({ assert, window }) {
document.dispatchEvent(new Event('DOMContentLoaded'));
assert.equal(window.document.querySelector('button')?.textContent, 'Hello world');
}
diff --git a/packages/svelte/tests/runtime-legacy/samples/head-script/main.svelte b/packages/svelte/tests/runtime-browser/samples/head-script/main.svelte
similarity index 100%
rename from packages/svelte/tests/runtime-legacy/samples/head-script/main.svelte
rename to packages/svelte/tests/runtime-browser/samples/head-script/main.svelte
diff --git a/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/_config.js b/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/_config.js
new file mode 100644
index 000000000000..c8fbb89b7c5a
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/_config.js
@@ -0,0 +1,17 @@
+import { test } from '../../assert';
+
+export default test({
+ // Test that @html does not execute scripts when instantiated in the client.
+ // Needs to be in this test suite because JSDOM does not quite get this right.
+ mode: ['client'],
+ test({ window, assert }) {
+ // In here to give effects etc time to execute
+ assert.htmlEqual(
+ window.document.body.innerHTML,
+ `