diff --git a/.changeset/sixty-cats-search.md b/.changeset/sixty-cats-search.md new file mode 100644 index 000000000000..6ffd00749437 --- /dev/null +++ b/.changeset/sixty-cats-search.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: catch error on @const tag in svelte:boundary in DEV mode diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js index 6da650a591d9..9228df970375 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js @@ -1,6 +1,7 @@ /** @import { BlockStatement, Statement, Expression } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ +import { dev } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; /** @@ -35,6 +36,9 @@ export function SvelteBoundary(node, context) { /** @type {Statement[]} */ const external_statements = []; + /** @type {Statement[]} */ + const internal_statements = []; + const snippets_visits = []; // Capture the `failed` implicit snippet prop @@ -53,7 +57,20 @@ export function SvelteBoundary(node, context) { /** @type {Statement[]} */ const init = []; context.visit(child, { ...context.state, init }); - external_statements.push(...init); + + if (dev) { + // In dev we must separate the declarations from the code + // that eagerly evaluate the expression... + for (const statement of init) { + if (statement.type === 'VariableDeclaration') { + external_statements.push(statement); + } else { + internal_statements.push(statement); + } + } + } else { + external_statements.push(...init); + } } else { nodes.push(child); } @@ -63,6 +80,10 @@ export function SvelteBoundary(node, context) { const block = /** @type {BlockStatement} */ (context.visit({ ...node.fragment, nodes })); + if (dev && internal_statements.length) { + block.body.unshift(...internal_statements); + } + const boundary = b.stmt( b.call('$.boundary', context.state.node, props, b.arrow([b.id('$$anchor')], block)) ); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/_config.js new file mode 100644 index 000000000000..3c0195ce3495 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/_config.js @@ -0,0 +1,33 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +// https://github.com/sveltejs/svelte/issues/15368 +export default test({ + compileOptions: { + dev: true + }, + + mode: ['client'], + + html: ` +

BOOM

+

BOOM

+
OK
+
OK
+ `, + + async test({ target, assert, component }) { + component.ok = false; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +

BOOM

+

BOOM

+

BOOM

+

BOOM

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/main.svelte new file mode 100644 index 000000000000..30e074c762f6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/main.svelte @@ -0,0 +1,49 @@ + + + +
{throwError()}
+ + {#snippet failed()} +

BOOM

+ {/snippet} +
+ + + {@const result = throwError()} +
{result}
+ + {#snippet failed()} +

BOOM

+ {/snippet} +
+ + +
{throwErrorOnUpdate()}
+ + {#snippet failed()} +

BOOM

+ {/snippet} +
+ + + {@const result = throwErrorOnUpdate()} +
{result}
+ + {#snippet failed()} +

BOOM

+ {/snippet} +