From 8e2f4b51c50a2e10bc482fac6d900be921dd8b74 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 23 Jul 2025 07:55:51 -0400 Subject: [PATCH 01/60] fix: unset batch before flushing queued effects (#16482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - add state changes resulting from an $effect to a separate new batch - schedule rerunning effects based on the sources that are dirty, not just rerunning them all blindly (excempting async effects which will have run by that time already) * test * better fix * tests * this fixes the last test somehow * fix #16477 * typo * copy over changeset from #16477 * copy over changeset from #16464 * changeset * dedupe * move flushing_sync check inside Batch.ensure * unused * flushing_sync -> is_flushing_sync * remove flush_effects method * dedupe declaration * tweak * tweak * update comment — it _does_ feel slightly wrong, but no wronger than the rest of this cursed function --------- Co-authored-by: Simon Holthausen --- .changeset/grumpy-boats-beg.md | 5 + .changeset/shiny-walls-fix.md | 5 + .changeset/thick-mice-kick.md | 5 + .../src/internal/client/reactivity/batch.js | 190 ++++++++++-------- .../src/internal/client/reactivity/sources.js | 11 +- .../Component.svelte | 7 + .../1000-reading-derived-effects/_config.js | 5 + .../1000-reading-derived-effects/main.svelte | 8 + .../_config.js | 26 +++ .../main.svelte | 27 +++ .../async-effect-triggers-await/_config.js | 32 +++ .../async-effect-triggers-await/main.svelte | 24 +++ .../binding-update-while-focused-2/_config.js | 24 +++ .../main.svelte | 21 ++ 14 files changed, 298 insertions(+), 92 deletions(-) create mode 100644 .changeset/grumpy-boats-beg.md create mode 100644 .changeset/shiny-walls-fix.md create mode 100644 .changeset/thick-mice-kick.md create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte diff --git a/.changeset/grumpy-boats-beg.md b/.changeset/grumpy-boats-beg.md new file mode 100644 index 0000000000..f677743def --- /dev/null +++ b/.changeset/grumpy-boats-beg.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: keep input in sync when binding updated via effect diff --git a/.changeset/shiny-walls-fix.md b/.changeset/shiny-walls-fix.md new file mode 100644 index 0000000000..91ed548728 --- /dev/null +++ b/.changeset/shiny-walls-fix.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent infinite async loop diff --git a/.changeset/thick-mice-kick.md b/.changeset/thick-mice-kick.md new file mode 100644 index 0000000000..eec55b77ee --- /dev/null +++ b/.changeset/thick-mice-kick.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: exclude derived writes from effect abort and rescheduling diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c452211894..ce413fa1e1 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -21,14 +21,13 @@ import { is_updating_effect, set_is_updating_effect, set_signal_status, - update_effect, - write_version + update_effect } from '../runtime.js'; import * as e from '../errors.js'; import { flush_tasks } from '../dom/task.js'; import { DEV } from 'esm-env'; import { invoke_error_boundary } from '../error-handling.js'; -import { old_values } from './sources.js'; +import { mark_reactions, old_values } from './sources.js'; import { unlink_effect } from './effects.js'; import { unset_context } from './async.js'; @@ -70,13 +69,15 @@ let last_scheduled_effect = null; let is_flushing = false; +let is_flushing_sync = false; + export class Batch { /** * The current values of any sources that are updated in this batch * They keys of this map are identical to `this.#previous` * @type {Map} */ - #current = new Map(); + current = new Map(); /** * The values of any sources that are updated in this batch _before_ those updates took place. @@ -156,7 +157,7 @@ export class Batch { * * @param {Effect[]} root_effects */ - #process(root_effects) { + process(root_effects) { queued_root_effects = []; /** @type {Map | null} */ @@ -169,7 +170,7 @@ export class Batch { current_values = new Map(); batch_deriveds = new Map(); - for (const [source, current] of this.#current) { + for (const [source, current] of this.current) { current_values.set(source, { v: source.v, wv: source.wv }); source.v = current; } @@ -202,9 +203,22 @@ export class Batch { this.#effects = []; this.#block_effects = []; + // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with + // newly updated sources, which could lead to infinite loops when effects run over and over again. + current_batch = null; + flush_queued_effects(render_effects); flush_queued_effects(effects); + // Reinstate the current batch if there was no new one created, as `process()` runs in a loop in `flush_effects()`. + // That method expects `current_batch` to be set, and could run the loop again if effects result in new effects + // being scheduled but without writes happening in which case no new batch is created. + if (current_batch === null) { + current_batch = this; + } else { + batches.delete(this); + } + this.#deferred?.resolve(); } else { // otherwise mark effects clean so they get scheduled on the next run @@ -300,7 +314,7 @@ export class Batch { this.#previous.set(source, value); } - this.#current.set(source, source.v); + this.current.set(source, source.v); } activate() { @@ -327,13 +341,13 @@ export class Batch { flush() { if (queued_root_effects.length > 0) { - this.flush_effects(); + flush_effects(); } else { this.#commit(); } if (current_batch !== this) { - // this can happen if a `flushSync` occurred during `this.flush_effects()`, + // this can happen if a `flushSync` occurred during `flush_effects()`, // which is permitted in legacy mode despite being a terrible idea return; } @@ -345,52 +359,6 @@ export class Batch { this.deactivate(); } - flush_effects() { - var was_updating_effect = is_updating_effect; - is_flushing = true; - - try { - var flush_count = 0; - set_is_updating_effect(true); - - while (queued_root_effects.length > 0) { - if (flush_count++ > 1000) { - if (DEV) { - var updates = new Map(); - - for (const source of this.#current.keys()) { - for (const [stack, update] of source.updated ?? []) { - var entry = updates.get(stack); - - if (!entry) { - entry = { error: update.error, count: 0 }; - updates.set(stack, entry); - } - - entry.count += update.count; - } - } - - for (const update of updates.values()) { - // eslint-disable-next-line no-console - console.error(update.error); - } - } - - infinite_loop_guard(); - } - - this.#process(queued_root_effects); - old_values.clear(); - } - } finally { - is_flushing = false; - set_is_updating_effect(was_updating_effect); - - last_scheduled_effect = null; - } - } - /** * Append and remove branches to/from the DOM */ @@ -412,19 +380,8 @@ export class Batch { this.#pending -= 1; if (this.#pending === 0) { - for (const e of this.#render_effects) { - set_signal_status(e, DIRTY); - schedule_effect(e); - } - - for (const e of this.#effects) { - set_signal_status(e, DIRTY); - schedule_effect(e); - } - - for (const e of this.#block_effects) { - set_signal_status(e, DIRTY); - schedule_effect(e); + for (const source of this.current.keys()) { + mark_reactions(source, DIRTY, false); } this.#render_effects = []; @@ -445,12 +402,12 @@ export class Batch { return (this.#deferred ??= deferred()).promise; } - static ensure(autoflush = true) { + static ensure() { if (current_batch === null) { const batch = (current_batch = new Batch()); batches.add(current_batch); - if (autoflush) { + if (!is_flushing_sync) { Batch.enqueue(() => { if (current_batch !== batch) { // a flushSync happened in the meantime @@ -487,32 +444,85 @@ export function flushSync(fn) { e.flush_sync_in_effect(); } - var result; + var was_flushing_sync = is_flushing_sync; + is_flushing_sync = true; - const batch = Batch.ensure(false); + try { + var result; - if (fn) { - batch.flush_effects(); + if (fn) { + flush_effects(); + result = fn(); + } - result = fn(); - } + while (true) { + flush_tasks(); - while (true) { - flush_tasks(); + if (queued_root_effects.length === 0) { + current_batch?.flush(); - if (queued_root_effects.length === 0) { - if (batch === current_batch) { - batch.flush(); + // we need to check again, in case we just updated an `$effect.pending()` + if (queued_root_effects.length === 0) { + // this would be reset in `flush_effects()` but since we are early returning here, + // we need to reset it here as well in case the first time there's 0 queued root effects + last_scheduled_effect = null; + + return /** @type {T} */ (result); + } } - // this would be reset in `batch.flush_effects()` but since we are early returning here, - // we need to reset it here as well in case the first time there's 0 queued root effects - last_scheduled_effect = null; + flush_effects(); + } + } finally { + is_flushing_sync = was_flushing_sync; + } +} + +function flush_effects() { + var was_updating_effect = is_updating_effect; + is_flushing = true; + + try { + var flush_count = 0; + set_is_updating_effect(true); + + while (queued_root_effects.length > 0) { + var batch = Batch.ensure(); + + if (flush_count++ > 1000) { + if (DEV) { + var updates = new Map(); + + for (const source of batch.current.keys()) { + for (const [stack, update] of source.updated ?? []) { + var entry = updates.get(stack); + + if (!entry) { + entry = { error: update.error, count: 0 }; + updates.set(stack, entry); + } + + entry.count += update.count; + } + } + + for (const update of updates.values()) { + // eslint-disable-next-line no-console + console.error(update.error); + } + } + + infinite_loop_guard(); + } - return /** @type {T} */ (result); + batch.process(queued_root_effects); + old_values.clear(); } + } finally { + is_flushing = false; + set_is_updating_effect(was_updating_effect); - batch.flush_effects(); + last_scheduled_effect = null; } } @@ -545,7 +555,7 @@ function flush_queued_effects(effects) { var effect = effects[i++]; if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) { - var wv = write_version; + var n = current_batch ? current_batch.current.size : 0; update_effect(effect); @@ -568,7 +578,11 @@ function flush_queued_effects(effects) { // if state is written in a user effect, abort and re-schedule, lest we run // effects that should be removed as a result of the state change - if (write_version > wv && (effect.f & USER_EFFECT) !== 0) { + if ( + current_batch !== null && + current_batch.current.size > n && + (effect.f & USER_EFFECT) !== 0 + ) { break; } } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index f6b14f3360..3b28c8fdce 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -179,7 +179,7 @@ export function internal_set(source, value) { source.v = value; - const batch = Batch.ensure(); + var batch = Batch.ensure(); batch.capture(source, old_value); if (DEV) { @@ -301,9 +301,10 @@ export function increment(source) { /** * @param {Value} signal * @param {number} status should be DIRTY or MAYBE_DIRTY + * @param {boolean} schedule_async * @returns {void} */ -function mark_reactions(signal, status) { +export function mark_reactions(signal, status, schedule_async = true) { var reactions = signal.reactions; if (reactions === null) return; @@ -323,14 +324,16 @@ function mark_reactions(signal, status) { continue; } + var should_schedule = (flags & DIRTY) === 0 && (schedule_async || (flags & ASYNC) === 0); + // don't set a DIRTY reaction to MAYBE_DIRTY - if ((flags & DIRTY) === 0) { + if (should_schedule) { set_signal_status(reaction, status); } if ((flags & DERIVED) !== 0) { mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY); - } else if ((flags & DIRTY) === 0) { + } else if (should_schedule) { schedule_effect(/** @type {Effect} */ (reaction)); } } diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte new file mode 100644 index 0000000000..7a54323cb9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js new file mode 100644 index 0000000000..2e4a27cf09 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + async test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte new file mode 100644 index 0000000000..bd326edfb9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte @@ -0,0 +1,8 @@ + + +{#each arr} + +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js new file mode 100644 index 0000000000..782ae945f9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/_config.js @@ -0,0 +1,26 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + + const [input] = target.querySelectorAll('input'); + + input.focus(); + input.value = '3'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + await tick(); + + assert.equal(input.value, '3'); + assert.htmlEqual(target.innerHTML, `

3

`); + + input.focus(); + input.value = '1'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + await tick(); + + assert.equal(input.value, '2'); + assert.htmlEqual(target.innerHTML, `

2

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte new file mode 100644 index 0000000000..763ce6ebf0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused/main.svelte @@ -0,0 +1,27 @@ + + + +

{await value}

+ + + {#snippet pending()} +

loading...

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js new file mode 100644 index 0000000000..c551cc6b8c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/_config.js @@ -0,0 +1,32 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + + const [increment] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + +

1

+

1

+ ` + ); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + +

2

+

2

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte new file mode 100644 index 0000000000..153fe03f0d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-triggers-await/main.svelte @@ -0,0 +1,24 @@ + + + + +

{JSON.stringify((await data), null, 2)}

+ {#if true} + +

{unrelated}

+ {/if} + + {#snippet pending()} +

loading...

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js new file mode 100644 index 0000000000..7a56c79d71 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/_config.js @@ -0,0 +1,24 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const [input] = target.querySelectorAll('input'); + + input.focus(); + input.value = '3'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + flushSync(); + + assert.equal(input.value, '3'); + assert.htmlEqual(target.innerHTML, `

3

`); + + input.focus(); + input.value = '1'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + flushSync(); + + assert.equal(input.value, '2'); + assert.htmlEqual(target.innerHTML, `

2

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte new file mode 100644 index 0000000000..b0597c223b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-2/main.svelte @@ -0,0 +1,21 @@ + + +

{value}

+ From c26365bdda1a13e98a2e75f17da655e08ff4f8bc Mon Sep 17 00:00:00 2001 From: Hyunbin Seo <47051820+hyunbinseo@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:02:28 +0900 Subject: [PATCH 02/60] chore: rename form `accept-charset` attribute (#16478) * chore: rename form accept-charset attribute * chore: utf-8 to lowercase Co-authored-by: Rich Harris * Update .changeset/healthy-carpets-deny.md --------- Co-authored-by: Rich Harris --- .changeset/healthy-carpets-deny.md | 5 +++++ packages/svelte/elements.d.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/healthy-carpets-deny.md diff --git a/.changeset/healthy-carpets-deny.md b/.changeset/healthy-carpets-deny.md new file mode 100644 index 0000000000..8f8db7fa9c --- /dev/null +++ b/.changeset/healthy-carpets-deny.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: rename form accept-charset attribute diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 1492f77792..604241592a 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -996,7 +996,7 @@ export interface HTMLFieldsetAttributes extends HTMLAttributes { - acceptcharset?: string | undefined | null; + 'accept-charset'?: 'utf-8' | (string & {}) | undefined | null; action?: string | undefined | null; autocomplete?: AutoFillBase | undefined | null; enctype?: From f8820956d2591288de53927c5e173242a13f125a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:23:52 -0400 Subject: [PATCH 03/60] Version Packages (#16484) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/grumpy-boats-beg.md | 5 ----- .changeset/healthy-carpets-deny.md | 5 ----- .changeset/shiny-walls-fix.md | 5 ----- .changeset/thick-mice-kick.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/grumpy-boats-beg.md delete mode 100644 .changeset/healthy-carpets-deny.md delete mode 100644 .changeset/shiny-walls-fix.md delete mode 100644 .changeset/thick-mice-kick.md diff --git a/.changeset/grumpy-boats-beg.md b/.changeset/grumpy-boats-beg.md deleted file mode 100644 index f677743def..0000000000 --- a/.changeset/grumpy-boats-beg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: keep input in sync when binding updated via effect diff --git a/.changeset/healthy-carpets-deny.md b/.changeset/healthy-carpets-deny.md deleted file mode 100644 index 8f8db7fa9c..0000000000 --- a/.changeset/healthy-carpets-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: rename form accept-charset attribute diff --git a/.changeset/shiny-walls-fix.md b/.changeset/shiny-walls-fix.md deleted file mode 100644 index 91ed548728..0000000000 --- a/.changeset/shiny-walls-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prevent infinite async loop diff --git a/.changeset/thick-mice-kick.md b/.changeset/thick-mice-kick.md deleted file mode 100644 index eec55b77ee..0000000000 --- a/.changeset/thick-mice-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: exclude derived writes from effect abort and rescheduling diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 5a5e532a08..8cd3850460 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.36.14 + +### Patch Changes + +- fix: keep input in sync when binding updated via effect ([#16482](https://github.com/sveltejs/svelte/pull/16482)) + +- fix: rename form accept-charset attribute ([#16478](https://github.com/sveltejs/svelte/pull/16478)) + +- fix: prevent infinite async loop ([#16482](https://github.com/sveltejs/svelte/pull/16482)) + +- fix: exclude derived writes from effect abort and rescheduling ([#16482](https://github.com/sveltejs/svelte/pull/16482)) + ## 5.36.13 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 4bf9a5df22..7fe1d161f2 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.36.13", + "version": "5.36.14", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 7d47fbc5f1..cd9d8b459c 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.36.13'; +export const VERSION = '5.36.14'; export const PUBLIC_VERSION = '5'; From 53417ea8f785f2ec884b095ac1bacd08aa30da86 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 24 Jul 2025 01:48:32 -0400 Subject: [PATCH 04/60] fix: preserve dirty status of deferred effects (#16487) * don't mark_reactions inside decrement, it can cause infinite loops * revert mark_reactions changes * preserve DIRTY/MAYBE_DIRTY status of deferred effects * changeset * tweak --- .changeset/cool-insects-argue.md | 5 ++ .../src/internal/client/reactivity/batch.js | 63 ++++++++++++++----- .../src/internal/client/reactivity/sources.js | 9 +-- .../async-effect-conservative/_config.js | 28 +++++++++ .../async-effect-conservative/main.svelte | 17 +++++ 5 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 .changeset/cool-insects-argue.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-conservative/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-conservative/main.svelte diff --git a/.changeset/cool-insects-argue.md b/.changeset/cool-insects-argue.md new file mode 100644 index 0000000000..ff1c520b7b --- /dev/null +++ b/.changeset/cool-insects-argue.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: preserve dirty status of deferred effects diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index ce413fa1e1..89bad947c7 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -10,7 +10,8 @@ import { INERT, RENDER_EFFECT, ROOT_EFFECT, - USER_EFFECT + USER_EFFECT, + MAYBE_DIRTY } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property } from '../../shared/utils.js'; @@ -27,7 +28,7 @@ import * as e from '../errors.js'; import { flush_tasks } from '../dom/task.js'; import { DEV } from 'esm-env'; import { invoke_error_boundary } from '../error-handling.js'; -import { mark_reactions, old_values } from './sources.js'; +import { old_values } from './sources.js'; import { unlink_effect } from './effects.js'; import { unset_context } from './async.js'; @@ -146,6 +147,18 @@ export class Batch { */ #block_effects = []; + /** + * Deferred effects (which run after async work has completed) that are DIRTY + * @type {Effect[]} + */ + #dirty_effects = []; + + /** + * Deferred effects that are MAYBE_DIRTY + * @type {Effect[]} + */ + #maybe_dirty_effects = []; + /** * A set of branches that still exist, but will be destroyed when this batch * is committed — we skip over these during `process` @@ -221,10 +234,9 @@ export class Batch { this.#deferred?.resolve(); } else { - // otherwise mark effects clean so they get scheduled on the next run - for (const e of this.#render_effects) set_signal_status(e, CLEAN); - for (const e of this.#effects) set_signal_status(e, CLEAN); - for (const e of this.#block_effects) set_signal_status(e, CLEAN); + this.#defer_effects(this.#render_effects); + this.#defer_effects(this.#effects); + this.#defer_effects(this.#block_effects); } if (current_values) { @@ -271,15 +283,15 @@ export class Batch { if (!skip && effect.fn !== null) { if (is_branch) { effect.f ^= CLEAN; - } else if ((flags & EFFECT) !== 0) { - this.#effects.push(effect); - } else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) { - this.#render_effects.push(effect); - } else if (is_dirty(effect)) { - if ((flags & ASYNC) !== 0) { + } else if ((flags & CLEAN) === 0) { + if ((flags & EFFECT) !== 0) { + this.#effects.push(effect); + } else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) { + this.#render_effects.push(effect); + } else if ((flags & ASYNC) !== 0) { var effects = effect.b?.pending ? this.#boundary_async_effects : this.#async_effects; effects.push(effect); - } else { + } else if (is_dirty(effect)) { if ((effect.f & BLOCK_EFFECT) !== 0) this.#block_effects.push(effect); update_effect(effect); } @@ -303,6 +315,21 @@ export class Batch { } } + /** + * @param {Effect[]} effects + */ + #defer_effects(effects) { + for (const e of effects) { + const target = (e.f & DIRTY) !== 0 ? this.#dirty_effects : this.#maybe_dirty_effects; + target.push(e); + + // mark as clean so they get scheduled if they depend on pending async state + set_signal_status(e, CLEAN); + } + + effects.length = 0; + } + /** * Associate a change to a given source with the current * batch, noting its previous and current values @@ -380,8 +407,14 @@ export class Batch { this.#pending -= 1; if (this.#pending === 0) { - for (const source of this.current.keys()) { - mark_reactions(source, DIRTY, false); + for (const e of this.#dirty_effects) { + set_signal_status(e, DIRTY); + schedule_effect(e); + } + + for (const e of this.#maybe_dirty_effects) { + set_signal_status(e, MAYBE_DIRTY); + schedule_effect(e); } this.#render_effects = []; diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 3b28c8fdce..7b5198542a 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -301,10 +301,9 @@ export function increment(source) { /** * @param {Value} signal * @param {number} status should be DIRTY or MAYBE_DIRTY - * @param {boolean} schedule_async * @returns {void} */ -export function mark_reactions(signal, status, schedule_async = true) { +function mark_reactions(signal, status) { var reactions = signal.reactions; if (reactions === null) return; @@ -324,16 +323,14 @@ export function mark_reactions(signal, status, schedule_async = true) { continue; } - var should_schedule = (flags & DIRTY) === 0 && (schedule_async || (flags & ASYNC) === 0); - // don't set a DIRTY reaction to MAYBE_DIRTY - if (should_schedule) { + if ((flags & DIRTY) === 0) { set_signal_status(reaction, status); } if ((flags & DERIVED) !== 0) { mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY); - } else if (should_schedule) { + } else { schedule_effect(/** @type {Effect} */ (reaction)); } } diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/_config.js new file mode 100644 index 0000000000..bab06a203d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/_config.js @@ -0,0 +1,28 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + await tick(); + + const [increment] = target.querySelectorAll('button'); + + assert.deepEqual(logs, [false]); + assert.htmlEqual(target.innerHTML, '

0

'); + + increment.click(); + await tick(); + assert.deepEqual(logs, [false]); + assert.htmlEqual(target.innerHTML, '

1

'); + + increment.click(); + await tick(); + assert.deepEqual(logs, [false, true]); + assert.htmlEqual(target.innerHTML, '

2

'); + + increment.click(); + await tick(); + assert.deepEqual(logs, [false, true]); + assert.htmlEqual(target.innerHTML, '

3

'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/main.svelte new file mode 100644 index 0000000000..5305067a5a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-conservative/main.svelte @@ -0,0 +1,17 @@ + + + + +

{await count}

+ + {#snippet pending()} +

loading...

+ {/snippet} +
From 46d3261ed9a7d6acb9908f52e989013795555a56 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:39:54 +0200 Subject: [PATCH 05/60] chore: don't do effect scheduling unnecessarily (#16489) mini-cleanup post #16487 - we don't need to do the work of scheduling an effect that's already dirty which means it already scheduled its root effect to run --- packages/svelte/src/internal/client/reactivity/sources.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 7b5198542a..3b2087d56b 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -323,14 +323,16 @@ function mark_reactions(signal, status) { continue; } + var not_dirty = (flags & DIRTY) === 0; + // don't set a DIRTY reaction to MAYBE_DIRTY - if ((flags & DIRTY) === 0) { + if (not_dirty) { set_signal_status(reaction, status); } if ((flags & DERIVED) !== 0) { mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY); - } else { + } else if (not_dirty) { schedule_effect(/** @type {Effect} */ (reaction)); } } From 4e74cd35fedf55e65b60c957041f79ca94dfc197 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:43:28 +0200 Subject: [PATCH 06/60] Version Packages (#16488) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/cool-insects-argue.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/cool-insects-argue.md diff --git a/.changeset/cool-insects-argue.md b/.changeset/cool-insects-argue.md deleted file mode 100644 index ff1c520b7b..0000000000 --- a/.changeset/cool-insects-argue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: preserve dirty status of deferred effects diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 8cd3850460..5bffa5f70e 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.36.15 + +### Patch Changes + +- fix: preserve dirty status of deferred effects ([#16487](https://github.com/sveltejs/svelte/pull/16487)) + ## 5.36.14 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 7fe1d161f2..051f82ec3a 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.36.14", + "version": "5.36.15", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index cd9d8b459c..1d469f29b0 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.36.14'; +export const VERSION = '5.36.15'; export const PUBLIC_VERSION = '5'; From 7eb11e0e247d9da4edb88dc5652101ceb55fc530 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:00:43 +0200 Subject: [PATCH 07/60] fix: don't destroy effect roots created inside of deriveds (#16492) We were wrongfully adding effect roots to `derived.effects`, too, which meant those were destroyed when the derived reran. --- .changeset/lemon-weeks-call.md | 5 ++ .../src/internal/client/reactivity/effects.js | 6 ++- .../src/internal/client/reactivity/types.d.ts | 2 +- packages/svelte/tests/signals/test.ts | 51 +++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 .changeset/lemon-weeks-call.md diff --git a/.changeset/lemon-weeks-call.md b/.changeset/lemon-weeks-call.md new file mode 100644 index 0000000000..ae62305630 --- /dev/null +++ b/.changeset/lemon-weeks-call.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't destroy effect roots created inside of deriveds diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index c4edd2bf8d..f44efa32f1 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -148,7 +148,11 @@ function create_effect(type, fn, sync, push = true) { } // if we're in a derived, add the effect there too - if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) { + if ( + active_reaction !== null && + (active_reaction.f & DERIVED) !== 0 && + (type & ROOT_EFFECT) === 0 + ) { var derived = /** @type {Derived} */ (active_reaction); (derived.effects ??= []).push(effect); } diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 72187e84a7..81f7197b80 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -54,7 +54,7 @@ export interface Reaction extends Signal { export interface Derived extends Value, Reaction { /** The derived function */ fn: () => V; - /** Effects created inside this signal */ + /** Effects created inside this signal. Used to destroy those effects when the derived reruns or is cleaned up */ effects: null | Effect[]; /** Parent effect or derived */ parent: Effect | Derived | null; diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 937324727b..eff6d6166a 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1390,4 +1390,55 @@ describe('signals', () => { destroy(); }; }); + + test('$effect.root inside deriveds stay alive independently', () => { + const log: any[] = []; + const c = state(0); + const cleanup: any[] = []; + const inner_states: any[] = []; + + const d = derived(() => { + const destroy = effect_root(() => { + const x = state(0); + inner_states.push(x); + + effect(() => { + log.push('inner ' + $.get(x)); + return () => { + log.push('inner destroyed'); + }; + }); + }); + + cleanup.push(destroy); + + return $.get(c); + }); + + return () => { + log.push($.get(d)); + flushSync(); + + assert.deepEqual(log, [0, 'inner 0']); + log.length = 0; + + set(inner_states[0], 1); + flushSync(); + + assert.deepEqual(log, ['inner destroyed', 'inner 1']); + log.length = 0; + + set(c, 1); + log.push($.get(d)); + flushSync(); + + assert.deepEqual(log, [1, 'inner 0']); + log.length = 0; + + cleanup.forEach((fn) => fn()); + flushSync(); + + assert.deepEqual(log, ['inner destroyed', 'inner destroyed']); + }; + }); }); From dc043fb2d3c87619e1c26315f600acd4fe19dfa4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 24 Jul 2025 10:16:15 -0400 Subject: [PATCH 08/60] fix: don't update a focused input with values from its own past (#16491) * fix: don't update a focused input with values from its own past * remove * fix --- .changeset/fast-mails-fail.md | 5 +++ .../client/dom/elements/bindings/input.js | 11 ++++-- .../src/internal/client/reactivity/batch.js | 12 +++++- .../_config.js | 37 +++++++++++++++++++ .../main.svelte | 25 +++++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 .changeset/fast-mails-fail.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/main.svelte diff --git a/.changeset/fast-mails-fail.md b/.changeset/fast-mails-fail.md new file mode 100644 index 0000000000..027cb01548 --- /dev/null +++ b/.changeset/fast-mails-fail.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't update a focused input with values from its own past diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js index 7c1fccea0f..7c73280dd6 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js @@ -8,7 +8,7 @@ import { queue_micro_task } from '../../task.js'; import { hydrating } from '../../hydration.js'; import { untrack } from '../../../runtime.js'; import { is_runes } from '../../../context.js'; -import { current_batch } from '../../../reactivity/batch.js'; +import { current_batch, previous_batch } from '../../../reactivity/batch.js'; /** * @param {HTMLInputElement} input @@ -76,13 +76,18 @@ export function bind_value(input, get, set = get) { var value = get(); - if (input === document.activeElement && batches.has(/** @type {Batch} */ (current_batch))) { + if (input === document.activeElement) { + // we need both, because in non-async mode, render effects run before previous_batch is set + var batch = /** @type {Batch} */ (previous_batch ?? current_batch); + // Never rewrite the contents of a focused input. We can get here if, for example, // an update is deferred because of async work depending on the input: // // //

{await find(query)}

- return; + if (batches.has(batch)) { + return; + } } if (is_numberlike_input(input) && value === to_number(input.value)) { diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 89bad947c7..123bc95d16 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -38,6 +38,13 @@ const batches = new Set(); /** @type {Batch | null} */ export let current_batch = null; +/** + * This is needed to avoid overwriting inputs in non-async mode + * TODO 6.0 remove this, as non-async mode will go away + * @type {Batch | null} + */ +export let previous_batch = null; + /** * When time travelling, we re-evaluate deriveds based on the temporary * values of their dependencies rather than their actual values, and cache @@ -71,7 +78,6 @@ let last_scheduled_effect = null; let is_flushing = false; let is_flushing_sync = false; - export class Batch { /** * The current values of any sources that are updated in this batch @@ -173,6 +179,8 @@ export class Batch { process(root_effects) { queued_root_effects = []; + previous_batch = null; + /** @type {Map | null} */ var current_values = null; @@ -218,6 +226,7 @@ export class Batch { // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with // newly updated sources, which could lead to infinite loops when effects run over and over again. + previous_batch = current_batch; current_batch = null; flush_queued_effects(render_effects); @@ -350,6 +359,7 @@ export class Batch { deactivate() { current_batch = null; + previous_batch = null; for (const update of effect_pending_updates) { effect_pending_updates.delete(update); diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js new file mode 100644 index 0000000000..b0772ad3c0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/_config.js @@ -0,0 +1,37 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, instance }) { + instance.shift(); + await tick(); + + const [input] = target.querySelectorAll('input'); + + input.focus(); + input.value = '1'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + await tick(); + + assert.htmlEqual(target.innerHTML, `

0

`); + assert.equal(input.value, '1'); + + input.focus(); + input.value = '2'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + await tick(); + + assert.htmlEqual(target.innerHTML, `

0

`); + assert.equal(input.value, '2'); + + instance.shift(); + await tick(); + assert.htmlEqual(target.innerHTML, `

1

`); + assert.equal(input.value, '2'); + + instance.shift(); + await tick(); + assert.htmlEqual(target.innerHTML, `

2

`); + assert.equal(input.value, '2'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/main.svelte new file mode 100644 index 0000000000..2fc898e654 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-binding-update-while-focused-2/main.svelte @@ -0,0 +1,25 @@ + + + + +

{await push(count)}

+ + {#snippet pending()} +

loading...

+ {/snippet} +
From 8ad02e4a8c920823034051e5e2a13df58f25ebd5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:22:30 +0200 Subject: [PATCH 09/60] Version Packages (#16493) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/fast-mails-fail.md | 5 ----- .changeset/lemon-weeks-call.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/fast-mails-fail.md delete mode 100644 .changeset/lemon-weeks-call.md diff --git a/.changeset/fast-mails-fail.md b/.changeset/fast-mails-fail.md deleted file mode 100644 index 027cb01548..0000000000 --- a/.changeset/fast-mails-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't update a focused input with values from its own past diff --git a/.changeset/lemon-weeks-call.md b/.changeset/lemon-weeks-call.md deleted file mode 100644 index ae62305630..0000000000 --- a/.changeset/lemon-weeks-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't destroy effect roots created inside of deriveds diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 5bffa5f70e..450ecde53b 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.36.16 + +### Patch Changes + +- fix: don't update a focused input with values from its own past ([#16491](https://github.com/sveltejs/svelte/pull/16491)) + +- fix: don't destroy effect roots created inside of deriveds ([#16492](https://github.com/sveltejs/svelte/pull/16492)) + ## 5.36.15 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 051f82ec3a..07954026b5 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.36.15", + "version": "5.36.16", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 1d469f29b0..5d76fc3f29 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.36.15'; +export const VERSION = '5.36.16'; export const PUBLIC_VERSION = '5'; From 0bba84cc203c7cab945527cc3928597d3612a37d Mon Sep 17 00:00:00 2001 From: Bladesheng Date: Fri, 25 Jul 2025 14:43:41 +0200 Subject: [PATCH 10/60] fix: add types for `part` attribute to svg attributes (#16499) --- .changeset/seven-colts-obey.md | 5 +++++ packages/svelte/elements.d.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/seven-colts-obey.md diff --git a/.changeset/seven-colts-obey.md b/.changeset/seven-colts-obey.md new file mode 100644 index 0000000000..b41216f649 --- /dev/null +++ b/.changeset/seven-colts-obey.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: add types for `part` attribute to svg attributes diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 604241592a..2e1042dfd6 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -1553,6 +1553,7 @@ export interface SVGAttributes extends AriaAttributes, DO height?: number | string | undefined | null; id?: string | undefined | null; lang?: string | undefined | null; + part?: string | undefined | null; max?: number | string | undefined | null; media?: string | undefined | null; // On the `textPath` element From b0f9ea3ae62cfb9eb4437898bf8a6b90e0531657 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:57:32 -0700 Subject: [PATCH 11/60] fix: throw on duplicate class field declarations (#16502) * fix: throw on duplicate class field declarations * doh * handle assignment in constructor * fix * apply suggestion from review * fix * fix failing test --- .changeset/odd-phones-taste.md | 5 ++ .../98-reference/.generated/compile-errors.md | 6 +++ .../svelte/messages/compile-errors/script.md | 4 ++ packages/svelte/src/compiler/errors.js | 10 ++++ .../phases/2-analyze/visitors/ClassBody.js | 53 ++++++++++++++++++- .../class-state-constructor-9/errors.json | 12 ++--- 6 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 .changeset/odd-phones-taste.md diff --git a/.changeset/odd-phones-taste.md b/.changeset/odd-phones-taste.md new file mode 100644 index 0000000000..ec9534b741 --- /dev/null +++ b/.changeset/odd-phones-taste.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: throw on duplicate class field declarations diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index 20f57770d1..957a9f67c7 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -364,6 +364,12 @@ The $ name is reserved, and cannot be used for variables and imports The $ prefix is reserved, and cannot be used for variables and imports ``` +### duplicate_class_field + +``` +`%name%` has already been declared +``` + ### each_item_invalid_assignment ``` diff --git a/packages/svelte/messages/compile-errors/script.md b/packages/svelte/messages/compile-errors/script.md index 2b0c5eafdf..5c1080aced 100644 --- a/packages/svelte/messages/compile-errors/script.md +++ b/packages/svelte/messages/compile-errors/script.md @@ -30,6 +30,10 @@ > The $ prefix is reserved, and cannot be used for variables and imports +## duplicate_class_field + +> `%name%` has already been declared + ## 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}`) diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 599d3e8248..e763a6e073 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -152,6 +152,16 @@ export function dollar_prefix_invalid(node) { e(node, 'dollar_prefix_invalid', `The $ prefix is reserved, and cannot be used for variables and imports\nhttps://svelte.dev/e/dollar_prefix_invalid`); } +/** + * `%name%` has already been declared + * @param {null | number | NodeLike} node + * @param {string} name + * @returns {never} + */ +export function duplicate_class_field(node, name) { + e(node, 'duplicate_class_field', `\`${name}\` has already been declared\nhttps://svelte.dev/e/duplicate_class_field`); +} + /** * 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 diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js index ffc39ac00d..2bfc1dbce3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js @@ -33,6 +33,9 @@ export function ClassBody(node, context) { /** @type {Map} */ const state_fields = new Map(); + /** @type {Map>} */ + const fields = new Map(); + context.state.analysis.classes.set(node, state_fields); /** @type {MethodDefinition | null} */ @@ -54,6 +57,14 @@ export function ClassBody(node, context) { e.state_field_duplicate(node, name); } + const _key = (key.type === 'PrivateIdentifier' ? '#' : '') + name; + const field = fields.get(_key); + + // if there's already a method or assigned field, error + if (field && !(field.length === 1 && field[0] === 'prop')) { + e.duplicate_class_field(node, _key); + } + state_fields.set(name, { node, type: rune, @@ -67,10 +78,48 @@ export function ClassBody(node, context) { for (const child of node.body) { if (child.type === 'PropertyDefinition' && !child.computed && !child.static) { handle(child, child.key, child.value); + const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key); + const field = fields.get(key); + if (!field) { + fields.set(key, [child.value ? 'assigned_prop' : 'prop']); + continue; + } + e.duplicate_class_field(child, key); } - if (child.type === 'MethodDefinition' && child.kind === 'constructor') { - constructor = child; + if (child.type === 'MethodDefinition') { + if (child.kind === 'constructor') { + constructor = child; + } else if (!child.computed) { + const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key); + const field = fields.get(key); + if (!field) { + fields.set(key, [child.kind]); + continue; + } + if ( + field.includes(child.kind) || + field.includes('prop') || + field.includes('assigned_prop') + ) { + e.duplicate_class_field(child, key); + } + if (child.kind === 'get') { + if (field.length === 1 && field[0] === 'set') { + field.push('get'); + continue; + } + } else if (child.kind === 'set') { + if (field.length === 1 && field[0] === 'get') { + field.push('set'); + continue; + } + } else { + field.push(child.kind); + continue; + } + e.duplicate_class_field(child, key); + } } } diff --git a/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json b/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json index b7dd4c8ed4..94b5f191c2 100644 --- a/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json +++ b/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json @@ -1,14 +1,14 @@ [ { - "code": "state_field_invalid_assignment", - "message": "Cannot assign to a state field before its declaration", + "code": "duplicate_class_field", + "message": "`count` has already been declared", "start": { - "line": 2, - "column": 1 + "line": 5, + "column": 2 }, "end": { - "line": 2, - "column": 12 + "line": 5, + "column": 24 } } ] From d0ebd42986c4d3e4db0420f1aaebd337afdb230f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:46:12 -0700 Subject: [PATCH 12/60] Version Packages (#16500) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/odd-phones-taste.md | 5 ----- .changeset/seven-colts-obey.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/odd-phones-taste.md delete mode 100644 .changeset/seven-colts-obey.md diff --git a/.changeset/odd-phones-taste.md b/.changeset/odd-phones-taste.md deleted file mode 100644 index ec9534b741..0000000000 --- a/.changeset/odd-phones-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: throw on duplicate class field declarations diff --git a/.changeset/seven-colts-obey.md b/.changeset/seven-colts-obey.md deleted file mode 100644 index b41216f649..0000000000 --- a/.changeset/seven-colts-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: add types for `part` attribute to svg attributes diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 450ecde53b..766c83763a 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.36.17 + +### Patch Changes + +- fix: throw on duplicate class field declarations ([#16502](https://github.com/sveltejs/svelte/pull/16502)) + +- fix: add types for `part` attribute to svg attributes ([#16499](https://github.com/sveltejs/svelte/pull/16499)) + ## 5.36.16 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 07954026b5..22bc8cc8fa 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.36.16", + "version": "5.36.17", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 5d76fc3f29..d2992a3dcd 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.36.16'; +export const VERSION = '5.36.17'; export const PUBLIC_VERSION = '5'; From c7851789d71cc3d0b5d89ce9c759a39be6caa51a Mon Sep 17 00:00:00 2001 From: 7nik Date: Sat, 26 Jul 2025 21:27:22 +0300 Subject: [PATCH 13/60] fix: always mark props as stateful (#16504) --- .changeset/gorgeous-jeans-begin.md | 5 +++++ .../phases/2-analyze/visitors/Identifier.js | 5 ++++- .../props-default-value-function/_config.js | 15 +++++++++++++++ .../props-default-value-function/inner.svelte | 4 ++++ .../props-default-value-function/main.svelte | 14 ++++++++++++++ .../props-default-value-function/wrapper.svelte | 7 +++++++ .../props-default-value-function/wrapper2.svelte | 7 +++++++ 7 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 .changeset/gorgeous-jeans-begin.md create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte diff --git a/.changeset/gorgeous-jeans-begin.md b/.changeset/gorgeous-jeans-begin.md new file mode 100644 index 0000000000..586c4d8a57 --- /dev/null +++ b/.changeset/gorgeous-jeans-begin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: always mark props as stateful diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js index cced326f9b..4dfdfe5af1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -93,7 +93,10 @@ export function Identifier(node, context) { context.state.expression.references.add(binding); context.state.expression.has_state ||= binding.kind !== 'static' && - !binding.is_function() && + (binding.kind === 'prop' || + binding.kind === 'bindable_prop' || + binding.kind === 'rest_prop' || + !binding.is_function()) && !context.state.scope.evaluate(node).is_known; } diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js new file mode 100644 index 0000000000..6b281f04f0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js @@ -0,0 +1,15 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + + test({ assert, target }) { + const btn = target.querySelector('button'); + + assert.htmlEqual(target.innerHTML, ` Inner: 0 Inner: 0`); + btn?.click(); + flushSync(); + assert.htmlEqual(target.innerHTML, ` Inner: 1 Inner: 1`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte new file mode 100644 index 0000000000..6bde0a15a8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte @@ -0,0 +1,4 @@ + +Inner: {getter()} diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte new file mode 100644 index 0000000000..2cb2f67b82 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte new file mode 100644 index 0000000000..525494ddfb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte new file mode 100644 index 0000000000..9498f432d8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte @@ -0,0 +1,7 @@ + + + From 7cc4d56263f724c4d76ab6140af4d117cbb41f1d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 26 Jul 2025 22:16:40 -0400 Subject: [PATCH 14/60] feat: ignore component options in `compileModule` (#16362) --- .changeset/violet-ways-sleep.md | 5 + .../svelte/src/compiler/validate-options.js | 172 +++++++++--------- 2 files changed, 96 insertions(+), 81 deletions(-) create mode 100644 .changeset/violet-ways-sleep.md diff --git a/.changeset/violet-ways-sleep.md b/.changeset/violet-ways-sleep.md new file mode 100644 index 0000000000..749ecb1719 --- /dev/null +++ b/.changeset/violet-ways-sleep.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: ignore component options in `compileModule` diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js index ed83375d22..2b727ad093 100644 --- a/packages/svelte/src/compiler/validate-options.js +++ b/packages/svelte/src/compiler/validate-options.js @@ -8,7 +8,7 @@ import * as w from './warnings.js'; * @typedef {(input: Input, keypath: string) => Required} Validator */ -const common = { +const common_options = { filename: string('(unknown)'), // default to process.cwd() where it exists to replicate svelte4 behavior (and make Deno work with this as well) @@ -48,110 +48,120 @@ const common = { }) }; -export const validate_module_options = - /** @type {Validator} */ ( - object({ - ...common - }) - ); +const component_options = { + accessors: deprecate(w.options_deprecated_accessors, boolean(false)), -export const validate_component_options = - /** @type {Validator} */ ( - object({ - ...common, + css: validator('external', (input) => { + if (input === true || input === false) { + throw_error( + 'The boolean options have been removed from the css option. Use "external" instead of false and "injected" instead of true' + ); + } + if (input === 'none') { + throw_error( + 'css: "none" is no longer a valid option. If this was crucial for you, please open an issue on GitHub with your use case.' + ); + } - accessors: deprecate(w.options_deprecated_accessors, boolean(false)), + if (input !== 'external' && input !== 'injected') { + throw_error(`css should be either "external" (default, recommended) or "injected"`); + } - css: validator('external', (input) => { - if (input === true || input === false) { - throw_error( - 'The boolean options have been removed from the css option. Use "external" instead of false and "injected" instead of true' - ); - } - if (input === 'none') { - throw_error( - 'css: "none" is no longer a valid option. If this was crucial for you, please open an issue on GitHub with your use case.' - ); - } + return input; + }), - if (input !== 'external' && input !== 'injected') { - throw_error(`css should be either "external" (default, recommended) or "injected"`); - } + cssHash: fun(({ css, hash }) => { + return `svelte-${hash(css)}`; + }), + + // TODO this is a sourcemap option, would be good to put under a sourcemap namespace + cssOutputFilename: string(undefined), + + customElement: boolean(false), + + discloseVersion: boolean(true), - return input; - }), + immutable: deprecate(w.options_deprecated_immutable, boolean(false)), - cssHash: fun(({ css, hash }) => { - return `svelte-${hash(css)}`; - }), + legacy: removed( + 'The legacy option has been removed. If you are using this because of legacy.componentApi, use compatibility.componentApi instead' + ), + + compatibility: object({ + componentApi: list([4, 5], 5) + }), + + loopGuardTimeout: warn_removed(w.options_removed_loop_guard_timeout), + + name: string(undefined), - // TODO this is a sourcemap option, would be good to put under a sourcemap namespace - cssOutputFilename: string(undefined), + namespace: list(['html', 'mathml', 'svg']), - customElement: boolean(false), + modernAst: boolean(false), - discloseVersion: boolean(true), + outputFilename: string(undefined), - immutable: deprecate(w.options_deprecated_immutable, boolean(false)), + preserveComments: boolean(false), - legacy: removed( - 'The legacy option has been removed. If you are using this because of legacy.componentApi, use compatibility.componentApi instead' - ), + fragments: list(['html', 'tree']), - compatibility: object({ - componentApi: list([4, 5], 5) - }), + preserveWhitespace: boolean(false), - loopGuardTimeout: warn_removed(w.options_removed_loop_guard_timeout), + runes: boolean(undefined), - name: string(undefined), + hmr: boolean(false), - namespace: list(['html', 'mathml', 'svg']), + sourcemap: validator(undefined, (input) => { + // Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map, + // so there's no good way to check type validity here + return input; + }), - modernAst: boolean(false), + enableSourcemap: warn_removed(w.options_removed_enable_sourcemap), - outputFilename: string(undefined), + hydratable: warn_removed(w.options_removed_hydratable), - preserveComments: boolean(false), + format: removed( + 'The format option has been removed in Svelte 4, the compiler only outputs ESM now. Remove "format" from your compiler options. ' + + 'If you did not set this yourself, bump the version of your bundler plugin (vite-plugin-svelte/rollup-plugin-svelte/svelte-loader)' + ), - fragments: list(['html', 'tree']), + tag: removed( + 'The tag option has been removed in Svelte 5. Use `` inside the component instead. ' + + 'If that does not solve your use case, please open an issue on GitHub with details.' + ), - preserveWhitespace: boolean(false), + sveltePath: removed( + 'The sveltePath option has been removed in Svelte 5. ' + + 'If this option was crucial for you, please open an issue on GitHub with your use case.' + ), - runes: boolean(undefined), + // These two were primarily created for svelte-preprocess (https://github.com/sveltejs/svelte/pull/6194), + // but with new TypeScript compilation modes strictly separating types it's not necessary anymore + errorMode: removed( + 'The errorMode option has been removed. If you are using this through svelte-preprocess with TypeScript, ' + + 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead' + ), - hmr: boolean(false), + varsReport: removed( + 'The vars option has been removed. If you are using this through svelte-preprocess with TypeScript, ' + + 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead' + ) +}; - sourcemap: validator(undefined, (input) => { - // Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map, - // so there's no good way to check type validity here - return input; - }), +export const validate_module_options = + /** @type {Validator} */ ( + object({ + ...common_options, + ...Object.fromEntries(Object.keys(component_options).map((key) => [key, () => {}])) + }) + ); - enableSourcemap: warn_removed(w.options_removed_enable_sourcemap), - hydratable: warn_removed(w.options_removed_hydratable), - format: removed( - 'The format option has been removed in Svelte 4, the compiler only outputs ESM now. Remove "format" from your compiler options. ' + - 'If you did not set this yourself, bump the version of your bundler plugin (vite-plugin-svelte/rollup-plugin-svelte/svelte-loader)' - ), - tag: removed( - 'The tag option has been removed in Svelte 5. Use `` inside the component instead. ' + - 'If that does not solve your use case, please open an issue on GitHub with details.' - ), - sveltePath: removed( - 'The sveltePath option has been removed in Svelte 5. ' + - 'If this option was crucial for you, please open an issue on GitHub with your use case.' - ), - // These two were primarily created for svelte-preprocess (https://github.com/sveltejs/svelte/pull/6194), - // but with new TypeScript compilation modes strictly separating types it's not necessary anymore - errorMode: removed( - 'The errorMode option has been removed. If you are using this through svelte-preprocess with TypeScript, ' + - 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead' - ), - varsReport: removed( - 'The vars option has been removed. If you are using this through svelte-preprocess with TypeScript, ' + - 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead' - ) +export const validate_component_options = + /** @type {Validator} */ ( + object({ + ...common_options, + ...component_options }) ); From 39ee7cf4c247965ebacdd16fc365a31f00506026 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 26 Jul 2025 22:22:14 -0700 Subject: [PATCH 15/60] Version Packages (#16505) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/gorgeous-jeans-begin.md | 5 ----- .changeset/violet-ways-sleep.md | 5 ----- packages/svelte/CHANGELOG.md | 10 ++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 .changeset/gorgeous-jeans-begin.md delete mode 100644 .changeset/violet-ways-sleep.md diff --git a/.changeset/gorgeous-jeans-begin.md b/.changeset/gorgeous-jeans-begin.md deleted file mode 100644 index 586c4d8a57..0000000000 --- a/.changeset/gorgeous-jeans-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: always mark props as stateful diff --git a/.changeset/violet-ways-sleep.md b/.changeset/violet-ways-sleep.md deleted file mode 100644 index 749ecb1719..0000000000 --- a/.changeset/violet-ways-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': minor ---- - -feat: ignore component options in `compileModule` diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 766c83763a..f939f69d28 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.37.0 + +### Minor Changes + +- feat: ignore component options in `compileModule` ([#16362](https://github.com/sveltejs/svelte/pull/16362)) + +### Patch Changes + +- fix: always mark props as stateful ([#16504](https://github.com/sveltejs/svelte/pull/16504)) + ## 5.36.17 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 22bc8cc8fa..c781eda8c8 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.36.17", + "version": "5.37.0", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index d2992a3dcd..e83ba6fb30 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.36.17'; +export const VERSION = '5.37.0'; export const PUBLIC_VERSION = '5'; From 1773df94845bc53d55a1c9e9eb033d08056c845a Mon Sep 17 00:00:00 2001 From: fkobi Date: Sun, 27 Jul 2025 14:17:50 +0000 Subject: [PATCH 16/60] docs: use sh instead of bash in source blocks (#16506) * use sh instead of bash in source blocks no bash-specific functionality is used * regenerate --------- Co-authored-by: Rich Harris --- CONTRIBUTING.md | 4 ++-- documentation/docs/01-introduction/02-getting-started.md | 2 +- documentation/docs/07-misc/02-testing.md | 4 ++-- .../docs/98-reference/.generated/compile-warnings.md | 2 +- packages/svelte/README.md | 2 +- packages/svelte/messages/compile-warnings/template.md | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2d3e45049..0653b08b76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,13 +101,13 @@ Test samples are kept in `/test/xxx/samples` folder. 1. To run test, run `pnpm test`. 1. To run a particular test suite, use `pnpm test `, for example: - ```bash + ```sh pnpm test validator ``` 1. To filter tests _within_ a test suite, use `pnpm test -t `, for example: - ```bash + ```sh pnpm test validator -t a11y-alt-text ``` diff --git a/documentation/docs/01-introduction/02-getting-started.md b/documentation/docs/01-introduction/02-getting-started.md index c7351729ff..e97a46ad34 100644 --- a/documentation/docs/01-introduction/02-getting-started.md +++ b/documentation/docs/01-introduction/02-getting-started.md @@ -4,7 +4,7 @@ title: Getting started We recommend using [SvelteKit](../kit), which lets you [build almost anything](../kit/project-types). It's the official application framework from the Svelte team and powered by [Vite](https://vite.dev/). Create a new project with: -```bash +```sh npx sv create myapp cd myapp npm install diff --git a/documentation/docs/07-misc/02-testing.md b/documentation/docs/07-misc/02-testing.md index db99b70770..bcec4db0a3 100644 --- a/documentation/docs/07-misc/02-testing.md +++ b/documentation/docs/07-misc/02-testing.md @@ -10,7 +10,7 @@ Unit tests allow you to test small isolated parts of your code. Integration test To setup Vitest manually, first install it: -```bash +```sh npm install -D vitest ``` @@ -166,7 +166,7 @@ It is possible to test your components in isolation using Vitest. To get started, install jsdom (a library that shims DOM APIs): -```bash +```sh npm install -D jsdom ``` diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index 2af9021a6a..01003f30c5 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -683,7 +683,7 @@ Some templating languages (including Svelte) will 'fix' HTML by turning ` Date: Sun, 27 Jul 2025 19:50:22 +0200 Subject: [PATCH 17/60] fix: `append_styles` in an effect to make them available on mount (#16509) Co-authored-by: Rich Harris --- .changeset/shaggy-comics-fail.md | 5 +++++ .changeset/wise-hairs-pay.md | 5 +++++ .../src/compiler/phases/2-analyze/index.js | 16 +++++++------- .../3-transform/client/transform-client.js | 5 +++-- .../svelte/src/internal/client/dom/css.js | 4 ++-- .../host-rune-access-injected-css/_config.js | 21 +++++++++++++++++++ .../host-rune-access-injected-css/main.svelte | 16 ++++++++++++++ .../Thing.svelte | 9 ++++++++ .../custom-element-injected-styles/_config.js | 15 +++++++++++++ .../main.svelte | 5 +++++ 10 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 .changeset/shaggy-comics-fail.md create mode 100644 .changeset/wise-hairs-pay.md create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/_config.js create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/Thing.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/main.svelte diff --git a/.changeset/shaggy-comics-fail.md b/.changeset/shaggy-comics-fail.md new file mode 100644 index 0000000000..981a25c978 --- /dev/null +++ b/.changeset/shaggy-comics-fail.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: `append_styles` in an effect to make them available on mount diff --git a/.changeset/wise-hairs-pay.md b/.changeset/wise-hairs-pay.md new file mode 100644 index 0000000000..7d96c1daab --- /dev/null +++ b/.changeset/wise-hairs-pay.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: always inject styles when compiling as a custom element diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index d407b44556..cd44fd998a 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -451,6 +451,8 @@ export function analyze_component(root, source, options) { } } + const is_custom_element = !!options.customElementOptions || options.customElement; + // TODO remove all the ?? stuff, we don't need it now that we're validating the config /** @type {ComponentAnalysis} */ const analysis = { @@ -500,13 +502,13 @@ export function analyze_component(root, source, options) { needs_props: false, event_directive_node: null, uses_event_attributes: false, - custom_element: options.customElementOptions ?? options.customElement, - inject_styles: options.css === 'injected' || options.customElement, - accessors: options.customElement - ? true - : (runes ? false : !!options.accessors) || - // because $set method needs accessors - options.compatibility?.componentApi === 4, + custom_element: is_custom_element, + inject_styles: options.css === 'injected' || is_custom_element, + accessors: + is_custom_element || + (runes ? false : !!options.accessors) || + // because $set method needs accessors + options.compatibility?.componentApi === 4, reactive_statements: new Map(), binding_groups: new Map(), slot_names: new Map(), 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 124438a9da..a56aca9c5f 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 @@ -621,8 +621,9 @@ export function client_component(analysis, options) { ); } - if (analysis.custom_element) { - const ce = analysis.custom_element; + const ce = options.customElementOptions ?? options.customElement; + + if (ce) { const ce_props = typeof ce === 'boolean' ? {} : ce.props || {}; /** @type {ESTree.Property[]} */ diff --git a/packages/svelte/src/internal/client/dom/css.js b/packages/svelte/src/internal/client/dom/css.js index 52be36aa1f..8e6faa0e32 100644 --- a/packages/svelte/src/internal/client/dom/css.js +++ b/packages/svelte/src/internal/client/dom/css.js @@ -1,6 +1,6 @@ import { DEV } from 'esm-env'; -import { queue_micro_task } from './task.js'; import { register_style } from '../dev/css.js'; +import { effect } from '../reactivity/effects.js'; /** * @param {Node} anchor @@ -8,7 +8,7 @@ import { register_style } from '../dev/css.js'; */ export function append_styles(anchor, css) { // Use `queue_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results - queue_micro_task(() => { + effect(() => { var root = anchor.getRootNode(); var target = /** @type {ShadowRoot} */ (root).host diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/_config.js new file mode 100644 index 0000000000..99a223492b --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/host-rune-access-injected-css/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../assert'; +const tick = () => Promise.resolve(); + +export default test({ + async test({ assert, target }) { + target.innerHTML = ''; + /** @type {any} */ + const el = target.querySelector('custom-element'); + + /** @type {string} */ + let html = ''; + const handle_evt = (e) => (html = e.detail); + el.addEventListener('html', handle_evt); + + await tick(); + await tick(); + await tick(); + + assert.ok(html.includes(' + + + + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/Thing.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/Thing.svelte new file mode 100644 index 0000000000..0a2b139274 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/Thing.svelte @@ -0,0 +1,9 @@ + + +

hello

+ + diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/_config.js b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/_config.js new file mode 100644 index 0000000000..74597504bd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/_config.js @@ -0,0 +1,15 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test({ assert, target }) { + const thing = /** @type HTMLElement & { object: { test: true }; } */ ( + target.querySelector('my-thing') + ); + + await tick(); + + assert.include(thing.shadowRoot?.innerHTML, 'red'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/main.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/main.svelte new file mode 100644 index 0000000000..ba5b788da9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-injected-styles/main.svelte @@ -0,0 +1,5 @@ + + + From 51771447c6cf941da7a07733435a5b18a91ddd28 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 27 Jul 2025 16:59:29 -0400 Subject: [PATCH 18/60] chore: remove `parser.template_untrimmed` (#16511) --- .changeset/shiny-berries-call.md | 5 +++++ packages/svelte/src/compiler/phases/1-parse/index.js | 7 ------- .../svelte/src/compiler/phases/1-parse/state/element.js | 8 -------- 3 files changed, 5 insertions(+), 15 deletions(-) create mode 100644 .changeset/shiny-berries-call.md diff --git a/.changeset/shiny-berries-call.md b/.changeset/shiny-berries-call.md new file mode 100644 index 0000000000..adc62b3cd0 --- /dev/null +++ b/.changeset/shiny-berries-call.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: remove `parser.template_untrimmed` diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 77cc2bf3fa..f5e0693b31 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -23,12 +23,6 @@ export class Parser { */ template; - /** - * @readonly - * @type {string} - */ - template_untrimmed; - /** * Whether or not we're in loose parsing mode, in which * case we try to continue parsing as much as possible @@ -67,7 +61,6 @@ export class Parser { } this.loose = loose; - this.template_untrimmed = template; this.template = template.trimEnd(); let match_lang; diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index 87332f647d..ed1b047d55 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -370,14 +370,6 @@ export default function element(parser) { // ... or we're followed by whitespace, for example near the end of the template, // which we want to take in so that language tools has more room to work with parser.allow_whitespace(); - if (parser.index === parser.template.length) { - while ( - parser.index < parser.template_untrimmed.length && - regex_whitespace.test(parser.template_untrimmed[parser.index]) - ) { - parser.index++; - } - } } } } From 03f2e44757925d0ca53f4e563965c3135ef81f16 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:56:41 -0700 Subject: [PATCH 19/60] chore: remove some todos (#16515) * chore: remove some todos * fix * fix * doh * convert `TemplateComment` back to `Comment` * `Evaluation.has_unknown` --- .changeset/happy-countries-dance.md | 5 +++++ packages/svelte/src/compiler/migrate/index.js | 4 ++-- packages/svelte/src/compiler/phases/1-parse/index.js | 1 - .../3-transform/client/visitors/CallExpression.js | 4 +++- .../3-transform/client/visitors/OnDirective.js | 4 ++-- packages/svelte/src/compiler/phases/scope.js | 11 +++++++++++ packages/svelte/src/compiler/types/template.d.ts | 12 +++++++++++- packages/svelte/types/index.d.ts | 12 +++++++++++- 8 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 .changeset/happy-countries-dance.md diff --git a/.changeset/happy-countries-dance.md b/.changeset/happy-countries-dance.md new file mode 100644 index 0000000000..641cf21fd8 --- /dev/null +++ b/.changeset/happy-countries-dance.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: remove some todos diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 6b2e6cda70..eb0e4eff8c 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -1707,14 +1707,14 @@ function extract_type_and_comment(declarator, state, path) { } // Ensure modifiers are applied in the same order as Svelte 4 -const modifier_order = [ +const modifier_order = /** @type {const} */ ([ 'preventDefault', 'stopPropagation', 'stopImmediatePropagation', 'self', 'trusted', 'once' -]; +]); /** * @param {AST.RegularElement | AST.SvelteElement | AST.SvelteWindow | AST.SvelteDocument | AST.SvelteBody} element diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index f5e0693b31..8f7ef76be5 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -1,5 +1,4 @@ /** @import { AST } from '#compiler' */ -/** @import { Comment } from 'estree' */ // @ts-expect-error acorn type definitions are borked in the release we use import { isIdentifierStart, isIdentifierChar } from 'acorn'; import fragment from './state/fragment.js'; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index 3e2f1414e6..c126742d3c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -82,7 +82,9 @@ export function CallExpression(node, context) { ['debug', 'dir', 'error', 'group', 'groupCollapsed', 'info', 'log', 'trace', 'warn'].includes( node.callee.property.name ) && - node.arguments.some((arg) => arg.type !== 'Literal') // TODO more cases? + node.arguments.some( + (arg) => arg.type === 'SpreadElement' || context.state.scope.evaluate(arg).has_unknown + ) ) { return b.call( node.callee, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/OnDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/OnDirective.js index 7a66a8ecbb..0ee3b0fb10 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/OnDirective.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/OnDirective.js @@ -3,14 +3,14 @@ import * as b from '#compiler/builders'; import { build_event, build_event_handler } from './shared/events.js'; -const modifiers = [ +const modifiers = /** @type {const} */ ([ 'stopPropagation', 'stopImmediatePropagation', 'preventDefault', 'self', 'trusted', 'once' -]; +]); /** * @param {AST.OnDirective} node diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 700e098e45..eaacf5dda9 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -180,6 +180,13 @@ class Evaluation { */ is_known = true; + /** + * True if the possible values contains `UNKNOWN` + * @readonly + * @type {boolean} + */ + has_unknown = false; + /** * True if the value is known to not be null/undefined * @readonly @@ -540,6 +547,10 @@ class Evaluation { if (value == null || value === UNKNOWN) { this.is_defined = false; } + + if (value === UNKNOWN) { + this.has_unknown = true; + } } if (this.values.size > 1 || typeof this.value === 'symbol') { diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index de06a41469..058a1a8e66 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -247,7 +247,17 @@ export namespace AST { name: string; /** The 'y' in `on:x={y}` */ expression: null | Expression; - modifiers: string[]; // TODO specify + modifiers: Array< + | 'capture' + | 'nonpassive' + | 'once' + | 'passive' + | 'preventDefault' + | 'self' + | 'stopImmediatePropagation' + | 'stopPropagation' + | 'trusted' + >; /** @internal */ metadata: { expression: ExpressionMetadata; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 9ea45af7e6..64aa9e23ba 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1296,7 +1296,17 @@ declare module 'svelte/compiler' { name: string; /** The 'y' in `on:x={y}` */ expression: null | Expression; - modifiers: string[]; + modifiers: Array< + | 'capture' + | 'nonpassive' + | 'once' + | 'passive' + | 'preventDefault' + | 'self' + | 'stopImmediatePropagation' + | 'stopPropagation' + | 'trusted' + >; } /** A `style:` directive */ From 48f2fa22c0c941ac6e550151212845adc628b3de Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 28 Jul 2025 12:04:06 -0700 Subject: [PATCH 20/60] fix: allow await expressions inside `{#await ...}` argument (#16514) * fix: allow await expressions inside `{#await ...}` argument * add test * apply suggestion from review --------- Co-authored-by: 7nik --- .changeset/long-roses-train.md | 5 +++ .../3-transform/client/visitors/AwaitBlock.js | 7 ++- .../samples/async-await/_config.js | 43 +++++++++++++++++++ .../samples/async-await/main.svelte | 22 ++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 .changeset/long-roses-train.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-await/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-await/main.svelte diff --git a/.changeset/long-roses-train.md b/.changeset/long-roses-train.md new file mode 100644 index 0000000000..10920ac5c4 --- /dev/null +++ b/.changeset/long-roses-train.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow await expressions inside `{#await ...}` argument diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js index c550c8e17b..4246091bcf 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js @@ -1,7 +1,7 @@ /** @import { BlockStatement, Pattern, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */ -import { extract_identifiers } from '../../../../utils/ast.js'; +import { extract_identifiers, is_expression_async } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { create_derived } from '../utils.js'; import { get_value } from './shared/declarations.js'; @@ -15,7 +15,10 @@ export function AwaitBlock(node, context) { context.state.template.push_comment(); // Visit {#await } first to ensure that scopes are in the correct order - const expression = b.thunk(build_expression(context, node.expression, node.metadata.expression)); + const expression = b.thunk( + build_expression(context, node.expression, node.metadata.expression), + node.metadata.expression.has_await + ); let then_block; let catch_block; diff --git a/packages/svelte/tests/runtime-runes/samples/async-await/_config.js b/packages/svelte/tests/runtime-runes/samples/async-await/_config.js new file mode 100644 index 0000000000..dda6a7a895 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-await/_config.js @@ -0,0 +1,43 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [reset, one, two, reject] = target.querySelectorAll('button'); + + await tick(); + assert.htmlEqual( + target.innerHTML, + ' waiting' + ); + + one.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ' one_res' + ); + + reset.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ' waiting' + ); + + two.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ' two_res' + ); + + reset.click(); + reject.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ' reject_catch' + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-await/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-await/main.svelte new file mode 100644 index 0000000000..8673e45414 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-await/main.svelte @@ -0,0 +1,22 @@ + + + + + + + + + {#await await deferred.promise + "_res"} + waiting + {:then res} + {res} + {:catch err} + {err}_catch + {/await} + + {#snippet pending()} +

pending

+ {/snippet} +
From d82edf6b1dfaac8af70462b1009f3b9da47a701a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 12:07:57 -0700 Subject: [PATCH 21/60] Version Packages (#16513) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/happy-countries-dance.md | 5 ----- .changeset/long-roses-train.md | 5 ----- .changeset/shaggy-comics-fail.md | 5 ----- .changeset/shiny-berries-call.md | 5 ----- .changeset/wise-hairs-pay.md | 5 ----- packages/svelte/CHANGELOG.md | 14 ++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 8 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 .changeset/happy-countries-dance.md delete mode 100644 .changeset/long-roses-train.md delete mode 100644 .changeset/shaggy-comics-fail.md delete mode 100644 .changeset/shiny-berries-call.md delete mode 100644 .changeset/wise-hairs-pay.md diff --git a/.changeset/happy-countries-dance.md b/.changeset/happy-countries-dance.md deleted file mode 100644 index 641cf21fd8..0000000000 --- a/.changeset/happy-countries-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: remove some todos diff --git a/.changeset/long-roses-train.md b/.changeset/long-roses-train.md deleted file mode 100644 index 10920ac5c4..0000000000 --- a/.changeset/long-roses-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: allow await expressions inside `{#await ...}` argument diff --git a/.changeset/shaggy-comics-fail.md b/.changeset/shaggy-comics-fail.md deleted file mode 100644 index 981a25c978..0000000000 --- a/.changeset/shaggy-comics-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: `append_styles` in an effect to make them available on mount diff --git a/.changeset/shiny-berries-call.md b/.changeset/shiny-berries-call.md deleted file mode 100644 index adc62b3cd0..0000000000 --- a/.changeset/shiny-berries-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: remove `parser.template_untrimmed` diff --git a/.changeset/wise-hairs-pay.md b/.changeset/wise-hairs-pay.md deleted file mode 100644 index 7d96c1daab..0000000000 --- a/.changeset/wise-hairs-pay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: always inject styles when compiling as a custom element diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index f939f69d28..59c44dd4f9 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,19 @@ # svelte +## 5.37.1 + +### Patch Changes + +- chore: remove some todos ([#16515](https://github.com/sveltejs/svelte/pull/16515)) + +- fix: allow await expressions inside `{#await ...}` argument ([#16514](https://github.com/sveltejs/svelte/pull/16514)) + +- fix: `append_styles` in an effect to make them available on mount ([#16509](https://github.com/sveltejs/svelte/pull/16509)) + +- chore: remove `parser.template_untrimmed` ([#16511](https://github.com/sveltejs/svelte/pull/16511)) + +- fix: always inject styles when compiling as a custom element ([#16509](https://github.com/sveltejs/svelte/pull/16509)) + ## 5.37.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index c781eda8c8..826850ea60 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.37.0", + "version": "5.37.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index e83ba6fb30..2d5083a2a5 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.37.0'; +export const VERSION = '5.37.1'; export const PUBLIC_VERSION = '5'; From a91e0154d4bc4b0a3209956647f868e518d0acba Mon Sep 17 00:00:00 2001 From: Laszlo Korte Date: Thu, 31 Jul 2025 09:45:52 +0200 Subject: [PATCH 22/60] fix: double event processing in Firefox (#16522) (#16527) * Fix double event processing (#16522) Firefox seems to garbage collect event objects during event propagation if no global reference to the event object is kept. That discards the __root marker set on the event object to early, leading to duplicate processing. * minor tweaks --------- Co-authored-by: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> --- .changeset/chilly-bananas-train.md | 5 +++++ .../svelte/src/internal/client/dom/elements/events.js | 9 +++++++++ 2 files changed, 14 insertions(+) create mode 100644 .changeset/chilly-bananas-train.md diff --git a/.changeset/chilly-bananas-train.md b/.changeset/chilly-bananas-train.md new file mode 100644 index 0000000000..b0305f9e61 --- /dev/null +++ b/.changeset/chilly-bananas-train.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: double event processing in firefox due to event object being garbage collected diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index fa3bf0b021..19bd1cfa18 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -141,6 +141,13 @@ export function delegate(events) { } } +// used to store the reference to the currently propagated event +// to prevent garbage collection between microtasks in Firefox +// If the event object is GCed too early, the expando __root property +// set on the event object is lost, causing the event delegation +// to process the event twice +let last_propagated_event = null; + /** * @this {EventTarget} * @param {Event} event @@ -153,6 +160,8 @@ export function handle_event_propagation(event) { var path = event.composedPath?.() || []; var current_target = /** @type {null | Element} */ (path[0] || event.target); + last_propagated_event = event; + // composedPath contains list of nodes the event has propagated through. // We check __root to skip all nodes below it in case this is a // parent of the __root node, which indicates that there's nested From f5950f866c51d9325a432ebfd5362bcf2647763a Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 31 Jul 2025 02:11:52 -0700 Subject: [PATCH 23/60] fix: correctly differentiate static fields before emitting `duplicate_class_field` (#16526) * fix: correctly differentiate static fields before emitting `duplicate_class_field` * remove unnecessary `#` concatenation * add test --- .changeset/nine-cups-film.md | 5 +++++ .../src/compiler/phases/2-analyze/visitors/ClassBody.js | 6 +++--- .../samples/class-state-constructor-9/input.svelte.js | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/nine-cups-film.md diff --git a/.changeset/nine-cups-film.md b/.changeset/nine-cups-film.md new file mode 100644 index 0000000000..ba72337dac --- /dev/null +++ b/.changeset/nine-cups-film.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly differentiate static fields before emitting `duplicate_class_field` diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js index 2bfc1dbce3..dd21637174 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js @@ -57,7 +57,7 @@ export function ClassBody(node, context) { e.state_field_duplicate(node, name); } - const _key = (key.type === 'PrivateIdentifier' ? '#' : '') + name; + const _key = (node.type === 'AssignmentExpression' || !node.static ? '' : '@') + name; const field = fields.get(_key); // if there's already a method or assigned field, error @@ -78,7 +78,7 @@ export function ClassBody(node, context) { for (const child of node.body) { if (child.type === 'PropertyDefinition' && !child.computed && !child.static) { handle(child, child.key, child.value); - const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key); + const key = /** @type {string} */ (get_name(child.key)); const field = fields.get(key); if (!field) { fields.set(key, [child.value ? 'assigned_prop' : 'prop']); @@ -91,7 +91,7 @@ export function ClassBody(node, context) { if (child.kind === 'constructor') { constructor = child; } else if (!child.computed) { - const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key); + const key = (child.static ? '@' : '') + get_name(child.key); const field = fields.get(key); if (!field) { fields.set(key, [child.kind]); diff --git a/packages/svelte/tests/validator/samples/class-state-constructor-9/input.svelte.js b/packages/svelte/tests/validator/samples/class-state-constructor-9/input.svelte.js index a8469e13af..3806046f3f 100644 --- a/packages/svelte/tests/validator/samples/class-state-constructor-9/input.svelte.js +++ b/packages/svelte/tests/validator/samples/class-state-constructor-9/input.svelte.js @@ -1,6 +1,6 @@ export class Counter { count = -1; - + static count() {} constructor() { this.count = $state(0); } From 72e46d31c075eeea232a1cc320efc272f5ef87d7 Mon Sep 17 00:00:00 2001 From: Elliot Bentley Date: Thu, 31 Jul 2025 11:34:28 +0100 Subject: [PATCH 24/60] fix: add types for SVG bindable attributes (#16525) * fix: types for SVG bind: attributes * add changeset and tweak --------- Co-authored-by: 7nik --- .changeset/cuddly-feet-doubt.md | 5 +++++ packages/svelte/elements.d.ts | 14 ++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 .changeset/cuddly-feet-doubt.md diff --git a/.changeset/cuddly-feet-doubt.md b/.changeset/cuddly-feet-doubt.md new file mode 100644 index 0000000000..8d7d955daa --- /dev/null +++ b/.changeset/cuddly-feet-doubt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: add bindable dimension attributes types to SVG and MathML elements diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 2e1042dfd6..b3d44d9ed6 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -464,6 +464,14 @@ export interface DOMAttributes { onfullscreenerror?: EventHandler | undefined | null; onfullscreenerrorcapture?: EventHandler | undefined | null; + // Dimensions + readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null; + readonly 'bind:contentBoxSize'?: Array | undefined | null; + readonly 'bind:borderBoxSize'?: Array | undefined | null; + readonly 'bind:devicePixelContentBoxSize'?: Array | undefined | null; + readonly 'bind:clientWidth'?: number | undefined | null; + readonly 'bind:clientHeight'?: number | undefined | null; + xmlns?: string | undefined | null; } @@ -839,13 +847,7 @@ export interface HTMLAttributes extends AriaAttributes, D */ 'bind:innerText'?: string | undefined | null; - readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null; - readonly 'bind:contentBoxSize'?: Array | undefined | null; - readonly 'bind:borderBoxSize'?: Array | undefined | null; - readonly 'bind:devicePixelContentBoxSize'?: Array | undefined | null; readonly 'bind:focused'?: boolean | undefined | null; - readonly 'bind:clientWidth'?: number | undefined | null; - readonly 'bind:clientHeight'?: number | undefined | null; readonly 'bind:offsetWidth'?: number | undefined | null; readonly 'bind:offsetHeight'?: number | undefined | null; From c04975d3db74e59f8612ffd0f64d3b3bb3133ef1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 31 Jul 2025 18:28:01 -0400 Subject: [PATCH 25/60] fix: prevent last_propagated_event from being DCE'd (#16538) * fix: prevent last_propagated_event from being DCE'd * changeset --- .changeset/serious-cars-hear.md | 5 +++++ packages/svelte/src/internal/client/dom/elements/events.js | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .changeset/serious-cars-hear.md diff --git a/.changeset/serious-cars-hear.md b/.changeset/serious-cars-hear.md new file mode 100644 index 0000000000..4647e89ed9 --- /dev/null +++ b/.changeset/serious-cars-hear.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent last_propagated_event from being DCE'd diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 19bd1cfa18..15544d7426 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -168,8 +168,11 @@ export function handle_event_propagation(event) { // mounted apps. In this case we don't want to trigger events multiple times. var path_idx = 0; + // the `last_propagated_event === event` check is redundant, but + // without it the variable will be DCE'd and things will + // fail mysteriously in Firefox // @ts-expect-error is added below - var handled_at = event.__root; + var handled_at = last_propagated_event === event && event.__root; if (handled_at) { var at_idx = path.indexOf(handled_at); From 9134856fece750608ba81da45c4a330b3d8ebb1e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:15:45 -0400 Subject: [PATCH 26/60] Version Packages (#16529) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/chilly-bananas-train.md | 5 ----- .changeset/cuddly-feet-doubt.md | 5 ----- .changeset/nine-cups-film.md | 5 ----- .changeset/serious-cars-hear.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/chilly-bananas-train.md delete mode 100644 .changeset/cuddly-feet-doubt.md delete mode 100644 .changeset/nine-cups-film.md delete mode 100644 .changeset/serious-cars-hear.md diff --git a/.changeset/chilly-bananas-train.md b/.changeset/chilly-bananas-train.md deleted file mode 100644 index b0305f9e61..0000000000 --- a/.changeset/chilly-bananas-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: double event processing in firefox due to event object being garbage collected diff --git a/.changeset/cuddly-feet-doubt.md b/.changeset/cuddly-feet-doubt.md deleted file mode 100644 index 8d7d955daa..0000000000 --- a/.changeset/cuddly-feet-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: add bindable dimension attributes types to SVG and MathML elements diff --git a/.changeset/nine-cups-film.md b/.changeset/nine-cups-film.md deleted file mode 100644 index ba72337dac..0000000000 --- a/.changeset/nine-cups-film.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly differentiate static fields before emitting `duplicate_class_field` diff --git a/.changeset/serious-cars-hear.md b/.changeset/serious-cars-hear.md deleted file mode 100644 index 4647e89ed9..0000000000 --- a/.changeset/serious-cars-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prevent last_propagated_event from being DCE'd diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 59c44dd4f9..6e0a199ddc 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.37.2 + +### Patch Changes + +- fix: double event processing in firefox due to event object being garbage collected ([#16527](https://github.com/sveltejs/svelte/pull/16527)) + +- fix: add bindable dimension attributes types to SVG and MathML elements ([#16525](https://github.com/sveltejs/svelte/pull/16525)) + +- fix: correctly differentiate static fields before emitting `duplicate_class_field` ([#16526](https://github.com/sveltejs/svelte/pull/16526)) + +- fix: prevent last_propagated_event from being DCE'd ([#16538](https://github.com/sveltejs/svelte/pull/16538)) + ## 5.37.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 826850ea60..8536135ca8 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.37.1", + "version": "5.37.2", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 2d5083a2a5..b8f3bcfdd5 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.37.1'; +export const VERSION = '5.37.2'; export const PUBLIC_VERSION = '5'; From 80678411706e99c0224b8bf995959c2d4e7fd4db Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:16:47 -0700 Subject: [PATCH 27/60] docs: add note about attachments to `svelte/action` docs (#16537) --- documentation/docs/98-reference/21-svelte-action.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/docs/98-reference/21-svelte-action.md b/documentation/docs/98-reference/21-svelte-action.md index 53423ec409..ef3ebfbf70 100644 --- a/documentation/docs/98-reference/21-svelte-action.md +++ b/documentation/docs/98-reference/21-svelte-action.md @@ -2,4 +2,6 @@ title: svelte/action --- +This module provides types for [actions](use), which have been superseded by [attachments](@attach). + > MODULE: svelte/action From 540f1b19ec56a69dde85f24471f25d9d465937e4 Mon Sep 17 00:00:00 2001 From: 7nik Date: Sat, 2 Aug 2025 22:29:10 +0300 Subject: [PATCH 28/60] fix: reset attribute cache after setting corresponding property (#16543) --- .changeset/afraid-carrots-study.md | 5 +++++ .../client/dom/elements/attributes.js | 4 +++- .../attribute-after-property/_config.js | 19 +++++++++++++++++++ .../attribute-after-property/main.svelte | 6 ++++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 .changeset/afraid-carrots-study.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/attribute-after-property/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/attribute-after-property/main.svelte diff --git a/.changeset/afraid-carrots-study.md b/.changeset/afraid-carrots-study.md new file mode 100644 index 0000000000..71f4232edb --- /dev/null +++ b/.changeset/afraid-carrots-study.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: reset attribute cache after setting corresponding property diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 22e532f5e4..2fa5d4541c 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -19,7 +19,7 @@ import { attach } from './attachments.js'; import { clsx } from '../../../shared/attributes.js'; import { set_class } from './class.js'; import { set_style } from './style.js'; -import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js'; +import { ATTACHMENT_KEY, NAMESPACE_HTML, UNINITIALIZED } from '../../../../constants.js'; import { block, branch, destroy_effect, effect } from '../../reactivity/effects.js'; import { init_select, select_option } from './bindings/select.js'; import { flatten } from '../../reactivity/async.js'; @@ -446,6 +446,8 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal ) { // @ts-ignore element[name] = value; + // remove it from attributes's cache + if (name in attributes) attributes[name] = UNINITIALIZED; } else if (typeof value !== 'function') { set_attribute(element, name, value, skip_warning); } diff --git a/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/_config.js b/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/_config.js new file mode 100644 index 0000000000..f6a98b1797 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/_config.js @@ -0,0 +1,19 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ target, assert }) { + const input = target.querySelector('input'); + const button = target.querySelector('button'); + + assert.equal(input?.step, 'any'); + + button?.click(); + flushSync(); + assert.equal(input?.step, '10'); + + button?.click(); + flushSync(); + assert.equal(input?.step, 'any'); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/main.svelte b/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/main.svelte new file mode 100644 index 0000000000..2921e4e241 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/attribute-after-property/main.svelte @@ -0,0 +1,6 @@ + + + + \ No newline at end of file From 0cbb5becf6d62ae3334b78606a76d21a91eda32b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 2 Aug 2025 21:17:58 -0700 Subject: [PATCH 29/60] Version Packages (#16544) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/afraid-carrots-study.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/afraid-carrots-study.md diff --git a/.changeset/afraid-carrots-study.md b/.changeset/afraid-carrots-study.md deleted file mode 100644 index 71f4232edb..0000000000 --- a/.changeset/afraid-carrots-study.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: reset attribute cache after setting corresponding property diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 6e0a199ddc..9766e3f06b 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.37.3 + +### Patch Changes + +- fix: reset attribute cache after setting corresponding property ([#16543](https://github.com/sveltejs/svelte/pull/16543)) + ## 5.37.2 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 8536135ca8..e8927fcf56 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.37.2", + "version": "5.37.3", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index b8f3bcfdd5..127b43e205 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.37.2'; +export const VERSION = '5.37.3'; export const PUBLIC_VERSION = '5'; From a3f18351af4f70df4b9029aaa7198ac5dfeac6dc Mon Sep 17 00:00:00 2001 From: Anthony Date: Tue, 5 Aug 2025 05:38:30 +0200 Subject: [PATCH 30/60] docs: corrections (#16550) * Add missing hyphen in "server-side rendering" * Fix incorrect use of "as such" * regenerate types --------- Co-authored-by: Chew Tee Ming --- documentation/docs/02-runes/04-$effect.md | 2 +- documentation/docs/03-template-syntax/06-snippet.md | 2 +- documentation/docs/03-template-syntax/08-@html.md | 2 +- .../docs/05-special-elements/07-svelte-options.md | 2 +- documentation/docs/06-runtime/03-lifecycle-hooks.md | 2 +- documentation/docs/07-misc/07-v5-migration-guide.md | 12 ++++++------ .../docs/98-reference/.generated/compile-errors.md | 2 +- .../docs/98-reference/.generated/compile-warnings.md | 2 +- packages/svelte/messages/compile-errors/script.md | 2 +- .../svelte/messages/compile-warnings/template.md | 2 +- packages/svelte/src/ambient.d.ts | 4 ++-- packages/svelte/types/index.d.ts | 4 ++-- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md index 5820e178a0..6c42f55795 100644 --- a/documentation/docs/02-runes/04-$effect.md +++ b/documentation/docs/02-runes/04-$effect.md @@ -135,7 +135,7 @@ An effect only reruns when the object it reads changes, not when a property insi An effect only depends on the values that it read the last time it ran. This has interesting implications for effects that have conditional code. -For instance, if `condition` is `true` in the code snippet below, the code inside the `if` block will run and `color` will be evaluated. As such, changes to either `condition` or `color` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE21RQW6DMBD8ytaNBJHaJFLViwNIVZ8RcnBgXVk1xsILTYT4e20TQg89IOPZ2fHM7siMaJBx9tmaWpFqjQNlAKXEihx7YVJpdIyfRkY3G4gB8Pi97cPanRtQU8AuwuF_eNUaQuPlOMtc1SlLRWlKUo1tOwJflUikQHZtA0klzCDc64Imx0ANn8bInV1CDhtHgjClrsftcSXotluLybOUb3g4JJHhOZs5WZpuIS9gjNqkJKQP5e2ClrR4SMdZ13E4xZ8zTPOTJU2A2uE_PQ9COCI926_hTVarIU4hu_REPlBrKq2q73ycrf1N-vS4TMUsulaVg3EtR8H9rFgsg8uUsT1B2F9eshigZHBRpuaD0D3mY8Qm2BfB5N2YyRzdNEYVDy0Ja-WsFjcOUuP1HvFLWA6H3XuHTUSmmDV2--0TXonxsKbp7G9C6R__NONS-MFNvxj_d6mBAgAA). +For instance, if `condition` is `true` in the code snippet below, the code inside the `if` block will run and `color` will be evaluated. This means that changes to either `condition` or `color` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE21RQW6DMBD8ytaNBJHaJFLViwNIVZ8RcnBgXVk1xsILTYT4e20TQg89IOPZ2fHM7siMaJBx9tmaWpFqjQNlAKXEihx7YVJpdIyfRkY3G4gB8Pi97cPanRtQU8AuwuF_eNUaQuPlOMtc1SlLRWlKUo1tOwJflUikQHZtA0klzCDc64Imx0ANn8bInV1CDhtHgjClrsftcSXotluLybOUb3g4JJHhOZs5WZpuIS9gjNqkJKQP5e2ClrR4SMdZ13E4xZ8zTPOTJU2A2uE_PQ9COCI926_hTVarIU4hu_REPlBrKq2q73ycrf1N-vS4TMUsulaVg3EtR8H9rFgsg8uUsT1B2F9eshigZHBRpuaD0D3mY8Qm2BfB5N2YyRzdNEYVDy0Ja-WsFjcOUuP1HvFLWA6H3XuHTUSmmDV2--0TXonxsKbp7G9C6R__NONS-MFNvxj_d6mBAgAA). Conversely, if `condition` is `false`, `color` will not be evaluated, and the effect will _only_ re-run again when `condition` changes. diff --git a/documentation/docs/03-template-syntax/06-snippet.md b/documentation/docs/03-template-syntax/06-snippet.md index ab536c6e5c..02f58e0f6c 100644 --- a/documentation/docs/03-template-syntax/06-snippet.md +++ b/documentation/docs/03-template-syntax/06-snippet.md @@ -277,4 +277,4 @@ Snippets can be created programmatically with the [`createRawSnippet`](svelte#cr ## Snippets and slots -In Svelte 4, content can be passed to components using [slots](legacy-slots). Snippets are more powerful and flexible, and as such slots are deprecated in Svelte 5. +In Svelte 4, content can be passed to components using [slots](legacy-slots). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5. diff --git a/documentation/docs/03-template-syntax/08-@html.md b/documentation/docs/03-template-syntax/08-@html.md index 30456fa666..a92accd093 100644 --- a/documentation/docs/03-template-syntax/08-@html.md +++ b/documentation/docs/03-template-syntax/08-@html.md @@ -22,7 +22,7 @@ It also will not compile Svelte code. ## Styling -Content rendered this way is 'invisible' to Svelte and as such will not receive [scoped styles](scoped-styles) — in other words, this will not work, and the `a` and `img` styles will be regarded as unused: +Content rendered this way is 'invisible' to Svelte and thus will not receive [scoped styles](scoped-styles). In other words, this will not work, and the `a` and `img` styles will be regarded as unused: ```svelte diff --git a/documentation/docs/05-special-elements/07-svelte-options.md b/documentation/docs/05-special-elements/07-svelte-options.md index d29042e278..a2e47aa04f 100644 --- a/documentation/docs/05-special-elements/07-svelte-options.md +++ b/documentation/docs/05-special-elements/07-svelte-options.md @@ -12,7 +12,7 @@ The `` element provides a place to specify per-component compile - `runes={false}` — forces a component into _legacy mode_ - `namespace="..."` — the namespace where this component will be used, can be "html" (the default), "svg" or "mathml" - `customElement={...}` — the [options](custom-elements#Component-options) to use when compiling this component as a custom element. If a string is passed, it is used as the `tag` option -- `css="injected"` — the component will inject its styles inline: During server side rendering, it's injected as a `