diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js
index 6d4d45558c..809c627098 100644
--- a/packages/svelte/src/compiler/phases/3-transform/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/utils.js
@@ -270,6 +270,19 @@ export function clean_nodes(
var first = trimmed[0];
+ // Special case: Add a comment if this is a lone script tag. This ensures that our run_scripts logic in template.js
+ // will always be able to call node.replaceWith() on the script tag in order to make it run. If we don't add this
+ // and would still call node.replaceWith() on the script tag, it would be a no-op because the script tag has no parent.
+ if (trimmed.length === 1 && first.type === 'RegularElement' && first.name === 'script') {
+ trimmed.push({
+ type: 'Comment',
+ data: '',
+ parent: first.parent,
+ start: -1,
+ end: -1
+ });
+ }
+
return {
hoisted,
trimmed,
diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js
index f5c1748a8a..7ccffc7d2c 100644
--- a/packages/svelte/src/internal/client/dom/template.js
+++ b/packages/svelte/src/internal/client/dom/template.js
@@ -72,21 +72,8 @@ export function template(content, flags) {
*/
/*#__NO_SIDE_EFFECTS__*/
export function template_with_script(content, flags) {
- var first = true;
var fn = template(content, flags);
-
- return () => {
- if (hydrating) return fn();
-
- var node = /** @type {Element | DocumentFragment} */ (fn());
-
- if (first) {
- first = false;
- run_scripts(node);
- }
-
- return node;
- };
+ return () => run_scripts(/** @type {Element | DocumentFragment} */ (fn()));
}
/**
@@ -151,21 +138,8 @@ export function ns_template(content, flags, ns = 'svg') {
*/
/*#__NO_SIDE_EFFECTS__*/
export function svg_template_with_script(content, flags) {
- var first = true;
var fn = ns_template(content, flags);
-
- return () => {
- if (hydrating) return fn();
-
- var node = /** @type {Element | DocumentFragment} */ (fn());
-
- if (first) {
- first = false;
- run_scripts(node);
- }
-
- return node;
- };
+ return () => run_scripts(/** @type {Element | DocumentFragment} */ (fn()));
}
/**
@@ -182,10 +156,11 @@ export function mathml_template(content, flags) {
* Creating a document fragment from HTML that contains script tags will not execute
* the scripts. We need to replace the script tags with new ones so that they are executed.
* @param {Element | DocumentFragment} node
+ * @returns {Node | Node[]}
*/
function run_scripts(node) {
// scripts were SSR'd, in which case they will run
- if (hydrating) return;
+ if (hydrating) return node;
const is_fragment = node.nodeType === 11;
const scripts =
@@ -202,28 +177,17 @@ function run_scripts(node) {
clone.textContent = script.textContent;
- const replace = () => {
- // The script has changed - if it's at the edges, the effect now points at dead nodes
- if (is_fragment ? node.firstChild === script : node === script) {
- effect.nodes_start = clone;
- }
- if (is_fragment ? node.lastChild === script : node === script) {
- effect.nodes_end = clone;
- }
-
- 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) {
- queue_micro_task(replace);
- } else {
- replace();
+ // The script has changed - if it's at the edges, the effect now points at dead nodes
+ if (is_fragment ? node.firstChild === script : node === script) {
+ effect.nodes_start = clone;
+ }
+ if (is_fragment ? node.lastChild === script : node === script) {
+ effect.nodes_end = clone;
}
+
+ script.replaceWith(clone);
}
+ return node;
}
/**
diff --git a/packages/svelte/tests/runtime-browser/samples/head-scripts/_config.js b/packages/svelte/tests/runtime-browser/samples/head-scripts/_config.js
new file mode 100644
index 0000000000..3ff1bf7286
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/samples/head-scripts/_config.js
@@ -0,0 +1,13 @@
+import { test } from '../../assert';
+
+export default test({
+ mode: ['client'],
+ async test({ assert, window }) {
+ // wait the script to load (maybe there a better way)
+ await new Promise((resolve) => setTimeout(resolve, 1));
+ assert.htmlEqual(
+ window.document.body.innerHTML,
+ `123`
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-browser/samples/head-scripts/main.svelte b/packages/svelte/tests/runtime-browser/samples/head-scripts/main.svelte
new file mode 100644
index 0000000000..999217a7a7
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/samples/head-scripts/main.svelte
@@ -0,0 +1,16 @@
+
+
+
+ {#each scriptSrcs as src}
+
+ {/each}
+
+
+???
\ No newline at end of file