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