From 2af7ba21563f1b7f011ffa9f0c08e4acb473eef3 Mon Sep 17 00:00:00 2001 From: Fedor Nezhivoi Date: Tue, 24 Jun 2025 20:05:24 +0700 Subject: [PATCH 001/148] fix: lift unsafe_state_mutation constraints for SvelteSet and SvelteMap created inside the derived (#16221) Co-authored-by: Fedor Nezhivoi --- .changeset/fair-bats-visit.md | 5 ++++ packages/svelte/src/reactivity/date.js | 4 +-- packages/svelte/src/reactivity/map.js | 6 ++--- packages/svelte/src/reactivity/set.js | 6 ++--- .../src/reactivity/url-search-params.js | 4 +-- packages/svelte/src/reactivity/url.js | 18 ++++++------- .../side-effect-derived-array/_config.js | 23 ++++++++++++++++ .../side-effect-derived-array/main.svelte | 25 +++++++++++++++++ .../side-effect-derived-date/_config.js | 23 ++++++++++++++++ .../side-effect-derived-date/main.svelte | 27 +++++++++++++++++++ .../side-effect-derived-map/_config.js | 23 ++++++++++++++++ .../side-effect-derived-map/main.svelte | 27 +++++++++++++++++++ .../side-effect-derived-object/_config.js | 23 ++++++++++++++++ .../side-effect-derived-object/main.svelte | 25 +++++++++++++++++ .../side-effect-derived-primitive/_config.js | 23 ++++++++++++++++ .../side-effect-derived-primitive/main.svelte | 25 +++++++++++++++++ .../side-effect-derived-set/_config.js | 23 ++++++++++++++++ .../side-effect-derived-set/main.svelte | 27 +++++++++++++++++++ .../_config.js | 23 ++++++++++++++++ .../main.svelte | 27 +++++++++++++++++++ .../side-effect-derived-url/_config.js | 23 ++++++++++++++++ .../side-effect-derived-url/main.svelte | 27 +++++++++++++++++++ 22 files changed, 418 insertions(+), 19 deletions(-) create mode 100644 .changeset/fair-bats-visit.md create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/main.svelte diff --git a/.changeset/fair-bats-visit.md b/.changeset/fair-bats-visit.md new file mode 100644 index 0000000000..bc0a426395 --- /dev/null +++ b/.changeset/fair-bats-visit.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +lift unsafe_state_mutation constraints for SvelteSet, SvelteMap, SvelteDate, SvelteURL and SvelteURLSearchParams created inside the derived diff --git a/packages/svelte/src/reactivity/date.js b/packages/svelte/src/reactivity/date.js index 4176f0ceec..8e2b5d41ab 100644 --- a/packages/svelte/src/reactivity/date.js +++ b/packages/svelte/src/reactivity/date.js @@ -1,6 +1,6 @@ /** @import { Source } from '#client' */ import { derived } from '../internal/client/index.js'; -import { source, set } from '../internal/client/reactivity/sources.js'; +import { set, state } from '../internal/client/reactivity/sources.js'; import { tag } from '../internal/client/dev/tracing.js'; import { active_reaction, get, set_active_reaction } from '../internal/client/runtime.js'; import { DEV } from 'esm-env'; @@ -40,7 +40,7 @@ var inited = false; * ``` */ export class SvelteDate extends Date { - #time = source(super.getTime()); + #time = state(super.getTime()); /** @type {Map>} */ #deriveds = new Map(); diff --git a/packages/svelte/src/reactivity/map.js b/packages/svelte/src/reactivity/map.js index eed163dbf2..cd2fac163f 100644 --- a/packages/svelte/src/reactivity/map.js +++ b/packages/svelte/src/reactivity/map.js @@ -1,6 +1,6 @@ /** @import { Source } from '#client' */ import { DEV } from 'esm-env'; -import { set, source } from '../internal/client/reactivity/sources.js'; +import { set, source, state } from '../internal/client/reactivity/sources.js'; import { label, tag } from '../internal/client/dev/tracing.js'; import { get } from '../internal/client/runtime.js'; import { increment } from './utils.js'; @@ -54,8 +54,8 @@ import { increment } from './utils.js'; export class SvelteMap extends Map { /** @type {Map>} */ #sources = new Map(); - #version = source(0); - #size = source(0); + #version = state(0); + #size = state(0); /** * @param {Iterable | null | undefined} [value] diff --git a/packages/svelte/src/reactivity/set.js b/packages/svelte/src/reactivity/set.js index fd22014cb3..8a656c2bc1 100644 --- a/packages/svelte/src/reactivity/set.js +++ b/packages/svelte/src/reactivity/set.js @@ -1,6 +1,6 @@ /** @import { Source } from '#client' */ import { DEV } from 'esm-env'; -import { source, set } from '../internal/client/reactivity/sources.js'; +import { source, set, state } from '../internal/client/reactivity/sources.js'; import { label, tag } from '../internal/client/dev/tracing.js'; import { get } from '../internal/client/runtime.js'; import { increment } from './utils.js'; @@ -48,8 +48,8 @@ var inited = false; export class SvelteSet extends Set { /** @type {Map>} */ #sources = new Map(); - #version = source(0); - #size = source(0); + #version = state(0); + #size = state(0); /** * @param {Iterable | null | undefined} [value] diff --git a/packages/svelte/src/reactivity/url-search-params.js b/packages/svelte/src/reactivity/url-search-params.js index c77ff9c822..389da7cdb6 100644 --- a/packages/svelte/src/reactivity/url-search-params.js +++ b/packages/svelte/src/reactivity/url-search-params.js @@ -1,5 +1,5 @@ import { DEV } from 'esm-env'; -import { source } from '../internal/client/reactivity/sources.js'; +import { state } from '../internal/client/reactivity/sources.js'; import { tag } from '../internal/client/dev/tracing.js'; import { get } from '../internal/client/runtime.js'; import { get_current_url } from './url.js'; @@ -34,7 +34,7 @@ export const REPLACE = Symbol(); * ``` */ export class SvelteURLSearchParams extends URLSearchParams { - #version = DEV ? tag(source(0), 'SvelteURLSearchParams version') : source(0); + #version = DEV ? tag(state(0), 'SvelteURLSearchParams version') : state(0); #url = get_current_url(); #updating = false; diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index 56732a0402..dfede23f6e 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -1,5 +1,5 @@ import { DEV } from 'esm-env'; -import { source, set } from '../internal/client/reactivity/sources.js'; +import { set, state } from '../internal/client/reactivity/sources.js'; import { tag } from '../internal/client/dev/tracing.js'; import { get } from '../internal/client/runtime.js'; import { REPLACE, SvelteURLSearchParams } from './url-search-params.js'; @@ -40,14 +40,14 @@ export function get_current_url() { * ``` */ export class SvelteURL extends URL { - #protocol = source(super.protocol); - #username = source(super.username); - #password = source(super.password); - #hostname = source(super.hostname); - #port = source(super.port); - #pathname = source(super.pathname); - #hash = source(super.hash); - #search = source(super.search); + #protocol = state(super.protocol); + #username = state(super.username); + #password = state(super.password); + #hostname = state(super.hostname); + #port = state(super.port); + #pathname = state(super.pathname); + #hash = state(super.hash); + #search = state(super.search); #searchParams; /** diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/_config.js new file mode 100644 index 0000000000..5ad6f57e31 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/main.svelte new file mode 100644 index 0000000000..6468dbebc9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/main.svelte @@ -0,0 +1,25 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/_config.js new file mode 100644 index 0000000000..5ad6f57e31 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/main.svelte new file mode 100644 index 0000000000..a3c6aa629f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/_config.js new file mode 100644 index 0000000000..5ad6f57e31 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/main.svelte new file mode 100644 index 0000000000..bdd5ccb75c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/_config.js new file mode 100644 index 0000000000..5ad6f57e31 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/main.svelte new file mode 100644 index 0000000000..2174bde59f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/main.svelte @@ -0,0 +1,25 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/_config.js new file mode 100644 index 0000000000..5ad6f57e31 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/main.svelte new file mode 100644 index 0000000000..21de8b9d91 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/main.svelte @@ -0,0 +1,25 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/_config.js new file mode 100644 index 0000000000..5ad6f57e31 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/main.svelte new file mode 100644 index 0000000000..8564f6e7c4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/_config.js new file mode 100644 index 0000000000..5ad6f57e31 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/main.svelte new file mode 100644 index 0000000000..014a1e4e6d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/_config.js new file mode 100644 index 0000000000..5ad6f57e31 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/main.svelte new file mode 100644 index 0000000000..69ead384c3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + From 922bee9a6d44c2a0313766a9a996411acf451408 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Wed, 25 Jun 2025 11:35:03 +0200 Subject: [PATCH 002/148] fix: use fine grained for template if the component is not explicitly in legacy mode (#16232) * fix: use fine grained for template if the component is not explicitly in legacy mode * chore: add comment Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * fix: add `LabeledStatement` to `instance.ast` check * fix: spread `keys` * fix: snapshots * fix: use `compileOption.runes` if defined + add other tests --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/cold-dingos-dream.md | 5 ++++ .../src/compiler/phases/2-analyze/index.js | 23 +++++++++++++++++++ .../client/visitors/shared/utils.js | 5 +++- .../svelte/src/compiler/phases/types.d.ts | 1 + .../svelte/tests/runtime-legacy/shared.ts | 5 +++- .../_config.js | 17 ++++++++++++++ .../main.svelte | 8 +++++++ .../test.svelte.js | 9 ++++++++ .../_config.js | 17 ++++++++++++++ .../main.svelte | 9 ++++++++ .../test.svelte.js | 9 ++++++++ .../_config.js | 17 ++++++++++++++ .../main.svelte | 9 ++++++++ .../test.svelte.js | 9 ++++++++ .../_config.js | 17 ++++++++++++++ .../main.svelte | 9 ++++++++ .../test.svelte.js | 9 ++++++++ .../_config.js | 17 ++++++++++++++ .../main.svelte | 9 ++++++++ .../test.svelte.js | 9 ++++++++ .../_config.js | 17 ++++++++++++++ .../main.svelte | 10 ++++++++ .../test.svelte.js | 9 ++++++++ .../_config.js | 17 ++++++++++++++ .../main.svelte | 12 ++++++++++ .../test.svelte.js | 9 ++++++++ .../samples/legacy-runes-ambiguous/_config.js | 17 ++++++++++++++ .../legacy-runes-ambiguous/main.svelte | 7 ++++++ .../legacy-runes-ambiguous/test.svelte.js | 9 ++++++++ .../purity/_expected/client/index.svelte.js | 6 ++--- 30 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 .changeset/cold-dingos-dream.md create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/test.svelte.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/test.svelte.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/test.svelte.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/test.svelte.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/test.svelte.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/test.svelte.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/test.svelte.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/test.svelte.js diff --git a/.changeset/cold-dingos-dream.md b/.changeset/cold-dingos-dream.md new file mode 100644 index 0000000000..c48414abc2 --- /dev/null +++ b/.changeset/cold-dingos-dream.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: use fine grained for template if the component is not explicitly in legacy mode diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index fded183b86..80adc10c1a 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -431,6 +431,29 @@ export function analyze_component(root, source, options) { template, elements: [], runes, + // if we are not in runes mode but we have no reserved references ($$props, $$restProps) + // and no `export let` we might be in a wannabe runes component that is using runes in an external + // module...we need to fallback to the runic behavior + maybe_runes: + !runes && + // if they explicitly disabled runes, use the legacy behavior + options.runes !== false && + ![...module.scope.references.keys()].some((name) => + ['$$props', '$$restProps'].includes(name) + ) && + !instance.ast.body.some( + (node) => + node.type === 'LabeledStatement' || + (node.type === 'ExportNamedDeclaration' && + ((node.declaration && + node.declaration.type === 'VariableDeclaration' && + node.declaration.kind === 'let') || + node.specifiers.some( + (specifier) => + specifier.local.type === 'Identifier' && + instance.scope.get(specifier.local.name)?.declaration_kind === 'let' + ))) + ), tracing: false, classes: new Map(), immutable: runes || options.immutable, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index 15982899c9..c861a51b7d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -370,7 +370,10 @@ export function validate_mutation(node, context, expression) { export function build_expression(context, expression, metadata, state = context.state) { const value = /** @type {Expression} */ (context.visit(expression, state)); - if (context.state.analysis.runes) { + // Components not explicitly in legacy mode might be expected to be in runes mode (especially since we didn't + // adjust this behavior until recently, which broke people's existing components), so we also bail in this case. + // Kind of an in-between-mode. + if (context.state.analysis.runes || context.state.analysis.maybe_runes) { return value; } diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 67cbd75ff8..8fa4bff619 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -51,6 +51,7 @@ export interface ComponentAnalysis extends Analysis { /** Used for CSS pruning and scoping */ elements: Array; runes: boolean; + maybe_runes: boolean; tracing: boolean; exports: Array<{ name: string; alias: string | null }>; /** Whether the component uses `$$props` */ diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 11ea9f6dda..126992a0c0 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -158,7 +158,10 @@ async function common_setup(cwd: string, runes: boolean | undefined, config: Run ...config.compileOptions, immutable: config.immutable, accessors: 'accessors' in config ? config.accessors : true, - runes + runes: + config.compileOptions && 'runes' in config.compileOptions + ? config.compileOptions.runes + : runes }; // load_compiled can be used for debugging a test. It means the compiler will not run on the input diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/_config.js new file mode 100644 index 0000000000..904fea3f65 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + runes: undefined + }, + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '0'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/main.svelte b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/main.svelte new file mode 100644 index 0000000000..cb5837dcd6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/main.svelte @@ -0,0 +1,8 @@ + + + +

{get()}

+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/test.svelte.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/test.svelte.js new file mode 100644 index 0000000000..f0e7181c29 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-explicit-false/test.svelte.js @@ -0,0 +1,9 @@ +let count = $state(0); + +export function get() { + return count; +} + +export function set() { + count++; +} diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/_config.js new file mode 100644 index 0000000000..904fea3f65 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + runes: undefined + }, + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '0'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/main.svelte b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/main.svelte new file mode 100644 index 0000000000..a7370b48d6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/main.svelte @@ -0,0 +1,9 @@ + + +

