From ce4f9722dab8d99140d410a9d1f71ec19d16535e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 8 Jan 2025 09:54:29 -0500 Subject: [PATCH 001/265] fix: remove bindable prop validation (#14946) --- .changeset/flat-otters-cheer.md | 5 ++++ .../3-transform/client/transform-client.js | 22 ---------------- packages/svelte/src/internal/client/index.js | 2 +- .../src/internal/client/reactivity/props.js | 6 +++-- .../svelte/src/internal/client/validate.js | 25 +------------------ .../samples/export-binding/_config.js | 10 -------- .../export-binding/counter/index.svelte | 8 ------ .../samples/export-binding/main.svelte | 7 ------ .../props-not-bindable-spread/Counter.svelte | 5 ---- .../props-not-bindable-spread/_config.js | 13 ---------- .../props-not-bindable-spread/main.svelte | 7 ------ .../samples/props-not-bindable/Counter.svelte | 5 ---- .../samples/props-not-bindable/_config.js | 13 ---------- .../samples/props-not-bindable/main.svelte | 7 ------ 14 files changed, 11 insertions(+), 124 deletions(-) create mode 100644 .changeset/flat-otters-cheer.md delete mode 100644 packages/svelte/tests/runtime-runes/samples/export-binding/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/export-binding/counter/index.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/export-binding/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/Counter.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/props-not-bindable/Counter.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/props-not-bindable/main.svelte diff --git a/.changeset/flat-otters-cheer.md b/.changeset/flat-otters-cheer.md new file mode 100644 index 000000000000..3f84de824247 --- /dev/null +++ b/.changeset/flat-otters-cheer.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: remove bindable prop validation diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 90901d29ce7d..a969117ed353 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -299,28 +299,6 @@ export function client_component(analysis, options) { (binding.kind === 'prop' || binding.kind === 'bindable_prop') && !name.startsWith('$$') ); - if (dev && analysis.runes) { - const exports = analysis.exports.map(({ name, alias }) => b.literal(alias ?? name)); - /** @type {ESTree.Literal[]} */ - const bindable = []; - for (const [name, binding] of properties) { - if (binding.kind === 'bindable_prop') { - bindable.push(b.literal(binding.prop_alias ?? name)); - } - } - instance.body.unshift( - b.stmt( - b.call( - '$.validate_prop_bindings', - b.id('$$props'), - b.array(bindable), - b.array(exports), - b.id(`${analysis.name}`) - ) - ) - ); - } - if (analysis.accessors) { for (const [name, binding] of properties) { const key = binding.prop_alias ?? name; diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 3b85ae18166e..2bf58c51f75d 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -150,7 +150,7 @@ export { setContext, hasContext } from './runtime.js'; -export { validate_binding, validate_each_keys, validate_prop_bindings } from './validate.js'; +export { validate_binding, validate_each_keys } from './validate.js'; export { raf } from './timing.js'; export { proxy } from './proxy.js'; export { create_custom_element } from './dom/elements/custom-element.js'; diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 8fb13f7e2cd0..3e5a0258c744 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -297,8 +297,10 @@ export function prop(props, key, flags, fallback) { var is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props; var setter = - get_descriptor(props, key)?.set ?? - (is_entry_props && bindable && key in props ? (v) => (props[key] = v) : undefined); + (bindable && + (get_descriptor(props, key)?.set ?? + (is_entry_props && key in props && ((v) => (props[key] = v))))) || + undefined; var fallback_value = /** @type {V} */ (fallback); var fallback_dirty = true; diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 417c145eb2ec..24e280edf88b 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -1,5 +1,5 @@ import { dev_current_component_function } from './runtime.js'; -import { get_descriptor, is_array } from '../shared/utils.js'; +import { is_array } from '../shared/utils.js'; import * as e from './errors.js'; import { FILENAME } from '../../constants.js'; import { render_effect } from './reactivity/effects.js'; @@ -38,29 +38,6 @@ export function validate_each_keys(collection, key_fn) { }); } -/** - * @param {Record} $$props - * @param {string[]} bindable - * @param {string[]} exports - * @param {Function & { [FILENAME]: string }} component - */ -export function validate_prop_bindings($$props, bindable, exports, component) { - for (const key in $$props) { - var setter = get_descriptor($$props, key)?.set; - var name = component.name; - - if (setter) { - if (exports.includes(key) && !bindable.includes(key)) { - e.bind_invalid_export(component[FILENAME], key, name); - } - - if (!bindable.includes(key)) { - e.bind_not_bindable(key, component[FILENAME], name); - } - } - } -} - /** * @param {string} binding * @param {() => Record} get_object diff --git a/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js b/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js deleted file mode 100644 index d3b9c8e4eb69..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js +++ /dev/null @@ -1,10 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true // to ensure we we catch the error - }, - error: - 'bind_invalid_export\n' + - 'Component counter/index.svelte has an export named `increment` that a consumer component is trying to access using `bind:increment`, which is disallowed. Instead, use `bind:this` (e.g. ``) and then access the property on the bound component instance (e.g. `component.increment`)' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/export-binding/counter/index.svelte b/packages/svelte/tests/runtime-runes/samples/export-binding/counter/index.svelte deleted file mode 100644 index 14e0de961b21..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/export-binding/counter/index.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - -{count} diff --git a/packages/svelte/tests/runtime-runes/samples/export-binding/main.svelte b/packages/svelte/tests/runtime-runes/samples/export-binding/main.svelte deleted file mode 100644 index 4ad1684701f9..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/export-binding/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/Counter.svelte deleted file mode 100644 index f22fd6e976dd..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/Counter.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -{rest.count} diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js b/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js deleted file mode 100644 index fa0994c3704a..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js +++ /dev/null @@ -1,13 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - html: '0', - - error: - 'bind_not_bindable\n' + - 'A component is attempting to bind to a non-bindable property `count` belonging to Counter.svelte (i.e. ``). To mark a property as bindable: `let { count = $bindable() } = $props()`' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/main.svelte deleted file mode 100644 index 80242b75c6dd..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/props-not-bindable/Counter.svelte deleted file mode 100644 index 4bc2db3968e7..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/Counter.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -{count} diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js b/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js deleted file mode 100644 index fa0994c3704a..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js +++ /dev/null @@ -1,13 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - html: '0', - - error: - 'bind_not_bindable\n' + - 'A component is attempting to bind to a non-bindable property `count` belonging to Counter.svelte (i.e. ``). To mark a property as bindable: `let { count = $bindable() } = $props()`' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-not-bindable/main.svelte deleted file mode 100644 index 80242b75c6dd..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - From a2565efa37dfbba2e462eb5cdbb6c47424ce98ad Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 8 Jan 2025 19:37:26 +0100 Subject: [PATCH 002/265] docs: tweak "invalid assignment" compiler error message (#14955) * docs: tweak "invalid assignment" compiler error message fixes #14702 * tweak wording --------- Co-authored-by: Rich Harris --- .changeset/sharp-dryers-try.md | 5 +++ .../98-reference/.generated/compile-errors.md | 34 ++++++++++++++++++- .../svelte/messages/compile-errors/script.md | 34 ++++++++++++++++++- packages/svelte/src/compiler/errors.js | 4 +-- .../_config.js | 2 +- .../runes-invalid-each-binding/_config.js | 2 +- .../runes-invalid-each-mutation/_config.js | 2 +- 7 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 .changeset/sharp-dryers-try.md diff --git a/.changeset/sharp-dryers-try.md b/.changeset/sharp-dryers-try.md new file mode 100644 index 000000000000..dcdbd99d0293 --- /dev/null +++ b/.changeset/sharp-dryers-try.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: tweak "invalid assignment" compiler error message diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index c4989200075d..a867cfe88cb9 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -331,7 +331,39 @@ The $ prefix is reserved, and cannot be used for variables and imports ### each_item_invalid_assignment ``` -Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`) +Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`) +``` + +In legacy mode, it was possible to reassign or bind to the each block argument itself: + +```svelte + + +{#each array as entry} + + + + + +{/each} +``` + +This turned out to be buggy and unpredictable, particularly when working with derived values (such as `array.map(...)`), and as such is forbidden in runes mode. You can achieve the same outcome by using the index instead: + +```svelte + + +{#each array as entry, i} + + + + + +{/each} ``` ### effect_invalid_placement diff --git a/packages/svelte/messages/compile-errors/script.md b/packages/svelte/messages/compile-errors/script.md index fa851cec89ea..0aa6fbed90d8 100644 --- a/packages/svelte/messages/compile-errors/script.md +++ b/packages/svelte/messages/compile-errors/script.md @@ -32,7 +32,39 @@ ## each_item_invalid_assignment -> Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`) +> Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`) + +In legacy mode, it was possible to reassign or bind to the each block argument itself: + +```svelte + + +{#each array as entry} + + + + + +{/each} +``` + +This turned out to be buggy and unpredictable, particularly when working with derived values (such as `array.map(...)`), and as such is forbidden in runes mode. You can achieve the same outcome by using the index instead: + +```svelte + + +{#each array as entry, i} + + + + + +{/each} +``` ## effect_invalid_placement diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 870cd9ac093d..a997eeef8d7c 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -151,12 +151,12 @@ export function dollar_prefix_invalid(node) { } /** - * Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`) + * Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`) * @param {null | number | NodeLike} node * @returns {never} */ export function each_item_invalid_assignment(node) { - e(node, "each_item_invalid_assignment", `Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. \`array[i] = value\` instead of \`entry = value\`)\nhttps://svelte.dev/e/each_item_invalid_assignment`); + e(node, "each_item_invalid_assignment", `Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. \`array[i] = value\` instead of \`entry = value\`, or \`bind:value={array[i]}\` instead of \`bind:value={entry}\`)\nhttps://svelte.dev/e/each_item_invalid_assignment`); } /** diff --git a/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-binding-this/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-binding-this/_config.js index 524c2f161c27..ed02e0960d39 100644 --- a/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-binding-this/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-binding-this/_config.js @@ -4,6 +4,6 @@ export default test({ error: { code: 'each_item_invalid_assignment', message: - 'Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)' + 'Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)' } }); diff --git a/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-binding/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-binding/_config.js index 524c2f161c27..ed02e0960d39 100644 --- a/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-binding/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-binding/_config.js @@ -4,6 +4,6 @@ export default test({ error: { code: 'each_item_invalid_assignment', message: - 'Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)' + 'Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)' } }); diff --git a/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-mutation/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-mutation/_config.js index 524c2f161c27..ed02e0960d39 100644 --- a/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-mutation/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/runes-invalid-each-mutation/_config.js @@ -4,6 +4,6 @@ export default test({ error: { code: 'each_item_invalid_assignment', message: - 'Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)' + 'Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)' } }); From 9aae463ae1be27a75e876a7a6f6bcfe5b023c19f Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:03:04 +0100 Subject: [PATCH 003/265] fix: silence false-positive statel value warning (#14958) fixes #14687 --- .changeset/tough-guests-sniff.md | 5 ++++ .../client/visitors/AssignmentExpression.js | 1 + .../_config.js | 24 ++++++++++++++----- .../main.svelte | 4 +++- 4 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 .changeset/tough-guests-sniff.md diff --git a/.changeset/tough-guests-sniff.md b/.changeset/tough-guests-sniff.md new file mode 100644 index 000000000000..fcb7b67c1a0a --- /dev/null +++ b/.changeset/tough-guests-sniff.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: silence false-positive stale value warning diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js index 3d8b9aaa968e..0c70f7e00cda 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js @@ -171,6 +171,7 @@ function build_assignment(operator, left, right, context) { // special case — ignore `bind:prop={getter, (v) => (...)}` / `bind:value={x.y}` if ( + path.at(-1) === 'BindDirective' || path.at(-1) === 'Component' || path.at(-1) === 'SvelteComponent' || (path.at(-1) === 'ArrowFunctionExpression' && diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js index ad7bdc654a0b..f0292274725e 100644 --- a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js @@ -1,21 +1,33 @@ import { flushSync } from 'svelte'; -import { test } from '../../test'; +import { ok, test } from '../../test'; export default test({ compileOptions: { dev: true }, - html: `
x
`, + html: `
x
`, test({ assert, target, warnings }) { const btn = target.querySelector('button'); + ok(btn); - flushSync(() => btn?.click()); - assert.htmlEqual(target.innerHTML, `
x
`); + flushSync(() => btn.click()); + assert.htmlEqual( + target.innerHTML, + `
x
` + ); - flushSync(() => btn?.click()); - assert.htmlEqual(target.innerHTML, `
x
`); + flushSync(() => btn.click()); + assert.htmlEqual( + target.innerHTML, + `
x
` + ); + + const input = target.querySelector('input'); + ok(input); + input.checked = true; + flushSync(() => input.dispatchEvent(new Event('change', { bubbles: true }))); assert.deepEqual(warnings, [ 'Assignment to `items` property (main.svelte:8:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.' diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte index 40592e08b8ca..ad94c4e56e03 100644 --- a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte @@ -2,7 +2,7 @@ import Test from './Test.svelte'; let entries = $state([]); - let object = $state({ items: null }); + let object = $state({ items: null, group: [] }); \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/_config.js b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/_config.js new file mode 100644 index 000000000000..4c77aea20684 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/_config.js @@ -0,0 +1,20 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + test({ target, warnings, assert }) { + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.deepEqual(warnings, []); + + flushSync(() => { + btn?.click(); + }); + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/main.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/main.svelte new file mode 100644 index 000000000000..4a3ce82726b6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/main.svelte @@ -0,0 +1,10 @@ + + + len % 2 === 0 ? arr : arr2, (v) => {}} /> \ No newline at end of file From b7400ae93b21f74030bd80a58f3d1e23edd1a5cf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 08:41:06 -0500 Subject: [PATCH 011/265] Version Packages (#14959) Co-authored-by: github-actions[bot] --- .changeset/long-weeks-brush.md | 5 ----- .changeset/sixty-paws-compete.md | 5 ----- .changeset/yellow-dodos-smell.md | 5 ----- packages/svelte/CHANGELOG.md | 10 ++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 6 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 .changeset/long-weeks-brush.md delete mode 100644 .changeset/sixty-paws-compete.md delete mode 100644 .changeset/yellow-dodos-smell.md diff --git a/.changeset/long-weeks-brush.md b/.changeset/long-weeks-brush.md deleted file mode 100644 index b0e184caa04d..000000000000 --- a/.changeset/long-weeks-brush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: account for parent scale when animating elements diff --git a/.changeset/sixty-paws-compete.md b/.changeset/sixty-paws-compete.md deleted file mode 100644 index 2fa3f9e84fb7..000000000000 --- a/.changeset/sixty-paws-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: apply `overflow: hidden` style when transitioning elements, where necessary diff --git a/.changeset/yellow-dodos-smell.md b/.changeset/yellow-dodos-smell.md deleted file mode 100644 index ea2aead6622e..000000000000 --- a/.changeset/yellow-dodos-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: properly add owners to function bindings diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 72fa09bf964e..6226fa851d9b 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.17.2 + +### Patch Changes + +- fix: account for parent scale when animating elements ([#14957](https://github.com/sveltejs/svelte/pull/14957)) + +- fix: apply `overflow: hidden` style when transitioning elements, where necessary ([#14930](https://github.com/sveltejs/svelte/pull/14930)) + +- fix: properly add owners to function bindings ([#14962](https://github.com/sveltejs/svelte/pull/14962)) + ## 5.17.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index ce6e4ffc42b3..3d5e350af0b3 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.17.1", + "version": "5.17.2", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index bb772687fbfb..99bf9408542c 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.17.1'; +export const VERSION = '5.17.2'; export const PUBLIC_VERSION = '5'; From bdc02008e4f8012b439d4ee1c05be0cdbf4dad80 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 9 Jan 2025 16:26:27 +0000 Subject: [PATCH 012/265] fix: reset dependency read versions after reaction execution (#14964) * fix: reset dependency read versions after reaction execution * fix: reset dependency read versions after reaction execution * fix: reset dependency read versions after reaction execution * fix: reset dependency read versions after reaction execution * fix: reset dependency read versions after reaction execution * chore: add test * changeset --------- Co-authored-by: paoloricciuti Co-authored-by: Rich Harris --- .changeset/fifty-chefs-invent.md | 5 +++++ .../svelte/src/internal/client/runtime.js | 8 ++++++++ .../Component.svelte | 7 +++++++ .../read-version-previous-reaction/_config.js | 19 +++++++++++++++++++ .../main.svelte | 18 ++++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 .changeset/fifty-chefs-invent.md create mode 100644 packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/main.svelte diff --git a/.changeset/fifty-chefs-invent.md b/.changeset/fifty-chefs-invent.md new file mode 100644 index 000000000000..eb151f67e2d7 --- /dev/null +++ b/.changeset/fifty-chefs-invent.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: reset dependency read versions after reaction execution diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 202608ce06bd..3c8879eb317b 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -434,6 +434,14 @@ export function update_reaction(reaction) { deps.length = skipped_deps; } + // If we are returning to an previous reaction then + // we need to increment the read version to ensure that + // any dependencies in this reaction aren't marked with + // the same version + if (previous_reaction !== null) { + read_version++; + } + return result; } finally { new_deps = previous_deps; diff --git a/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/Component.svelte b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/Component.svelte new file mode 100644 index 000000000000..afb62ced2f4b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/Component.svelte @@ -0,0 +1,7 @@ + + +

{label}

diff --git a/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/_config.js b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/_config.js new file mode 100644 index 000000000000..650e48e84ccd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/_config.js @@ -0,0 +1,19 @@ +import { ok, test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + html: `

0

`, + + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '1'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '2'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/main.svelte b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/main.svelte new file mode 100644 index 000000000000..773aabeea30b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/main.svelte @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file From dc8ae825b8c3fb8c286d54116af8d11244343974 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:36:14 -0500 Subject: [PATCH 013/265] Version Packages (#14965) Co-authored-by: github-actions[bot] --- .changeset/fifty-chefs-invent.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/fifty-chefs-invent.md diff --git a/.changeset/fifty-chefs-invent.md b/.changeset/fifty-chefs-invent.md deleted file mode 100644 index eb151f67e2d7..000000000000 --- a/.changeset/fifty-chefs-invent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: reset dependency read versions after reaction execution diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 6226fa851d9b..6023c6d4e185 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.17.3 + +### Patch Changes + +- fix: reset dependency read versions after reaction execution ([#14964](https://github.com/sveltejs/svelte/pull/14964)) + ## 5.17.2 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 3d5e350af0b3..459786330bd5 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.17.2", + "version": "5.17.3", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 99bf9408542c..0fd7d2f0397e 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.17.2'; +export const VERSION = '5.17.3'; export const PUBLIC_VERSION = '5'; From dbe5818560241d5a1b221b6e160298da48aebbb9 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 10 Jan 2025 01:14:52 +0000 Subject: [PATCH 014/265] fix: wrap each block expression in derived to encapsulte effects (#14967) * fix: wrap each block expression in derived to encapsulte effects * add test * Update .changeset/tender-apples-scream.md --------- Co-authored-by: Rich Harris --- .changeset/tender-apples-scream.md | 5 ++ .../src/internal/client/dom/blocks/each.js | 19 ++++---- .../samples/each-updates-9/_config.js | 16 +++++++ .../samples/each-updates-9/main.svelte | 46 +++++++++++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 .changeset/tender-apples-scream.md create mode 100644 packages/svelte/tests/runtime-runes/samples/each-updates-9/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/each-updates-9/main.svelte diff --git a/.changeset/tender-apples-scream.md b/.changeset/tender-apples-scream.md new file mode 100644 index 000000000000..836bdaffdf78 --- /dev/null +++ b/.changeset/tender-apples-scream.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: wrap each block expression in derived to encapsulate effects diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 9e6405594059..b17090948ae7 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -35,8 +35,9 @@ import { source, mutable_source, internal_set } from '../../reactivity/sources.j import { array_from, is_array } from '../../../shared/utils.js'; import { INERT } from '../../constants.js'; import { queue_micro_task } from '../task.js'; -import { active_effect, active_reaction } from '../../runtime.js'; +import { active_effect, active_reaction, get } from '../../runtime.js'; import { DEV } from 'esm-env'; +import { derived_safe_equal } from '../../reactivity/deriveds.js'; /** * The row of a keyed each block that is currently updating. We track this @@ -135,15 +136,17 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f var was_empty = false; - block(() => { + // TODO: ideally we could use derived for runes mode but because of the ability + // to use a store which can be mutated, we can't do that here as mutating a store + // will still result in the collection array being the same from the store + var each_array = derived_safe_equal(() => { var collection = get_collection(); - var array = is_array(collection) - ? collection - : collection == null - ? [] - : array_from(collection); + return is_array(collection) ? collection : collection == null ? [] : array_from(collection); + }); + block(() => { + var array = get(each_array); var length = array.length; if (was_empty && length === 0) { @@ -254,7 +257,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f // that a mutation occurred and it's made the collection MAYBE_DIRTY, so reading the // collection again can provide consistency to the reactive graph again as the deriveds // will now be `CLEAN`. - get_collection(); + get(each_array); }); if (hydrating) { diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates-9/_config.js b/packages/svelte/tests/runtime-runes/samples/each-updates-9/_config.js new file mode 100644 index 000000000000..ee35058c59bb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-updates-9/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [btn1] = target.querySelectorAll('button'); + + btn1.click(); + flushSync(); + + await Promise.resolve(); + await Promise.resolve(); + + assert.deepEqual(logs, ['cleanup']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates-9/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-updates-9/main.svelte new file mode 100644 index 000000000000..f5b2c8eb1282 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-updates-9/main.svelte @@ -0,0 +1,46 @@ + + + + +{#each myStore.data as _}{/each} From 41fb51349e5846bdb406baba555d02cbf4f55b8e Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Fri, 10 Jan 2025 11:28:18 +0100 Subject: [PATCH 015/265] fix: store access on component destroy (#14968) Co-authored-by: Oscar Dominguez --- .changeset/hot-kings-shout.md | 5 +++ .../3-transform/client/transform-client.js | 31 +++++++++++++--- .../src/internal/client/reactivity/store.js | 37 ++++++++++++++----- .../store-update-on-destroy/Test.svelte | 12 ++++++ .../store-update-on-destroy/_config.js | 12 ++++++ .../store-update-on-destroy/main.svelte | 15 ++++++++ 6 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 .changeset/hot-kings-shout.md create mode 100644 packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/Test.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/main.svelte diff --git a/.changeset/hot-kings-shout.md b/.changeset/hot-kings-shout.md new file mode 100644 index 000000000000..afba164abf98 --- /dev/null +++ b/.changeset/hot-kings-shout.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: store access on component destroy diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index a969117ed353..582c32b534ec 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -214,6 +214,8 @@ export function client_component(analysis, options) { /** @type {ESTree.VariableDeclaration[]} */ const legacy_reactive_declarations = []; + let needs_store_cleanup = false; + for (const [name, binding] of analysis.instance.scope.declarations) { if (binding.kind === 'legacy_reactive') { legacy_reactive_declarations.push( @@ -222,7 +224,10 @@ export function client_component(analysis, options) { } if (binding.kind === 'store_sub') { if (store_setup.length === 0) { - store_setup.push(b.const('$$stores', b.call('$.setup_stores'))); + needs_store_cleanup = true; + store_setup.push( + b.const(b.array_pattern([b.id('$$stores'), b.id('$$cleanup')]), b.call('$.setup_stores')) + ); } // We're creating an arrow function that gets the store value which minifies better for two or more references @@ -391,14 +396,28 @@ export function client_component(analysis, options) { analysis.reactive_statements.size > 0 || component_returned_object.length > 0; + // we want the cleanup function for the stores to run as the very last thing + // so that it can effectively clean up the store subscription even after the user effects runs if (should_inject_context) { component_block.body.unshift(b.stmt(b.call('$.push', ...push_args))); - component_block.body.push( - component_returned_object.length > 0 - ? b.return(b.call('$.pop', b.object(component_returned_object))) - : b.stmt(b.call('$.pop')) - ); + let to_push; + + if (component_returned_object.length > 0) { + let pop_call = b.call('$.pop', b.object(component_returned_object)); + to_push = needs_store_cleanup ? b.var('$$pop', pop_call) : b.return(pop_call); + } else { + to_push = b.stmt(b.call('$.pop')); + } + + component_block.body.push(to_push); + } + + if (needs_store_cleanup) { + component_block.body.push(b.stmt(b.call('$$cleanup'))); + if (component_returned_object.length > 0) { + component_block.body.push(b.return(b.id('$$pop'))); + } } if (analysis.uses_rest_props) { diff --git a/packages/svelte/src/internal/client/reactivity/store.js b/packages/svelte/src/internal/client/reactivity/store.js index 11eee23e0ae8..e7a92ee052e7 100644 --- a/packages/svelte/src/internal/client/reactivity/store.js +++ b/packages/svelte/src/internal/client/reactivity/store.js @@ -1,7 +1,8 @@ /** @import { StoreReferencesContainer } from '#client' */ /** @import { Store } from '#shared' */ import { subscribe_to_store } from '../../../store/utils.js'; -import { noop } from '../../shared/utils.js'; +import { get as get_store } from '../../../store/shared/index.js'; +import { define_property, noop } from '../../shared/utils.js'; import { get } from '../runtime.js'; import { teardown } from './effects.js'; import { mutable_source, set } from './sources.js'; @@ -13,6 +14,8 @@ import { mutable_source, set } from './sources.js'; */ let is_store_binding = false; +let IS_UNMOUNTED = Symbol(); + /** * Gets the current value of a store. If the store isn't subscribed to yet, it will create a proxy * signal that will be updated when the store is. The store references container is needed to @@ -30,7 +33,8 @@ export function store_get(store, store_name, stores) { unsubscribe: noop }); - if (entry.store !== store) { + // if the component that setup this is already unmounted we don't want to register a subscription + if (entry.store !== store && !(IS_UNMOUNTED in stores)) { entry.unsubscribe(); entry.store = store ?? null; @@ -54,6 +58,13 @@ export function store_get(store, store_name, stores) { } } + // if the component that setup this stores is already unmounted the source will be out of sync + // so we just use the `get` for the stores, less performant but it avoids to create a memory leak + // and it will keep the value consistent + if (store && IS_UNMOUNTED in stores) { + return get_store(store); + } + return get(entry.source); } @@ -103,20 +114,26 @@ export function invalidate_store(stores, store_name) { /** * Unsubscribes from all auto-subscribed stores on destroy - * @returns {StoreReferencesContainer} + * @returns {[StoreReferencesContainer, ()=>void]} */ export function setup_stores() { /** @type {StoreReferencesContainer} */ const stores = {}; - teardown(() => { - for (var store_name in stores) { - const ref = stores[store_name]; - ref.unsubscribe(); - } - }); + function cleanup() { + teardown(() => { + for (var store_name in stores) { + const ref = stores[store_name]; + ref.unsubscribe(); + } + define_property(stores, IS_UNMOUNTED, { + enumerable: false, + value: true + }); + }); + } - return stores; + return [stores, cleanup]; } /** diff --git a/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/Test.svelte b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/Test.svelte new file mode 100644 index 000000000000..364a4a7acac0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/Test.svelte @@ -0,0 +1,12 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/_config.js b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/_config.js new file mode 100644 index 000000000000..bb999887564b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/_config.js @@ -0,0 +1,12 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const input = target.querySelector('input'); + flushSync(() => { + input?.click(); + }); + assert.deepEqual(logs, [0, 1]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/main.svelte b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/main.svelte new file mode 100644 index 000000000000..7ba59b5afc09 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/main.svelte @@ -0,0 +1,15 @@ + + + + +{#if checked} + +{/if} From 9b6e65fbeb61297978a16e8f711ebe2dc97f2b10 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Fri, 10 Jan 2025 14:36:26 +0100 Subject: [PATCH 016/265] fix: correctly transform `pre` with no content (#14973) Closes #14971 --- .changeset/spicy-insects-check.md | 5 +++++ packages/svelte/src/compiler/phases/3-transform/utils.js | 2 +- .../tests/runtime-runes/samples/pre-no-content/_config.js | 5 +++++ .../tests/runtime-runes/samples/pre-no-content/main.svelte | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .changeset/spicy-insects-check.md create mode 100644 packages/svelte/tests/runtime-runes/samples/pre-no-content/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/pre-no-content/main.svelte diff --git a/.changeset/spicy-insects-check.md b/.changeset/spicy-insects-check.md new file mode 100644 index 000000000000..b998d36400ac --- /dev/null +++ b/.changeset/spicy-insects-check.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly transform `pre` with no content diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index 62a635de3536..46872fbfcfb8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -272,7 +272,7 @@ export function clean_nodes( var first = trimmed[0]; // initial newline inside a `
` is disregarded, if not followed by another newline
-	if (parent.type === 'RegularElement' && parent.name === 'pre' && first.type === 'Text') {
+	if (parent.type === 'RegularElement' && parent.name === 'pre' && first?.type === 'Text') {
 		const text = first.data.replace(regex_starts_with_newline, '');
 		if (text !== first.data) {
 			const tmp = text.replace(regex_starts_with_newline, '');
diff --git a/packages/svelte/tests/runtime-runes/samples/pre-no-content/_config.js b/packages/svelte/tests/runtime-runes/samples/pre-no-content/_config.js
new file mode 100644
index 000000000000..cb9d31a69f4f
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/pre-no-content/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+	html: `
`
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/pre-no-content/main.svelte b/packages/svelte/tests/runtime-runes/samples/pre-no-content/main.svelte
new file mode 100644
index 000000000000..a4357066a55f
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/pre-no-content/main.svelte
@@ -0,0 +1 @@
+

\ No newline at end of file

From 76ce303b0aa08e76cefc5d13f1fe21345caa0ce9 Mon Sep 17 00:00:00 2001
From: Rich Harris 
Date: Fri, 10 Jan 2025 12:05:23 -0500
Subject: [PATCH 017/265] chore: bump esrap (#14969)

* chore: bump esrap

* fix

* bump again
---
 packages/svelte/package.json                  |   2 +-
 packages/svelte/src/compiler/errors.js        | 330 +++++++++---------
 packages/svelte/src/compiler/warnings.js      | 312 ++++++++---------
 .../svelte/src/internal/client/warnings.js    |   6 +-
 .../svelte/src/internal/shared/warnings.js    |   2 +-
 .../_expected/client/index.svelte.js          |  10 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   8 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   6 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   4 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   2 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/main.svelte.js           |  16 +-
 .../_expected/server/main.svelte.js           |   4 +-
 .../_expected/client/index.svelte.js          |   8 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   2 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   6 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   6 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../hmr/_expected/client/index.svelte.js      |   6 +-
 .../hmr/_expected/server/index.svelte.js      |   2 +-
 .../_expected/client/index.svelte.js          |   6 +-
 .../_expected/client/module.svelte.js         |   2 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/server/module.svelte.js         |   2 +-
 .../_expected/client/index.svelte.js          |   6 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../purity/_expected/client/index.svelte.js   |   6 +-
 .../purity/_expected/server/index.svelte.js   |   2 +-
 .../_expected/client/index.svelte.js          |   8 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   6 +-
 .../_expected/server/index.svelte.js          |   4 +-
 .../_expected/client/index.svelte.js          |   6 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   8 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../samples/css-injected-map/_config.js       |   2 +-
 pnpm-lock.yaml                                |  10 +-
 45 files changed, 414 insertions(+), 414 deletions(-)

diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 459786330bd5..47aec9ee9388 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -155,7 +155,7 @@
     "axobject-query": "^4.1.0",
     "clsx": "^2.1.1",
     "esm-env": "^1.2.1",
-    "esrap": "^1.3.2",
+    "esrap": "^1.4.2",
     "is-reference": "^3.0.3",
     "locate-character": "^3.0.0",
     "magic-string": "^0.30.11",
diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js
index a997eeef8d7c..da038af4306d 100644
--- a/packages/svelte/src/compiler/errors.js
+++ b/packages/svelte/src/compiler/errors.js
@@ -52,7 +52,7 @@ function e(node, code, message) {
  * @returns {never}
  */
 export function options_invalid_value(node, details) {
-	e(node, "options_invalid_value", `Invalid compiler option: ${details}\nhttps://svelte.dev/e/options_invalid_value`);
+	e(node, 'options_invalid_value', `Invalid compiler option: ${details}\nhttps://svelte.dev/e/options_invalid_value`);
 }
 
 /**
@@ -62,7 +62,7 @@ export function options_invalid_value(node, details) {
  * @returns {never}
  */
 export function options_removed(node, details) {
-	e(node, "options_removed", `Invalid compiler option: ${details}\nhttps://svelte.dev/e/options_removed`);
+	e(node, 'options_removed', `Invalid compiler option: ${details}\nhttps://svelte.dev/e/options_removed`);
 }
 
 /**
@@ -72,7 +72,7 @@ export function options_removed(node, details) {
  * @returns {never}
  */
 export function options_unrecognised(node, keypath) {
-	e(node, "options_unrecognised", `Unrecognised compiler option ${keypath}\nhttps://svelte.dev/e/options_unrecognised`);
+	e(node, 'options_unrecognised', `Unrecognised compiler option ${keypath}\nhttps://svelte.dev/e/options_unrecognised`);
 }
 
 /**
@@ -81,7 +81,7 @@ export function options_unrecognised(node, keypath) {
  * @returns {never}
  */
 export function bindable_invalid_location(node) {
-	e(node, "bindable_invalid_location", `\`$bindable()\` can only be used inside a \`$props()\` declaration\nhttps://svelte.dev/e/bindable_invalid_location`);
+	e(node, 'bindable_invalid_location', `\`$bindable()\` can only be used inside a \`$props()\` declaration\nhttps://svelte.dev/e/bindable_invalid_location`);
 }
 
 /**
@@ -91,7 +91,7 @@ export function bindable_invalid_location(node) {
  * @returns {never}
  */
 export function constant_assignment(node, thing) {
-	e(node, "constant_assignment", `Cannot assign to ${thing}\nhttps://svelte.dev/e/constant_assignment`);
+	e(node, 'constant_assignment', `Cannot assign to ${thing}\nhttps://svelte.dev/e/constant_assignment`);
 }
 
 /**
@@ -101,7 +101,7 @@ export function constant_assignment(node, thing) {
  * @returns {never}
  */
 export function constant_binding(node, thing) {
-	e(node, "constant_binding", `Cannot bind to ${thing}\nhttps://svelte.dev/e/constant_binding`);
+	e(node, 'constant_binding', `Cannot bind to ${thing}\nhttps://svelte.dev/e/constant_binding`);
 }
 
 /**
@@ -111,7 +111,7 @@ export function constant_binding(node, thing) {
  * @returns {never}
  */
 export function declaration_duplicate(node, name) {
-	e(node, "declaration_duplicate", `\`${name}\` has already been declared\nhttps://svelte.dev/e/declaration_duplicate`);
+	e(node, 'declaration_duplicate', `\`${name}\` has already been declared\nhttps://svelte.dev/e/declaration_duplicate`);
 }
 
 /**
@@ -120,7 +120,7 @@ export function declaration_duplicate(node, name) {
  * @returns {never}
  */
 export function declaration_duplicate_module_import(node) {
-	e(node, "declaration_duplicate_module_import", `Cannot declare a variable with the same name as an import inside \`
diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-21/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/_config.js
new file mode 100644
index 000000000000..e301f83e6024
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/_config.js
@@ -0,0 +1,17 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+	html: '
0
', + mode: ['client'], + test({ assert, target }) { + let btn = target.querySelector('button'); + let div = target.querySelector('div'); + + flushSync(() => { + btn?.click(); + }); + + assert.equal(div?.innerHTML, `1`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-21/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/main.svelte new file mode 100644 index 000000000000..ed3140b1efb6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/main.svelte @@ -0,0 +1,14 @@ + + + + + + + {#snippet failed()} +
{count}
+ {/snippet} +
From efa5acf24e040160172550eb5f7fa7ca9390f2e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:18:43 -0500 Subject: [PATCH 021/265] Version Packages (#14970) Co-authored-by: github-actions[bot] --- .changeset/healthy-hairs-run.md | 5 ----- .changeset/hot-kings-shout.md | 5 ----- .changeset/spicy-insects-check.md | 5 ----- .changeset/tender-apples-scream.md | 5 ----- packages/svelte/CHANGELOG.md | 12 ++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 7 files changed, 14 insertions(+), 22 deletions(-) delete mode 100644 .changeset/healthy-hairs-run.md delete mode 100644 .changeset/hot-kings-shout.md delete mode 100644 .changeset/spicy-insects-check.md delete mode 100644 .changeset/tender-apples-scream.md diff --git a/.changeset/healthy-hairs-run.md b/.changeset/healthy-hairs-run.md deleted file mode 100644 index 5f0ccd5234fd..000000000000 --- a/.changeset/healthy-hairs-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: never consider inert boundary effects diff --git a/.changeset/hot-kings-shout.md b/.changeset/hot-kings-shout.md deleted file mode 100644 index afba164abf98..000000000000 --- a/.changeset/hot-kings-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: store access on component destroy diff --git a/.changeset/spicy-insects-check.md b/.changeset/spicy-insects-check.md deleted file mode 100644 index b998d36400ac..000000000000 --- a/.changeset/spicy-insects-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly transform `pre` with no content diff --git a/.changeset/tender-apples-scream.md b/.changeset/tender-apples-scream.md deleted file mode 100644 index 836bdaffdf78..000000000000 --- a/.changeset/tender-apples-scream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: wrap each block expression in derived to encapsulate effects diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 6023c6d4e185..4f3b9a168a79 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.17.4 + +### Patch Changes + +- fix: never consider inert boundary effects ([#14999](https://github.com/sveltejs/svelte/pull/14999)) + +- fix: store access on component destroy ([#14968](https://github.com/sveltejs/svelte/pull/14968)) + +- fix: correctly transform `pre` with no content ([#14973](https://github.com/sveltejs/svelte/pull/14973)) + +- fix: wrap each block expression in derived to encapsulate effects ([#14967](https://github.com/sveltejs/svelte/pull/14967)) + ## 5.17.3 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 47aec9ee9388..f426b97be4aa 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.17.3", + "version": "5.17.4", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 0fd7d2f0397e..69a883393828 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.17.3'; +export const VERSION = '5.17.4'; export const PUBLIC_VERSION = '5'; From a129592e5b248a734a68da6e9028941803a3d063 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 14 Jan 2025 01:36:33 +0100 Subject: [PATCH 022/265] feat: allow const tag inside `svelte:boundary` (#14993) * feat: allow const tag inside `svelte:boundary` * chore: add better test * docs: update docs for `@const` --- .changeset/curvy-lies-rush.md | 5 +++++ .../docs/03-template-syntax/09-@const.md | 2 +- .../phases/2-analyze/visitors/ConstTag.js | 1 + .../client/visitors/SvelteBoundary.js | 21 +++++++++++++++---- .../const-tag-boundary/FlakyComponent.svelte | 3 +++ .../samples/const-tag-boundary/_config.js | 17 +++++++++++++++ .../samples/const-tag-boundary/main.svelte | 14 +++++++++++++ .../errors.json | 1 + .../input.svelte | 11 ++++++++++ 9 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 .changeset/curvy-lies-rush.md create mode 100644 packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte create mode 100644 packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json create mode 100644 packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte diff --git a/.changeset/curvy-lies-rush.md b/.changeset/curvy-lies-rush.md new file mode 100644 index 000000000000..25c6ffc1d91b --- /dev/null +++ b/.changeset/curvy-lies-rush.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: allow const tag inside `svelte:boundary` diff --git a/documentation/docs/03-template-syntax/09-@const.md b/documentation/docs/03-template-syntax/09-@const.md index f4bde77c23e8..c42d3560fd0e 100644 --- a/documentation/docs/03-template-syntax/09-@const.md +++ b/documentation/docs/03-template-syntax/09-@const.md @@ -11,4 +11,4 @@ The `{@const ...}` tag defines a local constant. {/each} ``` -`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — or a ``. +`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `` or a ` a.type === 'Attribute' && a.name === 'slot'))) ) { 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 ef9f6bd798b1..325485d4c003 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 @@ -33,21 +33,34 @@ export function SvelteBoundary(node, context) { const nodes = []; /** @type {Statement[]} */ - const snippet_statements = []; + const external_statements = []; + + const snippets_visits = []; // Capture the `failed` implicit snippet prop for (const child of node.fragment.nodes) { if (child.type === 'SnippetBlock' && child.expression.name === 'failed') { + // we need to delay the visit of the snippets in case they access a ConstTag that is declared + // after the snippets so that the visitor for the const tag can be updated + snippets_visits.push(() => { + /** @type {Statement[]} */ + const init = []; + context.visit(child, { ...context.state, init }); + props.properties.push(b.prop('init', child.expression, child.expression)); + external_statements.push(...init); + }); + } else if (child.type === 'ConstTag') { /** @type {Statement[]} */ const init = []; context.visit(child, { ...context.state, init }); - props.properties.push(b.prop('init', child.expression, child.expression)); - snippet_statements.push(...init); + external_statements.push(...init); } else { nodes.push(child); } } + snippets_visits.forEach((visit) => visit()); + const block = /** @type {BlockStatement} */ (context.visit({ ...node.fragment, nodes })); const boundary = b.stmt( @@ -56,6 +69,6 @@ export function SvelteBoundary(node, context) { context.state.template.push(''); context.state.init.push( - snippet_statements.length > 0 ? b.block([...snippet_statements, boundary]) : boundary + external_statements.length > 0 ? b.block([...external_statements, boundary]) : boundary ); } diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte new file mode 100644 index 000000000000..8bbec90de4ea --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js new file mode 100644 index 000000000000..4338969a48e0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: '

2

', + mode: ['client'], + test({ target, assert }) { + const btn = target.querySelector('button'); + const p = target.querySelector('p'); + + flushSync(() => { + btn?.click(); + }); + + assert.equal(p?.innerHTML, '4'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte new file mode 100644 index 000000000000..25ea8a3ffc59 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte @@ -0,0 +1,14 @@ + + + + + + {@const double = test * 2} + {#snippet failed()} +

{double}

+ {/snippet} + +
\ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte new file mode 100644 index 000000000000..5708cc36ca00 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte @@ -0,0 +1,11 @@ + + + + {@const x = a} + {#snippet failed()} + {x} + {/snippet} + + \ No newline at end of file From 99fdc3f0ab2043af1c6cfc23f6aa886d047ddb0d Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 14 Jan 2025 10:51:57 +0100 Subject: [PATCH 023/265] docs: update error message for `const_tag_invalid_placement` (#15003) --- documentation/docs/98-reference/.generated/compile-errors.md | 2 +- packages/svelte/messages/compile-errors/template.md | 2 +- packages/svelte/src/compiler/errors.js | 4 ++-- .../tests/validator/samples/const-tag-placement-1/errors.json | 2 +- .../tests/validator/samples/const-tag-placement-2/errors.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index a867cfe88cb9..2fef3bd45d50 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -187,7 +187,7 @@ Cyclical dependency detected: %cycle% ### const_tag_invalid_placement ``` -`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `` or `` +`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, ``, `` ``` ### constant_assignment diff --git a/packages/svelte/messages/compile-errors/template.md b/packages/svelte/messages/compile-errors/template.md index 287178ef8799..d061416dbd1a 100644 --- a/packages/svelte/messages/compile-errors/template.md +++ b/packages/svelte/messages/compile-errors/template.md @@ -118,7 +118,7 @@ ## const_tag_invalid_placement -> `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `` or `` +> `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, ``, `` ## debug_tag_invalid_arguments diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index da038af4306d..53a6ac6849ec 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -888,12 +888,12 @@ export function const_tag_invalid_expression(node) { } /** - * `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `` or `` + * `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, ``, `` * @param {null | number | NodeLike} node * @returns {never} */ export function const_tag_invalid_placement(node) { - e(node, 'const_tag_invalid_placement', `\`{@const}\` must be the immediate child of \`{#snippet}\`, \`{#if}\`, \`{:else if}\`, \`{:else}\`, \`{#each}\`, \`{:then}\`, \`{:catch}\`, \`\` or \`\`\nhttps://svelte.dev/e/const_tag_invalid_placement`); + e(node, 'const_tag_invalid_placement', `\`{@const}\` must be the immediate child of \`{#snippet}\`, \`{#if}\`, \`{:else if}\`, \`{:else}\`, \`{#each}\`, \`{:then}\`, \`{:catch}\`, \`\`, \`\`\nhttps://svelte.dev/e/const_tag_invalid_placement`); } /** diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json index 144345527ad7..514e5d056195 100644 --- a/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json @@ -1,7 +1,7 @@ [ { "code": "const_tag_invalid_placement", - "message": "`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `` or ``", + "message": "`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, ``, ``", "start": { "line": 5, "column": 0 diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json index 64a24c5f481a..6b968f7eda01 100644 --- a/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json @@ -1,7 +1,7 @@ [ { "code": "const_tag_invalid_placement", - "message": "`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `` or ``", + "message": "`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, ``, ``", "start": { "line": 7, "column": 4 From dae4c5f5e191927d3f05a156b986b605b6d1e97a Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 14 Jan 2025 15:58:42 +0000 Subject: [PATCH 024/265] fix: ensure signal write invalidation within effects is consistent (#14989) * fix: ensure signal write invalidation within effects is persistent * fix: ensure signal write invalidation within effects is persistent * fix: ensure signal write invalidation within effects is persistent * address feedback --- .changeset/tricky-spiders-collect.md | 5 ++ .../src/internal/client/reactivity/sources.js | 22 +++----- .../svelte/src/internal/client/runtime.js | 55 +++++++++++++++---- packages/svelte/tests/signals/test.ts | 36 ++++++++++++ 4 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 .changeset/tricky-spiders-collect.md diff --git a/.changeset/tricky-spiders-collect.md b/.changeset/tricky-spiders-collect.md new file mode 100644 index 000000000000..0ea8b71e5efc --- /dev/null +++ b/.changeset/tricky-spiders-collect.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure signal write invalidation within effects is consistent diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index e0facf3b55d3..4500a7c5a84a 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -3,7 +3,6 @@ import { DEV } from 'esm-env'; import { component_context, active_reaction, - new_deps, active_effect, untracked_writes, get, @@ -29,7 +28,8 @@ import { INSPECT_EFFECT, UNOWNED, MAYBE_DIRTY, - BLOCK_EFFECT + BLOCK_EFFECT, + ROOT_EFFECT } from '../constants.js'; import * as e from '../errors.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; @@ -182,26 +182,20 @@ export function internal_set(source, value) { mark_reactions(source, DIRTY); - // If the current signal is running for the first time, it won't have any - // reactions as we only allocate and assign the reactions after the signal - // has fully executed. So in the case of ensuring it registers the reaction + // It's possible that the current reaction might not have up-to-date dependencies + // whilst it's actively running. So in the case of ensuring it registers the reaction // properly for itself, we need to ensure the current effect actually gets // scheduled. i.e: `$effect(() => x++)` if ( is_runes() && active_effect !== null && (active_effect.f & CLEAN) !== 0 && - (active_effect.f & BRANCH_EFFECT) === 0 + (active_effect.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ) { - if (new_deps !== null && new_deps.includes(source)) { - set_signal_status(active_effect, DIRTY); - schedule_effect(active_effect); + if (untracked_writes === null) { + set_untracked_writes([source]); } else { - if (untracked_writes === null) { - set_untracked_writes([source]); - } else { - untracked_writes.push(source); - } + untracked_writes.push(source); } } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 3c8879eb317b..eca5ee94f907 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -382,6 +382,34 @@ export function handle_error(error, effect, previous_effect, component_context) } } +/** + * @param {Value} signal + * @param {Effect} effect + * @param {number} [depth] + */ +function schedule_possible_effect_self_invalidation(signal, effect, depth = 0) { + var reactions = signal.reactions; + if (reactions === null) return; + + for (var i = 0; i < reactions.length; i++) { + var reaction = reactions[i]; + if ((reaction.f & DERIVED) !== 0) { + schedule_possible_effect_self_invalidation( + /** @type {Derived} */ (reaction), + effect, + depth + 1 + ); + } else if (effect === reaction) { + if (depth === 0) { + set_signal_status(reaction, DIRTY); + } else if ((reaction.f & CLEAN) !== 0) { + set_signal_status(reaction, MAYBE_DIRTY); + } + schedule_effect(/** @type {Effect} */ (reaction)); + } + } +} + /** * @template V * @param {Reaction} reaction @@ -434,6 +462,22 @@ export function update_reaction(reaction) { deps.length = skipped_deps; } + // If we're inside an effect and we have untracked writes, then we need to + // ensure that if any of those untracked writes result in re-invalidation + // of the current effect, then that happens accordingly + if ( + is_runes() && + untracked_writes !== null && + (reaction.f & (DERIVED | MAYBE_DIRTY | DIRTY)) === 0 + ) { + for (i = 0; i < /** @type {Source[]} */ (untracked_writes).length; i++) { + schedule_possible_effect_self_invalidation( + untracked_writes[i], + /** @type {Effect} */ (reaction) + ); + } + } + // If we are returning to an previous reaction then // we need to increment the read version to ensure that // any dependencies in this reaction aren't marked with @@ -907,17 +951,6 @@ export function get(signal) { } else { new_deps.push(signal); } - - if ( - untracked_writes !== null && - active_effect !== null && - (active_effect.f & CLEAN) !== 0 && - (active_effect.f & BRANCH_EFFECT) === 0 && - untracked_writes.includes(signal) - ) { - set_signal_status(active_effect, DIRTY); - schedule_effect(active_effect); - } } } else if (is_derived && /** @type {Derived} */ (signal).deps === null) { var derived = /** @type {Derived} */ (signal); diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index e198a5a89de7..a9d29920cf84 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -402,6 +402,42 @@ describe('signals', () => { }; }); + test('schedules rerun when writing to signal before reading it from derived', (runes) => { + if (!runes) return () => {}; + let log: any[] = []; + + const value = state(1); + const double = derived(() => $.get(value) * 2); + + user_effect(() => { + set(value, 10); + log.push($.get(double)); + }); + + return () => { + flushSync(); + assert.deepEqual(log, [20]); + }; + }); + + test('schedules rerun when writing to signal after reading it from derived', (runes) => { + if (!runes) return () => {}; + let log: any[] = []; + + const value = state(1); + const double = derived(() => $.get(value) * 2); + + user_effect(() => { + log.push($.get(double)); + set(value, 10); + }); + + return () => { + flushSync(); + assert.deepEqual(log, [2, 20]); + }; + }); + test('effect teardown is removed on re-run', () => { const count = state(0); let first = true; From a1698c63ec8ffe8974b077154c7a4cff6f791933 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:05:05 +0000 Subject: [PATCH 025/265] Version Packages (#15001) Co-authored-by: github-actions[bot] --- .changeset/curvy-lies-rush.md | 5 ----- .changeset/tricky-spiders-collect.md | 5 ----- packages/svelte/CHANGELOG.md | 8 ++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 .changeset/curvy-lies-rush.md delete mode 100644 .changeset/tricky-spiders-collect.md diff --git a/.changeset/curvy-lies-rush.md b/.changeset/curvy-lies-rush.md deleted file mode 100644 index 25c6ffc1d91b..000000000000 --- a/.changeset/curvy-lies-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -feat: allow const tag inside `svelte:boundary` diff --git a/.changeset/tricky-spiders-collect.md b/.changeset/tricky-spiders-collect.md deleted file mode 100644 index 0ea8b71e5efc..000000000000 --- a/.changeset/tricky-spiders-collect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure signal write invalidation within effects is consistent diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 4f3b9a168a79..e0ae3f313e58 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.17.5 + +### Patch Changes + +- feat: allow const tag inside `svelte:boundary` ([#14993](https://github.com/sveltejs/svelte/pull/14993)) + +- fix: ensure signal write invalidation within effects is consistent ([#14989](https://github.com/sveltejs/svelte/pull/14989)) + ## 5.17.4 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index f426b97be4aa..0b69fb6edf6e 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.17.4", + "version": "5.17.5", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 69a883393828..b57f60055a6d 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.17.4'; +export const VERSION = '5.17.5'; export const PUBLIC_VERSION = '5'; From dfa97a5e643655d919a7c6ff80fb3841fe1111f9 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 14 Jan 2025 17:28:44 +0100 Subject: [PATCH 026/265] feat: allow every children in `template` tags (#15007) * feat: allow every children in `template` tags * Update .changeset/dry-mails-return.md --------- Co-authored-by: Rich Harris --- .changeset/dry-mails-return.md | 5 +++++ packages/svelte/src/html-tree-validation.js | 2 ++ .../samples/invalid-node-placement-template/errors.json | 1 + .../samples/invalid-node-placement-template/input.svelte | 6 ++++++ 4 files changed, 14 insertions(+) create mode 100644 .changeset/dry-mails-return.md create mode 100644 packages/svelte/tests/validator/samples/invalid-node-placement-template/errors.json create mode 100644 packages/svelte/tests/validator/samples/invalid-node-placement-template/input.svelte diff --git a/.changeset/dry-mails-return.md b/.changeset/dry-mails-return.md new file mode 100644 index 000000000000..8f87f8e33b48 --- /dev/null +++ b/.changeset/dry-mails-return.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: allow `