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, + `
+
+ +
` + ); + } +}); diff --git a/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/main.svelte b/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/main.svelte new file mode 100644 index 000000000000..3c3eb067df9c --- /dev/null +++ b/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/main.svelte @@ -0,0 +1,7 @@ +
+ +
+{@html ``} +{#if true} + {@html ``} +{/if} diff --git a/packages/svelte/tests/runtime-browser/samples/html-tag-script/_config.js b/packages/svelte/tests/runtime-browser/samples/html-tag-script/_config.js index 130d23d0b508..63aa66aa1bcc 100644 --- a/packages/svelte/tests/runtime-browser/samples/html-tag-script/_config.js +++ b/packages/svelte/tests/runtime-browser/samples/html-tag-script/_config.js @@ -3,6 +3,12 @@ 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. - html: `
`, - mode: ['client'] + mode: ['client'], + test({ window, assert }) { + // In here to give effects etc time to execute + assert.htmlEqual( + window.document.body.innerHTML, + `
` + ); + } }); diff --git a/packages/svelte/tests/runtime-browser/samples/sole-script-tag/_config.js b/packages/svelte/tests/runtime-browser/samples/sole-script-tag/_config.js new file mode 100644 index 000000000000..650e9241eaff --- /dev/null +++ b/packages/svelte/tests/runtime-browser/samples/sole-script-tag/_config.js @@ -0,0 +1,11 @@ +import { test } from '../../assert'; + +export default test({ + // Test that template with sole script tag does execute 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, 'this should be executed'); + } +}); diff --git a/packages/svelte/tests/runtime-browser/samples/sole-script-tag/main.svelte b/packages/svelte/tests/runtime-browser/samples/sole-script-tag/main.svelte new file mode 100644 index 000000000000..509b36028d43 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/samples/sole-script-tag/main.svelte @@ -0,0 +1,4 @@ +
+{#if true} + +{/if} diff --git a/packages/svelte/tests/runtime-browser/test.ts b/packages/svelte/tests/runtime-browser/test.ts index c47a72ccb432..b2df361aa562 100644 --- a/packages/svelte/tests/runtime-browser/test.ts +++ b/packages/svelte/tests/runtime-browser/test.ts @@ -194,10 +194,10 @@ async function run_test( }); if (build_result_ssr) { - const html = await page.evaluate( + const result: any = await page.evaluate( build_result_ssr.outputFiles[0].text + '; test_ssr.default()' ); - await page.setContent('
' + html + '
'); + await page.setContent('' + result.head + '
' + result.html + '
'); } else { await page.setContent('
'); }