{get()}

+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/test.svelte.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/test.svelte.js new file mode 100644 index 0000000000..f0e7181c29 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$props/test.svelte.js @@ -0,0 +1,9 @@ +let count = $state(0); + +export function get() { + return count; +} + +export function set() { + count++; +} diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/_config.js new file mode 100644 index 0000000000..904fea3f65 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + runes: undefined + }, + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '0'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/main.svelte b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/main.svelte new file mode 100644 index 0000000000..ccf9bd2864 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/main.svelte @@ -0,0 +1,9 @@ + + +

{get()}

+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/test.svelte.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/test.svelte.js new file mode 100644 index 0000000000..f0e7181c29 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-$$restProps/test.svelte.js @@ -0,0 +1,9 @@ +let count = $state(0); + +export function get() { + return count; +} + +export function set() { + count++; +} diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/_config.js new file mode 100644 index 0000000000..2d72de6d13 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + runes: undefined + }, + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '1'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/main.svelte b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/main.svelte new file mode 100644 index 0000000000..49aef81518 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/main.svelte @@ -0,0 +1,9 @@ + + +

{get()}

+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/test.svelte.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/test.svelte.js new file mode 100644 index 0000000000..f0e7181c29 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-const/test.svelte.js @@ -0,0 +1,9 @@ +let count = $state(0); + +export function get() { + return count; +} + +export function set() { + count++; +} diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/_config.js new file mode 100644 index 0000000000..904fea3f65 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + runes: undefined + }, + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '0'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/main.svelte b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/main.svelte new file mode 100644 index 0000000000..f7cd0ede2e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/main.svelte @@ -0,0 +1,9 @@ + + +

{get()}

+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/test.svelte.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/test.svelte.js new file mode 100644 index 0000000000..f0e7181c29 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/test.svelte.js @@ -0,0 +1,9 @@ +let count = $state(0); + +export function get() { + return count; +} + +export function set() { + count++; +} diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/_config.js new file mode 100644 index 0000000000..904fea3f65 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + runes: undefined + }, + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '0'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/main.svelte new file mode 100644 index 0000000000..36becde4a3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/main.svelte @@ -0,0 +1,10 @@ + + +{x} +

{get()}

+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/test.svelte.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/test.svelte.js new file mode 100644 index 0000000000..f0e7181c29 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let-2/test.svelte.js @@ -0,0 +1,9 @@ +let count = $state(0); + +export function get() { + return count; +} + +export function set() { + count++; +} diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/_config.js new file mode 100644 index 0000000000..904fea3f65 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + runes: undefined + }, + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '0'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/main.svelte b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/main.svelte new file mode 100644 index 0000000000..1449b7d582 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/main.svelte @@ -0,0 +1,12 @@ + + +{x} +

{get()}

+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/test.svelte.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/test.svelte.js new file mode 100644 index 0000000000..f0e7181c29 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-let/test.svelte.js @@ -0,0 +1,9 @@ +let count = $state(0); + +export function get() { + return count; +} + +export function set() { + count++; +} diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/_config.js new file mode 100644 index 0000000000..2d72de6d13 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + runes: undefined + }, + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '1'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/main.svelte b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/main.svelte new file mode 100644 index 0000000000..698a852ac9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/main.svelte @@ -0,0 +1,7 @@ + + +

{get()}

+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/test.svelte.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/test.svelte.js new file mode 100644 index 0000000000..f0e7181c29 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous/test.svelte.js @@ -0,0 +1,9 @@ +let count = $state(0); + +export function get() { + return count; +} + +export function set() { + count++; +} diff --git a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js index da6fdf44d8..a351851875 100644 --- a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js @@ -8,13 +8,11 @@ export default function Purity($$anchor) { var fragment = root(); var p = $.first_child(fragment); - p.textContent = ( - $.untrack(() => Math.max(0, Math.min(0, 100))) - ); + p.textContent = '0'; var p_1 = $.sibling(p, 2); - p_1.textContent = ($.untrack(() => location.href)); + p_1.textContent = location.href; var node = $.sibling(p_1, 2); From 777e67b9487ffbc8f433e50752243fcb51d22392 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:38:03 +0200 Subject: [PATCH 003/148] Version Packages (#16216) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/clever-dodos-jam.md | 5 ----- .changeset/cold-dingos-dream.md | 5 ----- .changeset/fair-bats-visit.md | 5 ----- packages/svelte/CHANGELOG.md | 10 ++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 6 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 .changeset/clever-dodos-jam.md delete mode 100644 .changeset/cold-dingos-dream.md delete mode 100644 .changeset/fair-bats-visit.md diff --git a/.changeset/clever-dodos-jam.md b/.changeset/clever-dodos-jam.md deleted file mode 100644 index bdeb979184..0000000000 --- a/.changeset/clever-dodos-jam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: untrack `$inspect.with` and add check for unsafe mutation diff --git a/.changeset/cold-dingos-dream.md b/.changeset/cold-dingos-dream.md deleted file mode 100644 index c48414abc2..0000000000 --- a/.changeset/cold-dingos-dream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: use fine grained for template if the component is not explicitly in legacy mode diff --git a/.changeset/fair-bats-visit.md b/.changeset/fair-bats-visit.md deleted file mode 100644 index bc0a426395..0000000000 --- a/.changeset/fair-bats-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -lift unsafe_state_mutation constraints for SvelteSet, SvelteMap, SvelteDate, SvelteURL and SvelteURLSearchParams created inside the derived diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index b29ecc3fe3..e254be754f 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.34.8 + +### Patch Changes + +- fix: untrack `$inspect.with` and add check for unsafe mutation ([#16209](https://github.com/sveltejs/svelte/pull/16209)) + +- fix: use fine grained for template if the component is not explicitly in legacy mode ([#16232](https://github.com/sveltejs/svelte/pull/16232)) + +- lift unsafe_state_mutation constraints for SvelteSet, SvelteMap, SvelteDate, SvelteURL and SvelteURLSearchParams created inside the derived ([#16221](https://github.com/sveltejs/svelte/pull/16221)) + ## 5.34.7 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index bdf7576e22..17a3e980c4 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.34.7", + "version": "5.34.8", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 748cc3ddd6..ec0126b17d 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.34.7'; +export const VERSION = '5.34.8'; export const PUBLIC_VERSION = '5'; From facf528cb8a7289c8b3eaf036949a515f5ad2108 Mon Sep 17 00:00:00 2001 From: brunnerh Date: Wed, 25 Jun 2025 20:35:48 +0200 Subject: [PATCH 004/148] docs: Simplify TS config docs (#16236) * Simplify TypeScript config requirements Assignments are allowed in the constructor from 5.31.0 onward, so retaining the classes via ES2015 should be enough. * Add back a little bit of detail. --- documentation/docs/07-misc/03-typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/07-misc/03-typescript.md b/documentation/docs/07-misc/03-typescript.md index fbf8817069..ff33885fb8 100644 --- a/documentation/docs/07-misc/03-typescript.md +++ b/documentation/docs/07-misc/03-typescript.md @@ -83,7 +83,7 @@ If you're using tools like Rollup or Webpack instead, install their respective S When using TypeScript, make sure your `tsconfig.json` is setup correctly. -- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2022`, or a `target` of at least `ES2015` alongside [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields). This ensures that rune declarations on class fields are not messed with, which would break the Svelte compiler +- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2015` so classes are not compiled to functions - Set [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) to `true` so that imports are left as-is - Set [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) to `true` so that each file is looked at in isolation. TypeScript has a few features which require cross-file analysis and compilation, which the Svelte compiler and tooling like Vite don't do. From 0b2b9c724fef657af9371089e468d5c49c209e36 Mon Sep 17 00:00:00 2001 From: 7nik Date: Wed, 25 Jun 2025 21:38:26 +0300 Subject: [PATCH 005/148] chore: adjust linting (#16231) * chore: adjust linting * more nice treeshaking failure message * lint --- .prettierignore | 4 +- eslint.config.js | 1 + .../svelte/scripts/check-treeshakeability.js | 50 ++++++++++--------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/.prettierignore b/.prettierignore index d5c124353c..72cd10aca8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,6 +7,7 @@ packages/**/config/*.js # packages/svelte packages/svelte/messages/**/*.md +packages/svelte/scripts/_bundle.js packages/svelte/src/compiler/errors.js packages/svelte/src/compiler/warnings.js packages/svelte/src/internal/client/errors.js @@ -25,8 +26,7 @@ packages/svelte/tests/hydration/samples/*/_expected.html packages/svelte/tests/hydration/samples/*/_override.html packages/svelte/types packages/svelte/compiler/index.js -playgrounds/sandbox/input/**.svelte -playgrounds/sandbox/output +playgrounds/sandbox/src/* # sites/svelte.dev sites/svelte.dev/static/svelte-app.json diff --git a/eslint.config.js b/eslint.config.js index d6c977a36a..d7044fc9f1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -87,6 +87,7 @@ export default [ '**/*.d.ts', '**/tests', 'packages/svelte/scripts/process-messages/templates/*.js', + 'packages/svelte/scripts/_bundle.js', 'packages/svelte/src/compiler/errors.js', 'packages/svelte/src/internal/client/errors.js', 'packages/svelte/src/internal/client/warnings.js', diff --git a/packages/svelte/scripts/check-treeshakeability.js b/packages/svelte/scripts/check-treeshakeability.js index 1501ee6954..e883496fe2 100644 --- a/packages/svelte/scripts/check-treeshakeability.js +++ b/packages/svelte/scripts/check-treeshakeability.js @@ -118,36 +118,40 @@ const bundle = await bundle_code( ).js.code ); -if (!bundle.includes('hydrate_node') && !bundle.includes('hydrate_next')) { - // eslint-disable-next-line no-console - console.error(`✅ Hydration code treeshakeable`); -} else { - failed = true; - // eslint-disable-next-line no-console - console.error(`❌ Hydration code not treeshakeable`); -} +/** + * @param {string} case_name + * @param {string[]} strings + */ +function check_bundle(case_name, ...strings) { + for (const string of strings) { + const index = bundle.indexOf(string); + if (index >= 0) { + // eslint-disable-next-line no-console + console.error(`❌ ${case_name} not treeshakeable`); + failed = true; -if (!bundle.includes('component_context.l')) { - // eslint-disable-next-line no-console - console.error(`✅ Legacy code treeshakeable`); -} else { - failed = true; + let lines = bundle.slice(index - 500, index + 500).split('\n'); + const target_line = lines.findIndex((line) => line.includes(string)); + // mark the failed line + lines = lines + .map((line, i) => (i === target_line ? `> ${line}` : `| ${line}`)) + .slice(target_line - 5, target_line + 6); + // eslint-disable-next-line no-console + console.error('The first failed line:\n' + lines.join('\n')); + return; + } + } // eslint-disable-next-line no-console - console.error(`❌ Legacy code not treeshakeable`); + console.error(`✅ ${case_name} treeshakeable`); } -if (!bundle.includes(`'CreatedAt'`)) { - // eslint-disable-next-line no-console - console.error(`✅ $inspect.trace code treeshakeable`); -} else { - failed = true; - // eslint-disable-next-line no-console - console.error(`❌ $inspect.trace code not treeshakeable`); -} +check_bundle('Hydration code', 'hydrate_node', 'hydrate_next'); +check_bundle('Legacy code', 'component_context.l'); +check_bundle('$inspect.trace', `'CreatedAt'`); if (failed) { // eslint-disable-next-line no-console - console.error(bundle); + console.error('Full bundle at', path.resolve('scripts/_bundle.js')); fs.writeFileSync('scripts/_bundle.js', bundle); } From da2feafe6765d2278ff053f62e6603a60b06833e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 25 Jun 2025 14:40:52 -0400 Subject: [PATCH 006/148] chore: silence some pesky logs (#16241) --- .../samples/inspect-state-unsafe-mutation/_config.js | 6 +++++- .../legacy-runes-ambiguous-export-labeled/_config.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/_config.js index 7e8fcd2d48..dcf3a8bc3d 100644 --- a/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/_config.js @@ -5,5 +5,9 @@ export default test({ compileOptions: { dev: true }, - error: 'state_unsafe_mutation' + + error: 'state_unsafe_mutation', + + // silence the logs + test({ logs }) {} }); diff --git a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/_config.js b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/_config.js index 904fea3f65..c4cff665d2 100644 --- a/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/legacy-runes-ambiguous-export-labeled/_config.js @@ -6,7 +6,7 @@ export default test({ compileOptions: { runes: undefined }, - async test({ assert, target }) { + async test({ assert, target, logs }) { const p = target.querySelector('p'); const btn = target.querySelector('button'); flushSync(() => { From 213274a75ecc71dd86a31ba2dab4f594fc2cd265 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 26 Jun 2025 18:23:10 -0400 Subject: [PATCH 007/148] chore: reduce some select.value indirection (#16250) --- .../client/visitors/RegularElement.js | 21 ++++++++----------- .../client/visitors/shared/utils.js | 14 ------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 1aefff0db0..b0f285eb41 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -25,7 +25,6 @@ import { process_children } from './shared/fragment.js'; import { build_render_statement, build_template_chunk, - build_update_assignment, get_expression_id, memoize_expression } from './shared/utils.js'; @@ -657,17 +656,15 @@ function build_element_special_value_attribute(element, node_id, attribute, cont } if (has_state) { - const id = state.scope.generate(`${node_id.name}_value`); - build_update_assignment( - state, - id, - // `