From 6a7df1cce82e2f7df21f056d89458d2fb47530cc Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Fri, 13 Jun 2025 19:59:20 +0800 Subject: [PATCH 001/173] fix: add missing typings for some dimension bindings (#16142) * fix: typings for dimension bindings * tweak description --- .changeset/clever-cats-invent.md | 5 +++++ packages/svelte/elements.d.ts | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .changeset/clever-cats-invent.md diff --git a/.changeset/clever-cats-invent.md b/.changeset/clever-cats-invent.md new file mode 100644 index 0000000000..589b7d3eae --- /dev/null +++ b/.changeset/clever-cats-invent.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: add missing typings for some dimension bindings diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 076cc71b38..fe4078f56a 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -844,6 +844,10 @@ export interface HTMLAttributes extends AriaAttributes, D 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; // SvelteKit 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; From 36315fece462cf1a2afc4f1d44f9f527df3ae7db Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Sat, 14 Jun 2025 04:18:52 -0700 Subject: [PATCH 002/173] fix: prune typescript class field declarations (#16154) * fix: prune typescript class field declarations * add test --- .changeset/neat-lemons-occur.md | 5 +++++ .../phases/1-parse/remove_typescript_nodes.js | 13 +++++++++++++ .../runtime-runes/samples/typescript/main.svelte | 1 + 3 files changed, 19 insertions(+) create mode 100644 .changeset/neat-lemons-occur.md diff --git a/.changeset/neat-lemons-occur.md b/.changeset/neat-lemons-occur.md new file mode 100644 index 0000000000..53d77ed764 --- /dev/null +++ b/.changeset/neat-lemons-occur.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prune typescript class field declarations diff --git a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js index aba94ee20d..cb498c3c13 100644 --- a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js +++ b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js @@ -115,6 +115,19 @@ const visitors = { TSDeclareFunction() { return b.empty; }, + ClassBody(node, context) { + const body = []; + for (const _child of node.body) { + const child = context.visit(_child); + if (child.type !== 'PropertyDefinition' || !child.declare) { + body.push(child); + } + } + return { + ...node, + body + }; + }, ClassDeclaration(node, context) { if (node.declare) { return b.empty; diff --git a/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte index d1b6452df4..4fc7c4ec38 100644 --- a/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte @@ -14,6 +14,7 @@ class Foo { public name: string; + declare bar: string; x = 'x' as const; constructor(name: string) { this.name = name; From da63318191f85baf302e494615f23b46f74908f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:31:00 -0400 Subject: [PATCH 003/173] Version Packages (#16147) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/clever-cats-invent.md | 5 ----- .changeset/neat-lemons-occur.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/clever-cats-invent.md delete mode 100644 .changeset/neat-lemons-occur.md diff --git a/.changeset/clever-cats-invent.md b/.changeset/clever-cats-invent.md deleted file mode 100644 index 589b7d3eae..0000000000 --- a/.changeset/clever-cats-invent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: add missing typings for some dimension bindings diff --git a/.changeset/neat-lemons-occur.md b/.changeset/neat-lemons-occur.md deleted file mode 100644 index 53d77ed764..0000000000 --- a/.changeset/neat-lemons-occur.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prune typescript class field declarations diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 23d834108a..563086d8d9 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.34.2 + +### Patch Changes + +- fix: add missing typings for some dimension bindings ([#16142](https://github.com/sveltejs/svelte/pull/16142)) + +- fix: prune typescript class field declarations ([#16154](https://github.com/sveltejs/svelte/pull/16154)) + ## 5.34.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 83d5b8c3e6..2e4234fcaf 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.1", + "version": "5.34.2", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 2d603694d2..c1df14d295 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.1'; +export const VERSION = '5.34.2'; export const PUBLIC_VERSION = '5'; From 113a3daab2a2742127a5857b76d1314deb260e03 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Sat, 14 Jun 2025 17:14:39 +0200 Subject: [PATCH 004/173] fix: don't define `error.message` if it's not configurable (#16149) * fix: don't define `error.message` if it's not configurable * fix: print console.error with updated stack * fix: revert second `console.error` --- .changeset/unlucky-plants-jump.md | 5 +++++ packages/svelte/src/internal/client/error-handling.js | 8 +++++++- .../samples/non-configurable-errors/_config.js | 8 ++++++++ .../samples/non-configurable-errors/main.svelte | 11 +++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .changeset/unlucky-plants-jump.md create mode 100644 packages/svelte/tests/runtime-runes/samples/non-configurable-errors/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/non-configurable-errors/main.svelte diff --git a/.changeset/unlucky-plants-jump.md b/.changeset/unlucky-plants-jump.md new file mode 100644 index 0000000000..814cb99b69 --- /dev/null +++ b/.changeset/unlucky-plants-jump.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't define `error.message` if it's not configurable diff --git a/packages/svelte/src/internal/client/error-handling.js b/packages/svelte/src/internal/client/error-handling.js index aeec1d8b47..99f3ed6cd4 100644 --- a/packages/svelte/src/internal/client/error-handling.js +++ b/packages/svelte/src/internal/client/error-handling.js @@ -3,7 +3,7 @@ import { DEV } from 'esm-env'; import { FILENAME } from '../../constants.js'; import { is_firefox } from './dom/operations.js'; import { BOUNDARY_EFFECT, EFFECT_RAN } from './constants.js'; -import { define_property } from '../shared/utils.js'; +import { define_property, get_descriptor } from '../shared/utils.js'; import { active_effect } from './runtime.js'; /** @@ -63,6 +63,12 @@ function adjust_error(error, effect) { if (adjusted_errors.has(error)) return; adjusted_errors.add(error); + const message_descriptor = get_descriptor(error, 'message'); + + // if the message was already changed and it's not configurable we can't change it + // or it will throw a different error swallowing the original error + if (message_descriptor && !message_descriptor.configurable) return; + var indent = is_firefox ? ' ' : '\t'; var component_stack = `\n${indent}in ${effect.fn?.name || ''}`; var context = effect.ctx; diff --git a/packages/svelte/tests/runtime-runes/samples/non-configurable-errors/_config.js b/packages/svelte/tests/runtime-runes/samples/non-configurable-errors/_config.js new file mode 100644 index 0000000000..5bbe4483d3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-configurable-errors/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + error: 'test' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-configurable-errors/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-configurable-errors/main.svelte new file mode 100644 index 0000000000..f71a5e6c43 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-configurable-errors/main.svelte @@ -0,0 +1,11 @@ + From 92ea58bee6f27dde6b1d29527c0e2f97047219f0 Mon Sep 17 00:00:00 2001 From: Matei Trandafir Date: Sat, 14 Jun 2025 18:15:19 +0300 Subject: [PATCH 005/173] fix: don't eagerly execute deriveds on resume (#16150) * Add failing test * Add {@const} test case * Fix the bug * Add yet another test case * Better fix * Changeset * simplify * this appears to be unnecessary --------- Co-authored-by: Rich Harris --- .changeset/poor-pumpkins-exercise.md | 5 ++++ .../src/internal/client/reactivity/effects.js | 13 ++++------- .../if-nested-template/Component.svelte | 7 ++++++ .../samples/if-nested-template/_config.js | 17 ++++++++++++++ .../samples/if-nested-template/main.svelte | 23 +++++++++++++++++++ 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 .changeset/poor-pumpkins-exercise.md create mode 100644 packages/svelte/tests/runtime-runes/samples/if-nested-template/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/if-nested-template/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/if-nested-template/main.svelte diff --git a/.changeset/poor-pumpkins-exercise.md b/.changeset/poor-pumpkins-exercise.md new file mode 100644 index 0000000000..cd5d08a9b5 --- /dev/null +++ b/.changeset/poor-pumpkins-exercise.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: don't eagerly execute deriveds on resume diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 54994b9bd1..625c0d1822 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -329,6 +329,7 @@ export function render_effect(fn) { /** * @param {(...expressions: any) => void | (() => void)} fn * @param {Array<() => any>} thunks + * @param {(fn: () => T) => Derived} d * @returns {Effect} */ export function template_effect(fn, thunks = [], d = derived) { @@ -598,15 +599,11 @@ function resume_children(effect, local) { if ((effect.f & INERT) === 0) return; effect.f ^= INERT; - // Ensure the effect is marked as clean again so that any dirty child - // effects can schedule themselves for execution - if ((effect.f & CLEAN) === 0) { - effect.f ^= CLEAN; - } - // If a dependency of this effect changed while it was paused, - // schedule the effect to update - if (check_dirtiness(effect)) { + // schedule the effect to update. we don't use `check_dirtiness` + // here because we don't want to eagerly recompute a derived like + // `{#if foo}{foo.bar()}{/if}` if `foo` is now `undefined + if ((effect.f & CLEAN) !== 0) { set_signal_status(effect, DIRTY); schedule_effect(effect); } diff --git a/packages/svelte/tests/runtime-runes/samples/if-nested-template/Component.svelte b/packages/svelte/tests/runtime-runes/samples/if-nested-template/Component.svelte new file mode 100644 index 0000000000..b4281bbcbd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/if-nested-template/Component.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/if-nested-template/_config.js b/packages/svelte/tests/runtime-runes/samples/if-nested-template/_config.js new file mode 100644 index 0000000000..673f668916 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/if-nested-template/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [btn1, btn2] = target.querySelectorAll('button'); + const [div] = target.querySelectorAll('div'); + + flushSync(() => btn1?.click()); + assert.htmlEqual(div.innerHTML, '123 123'); + assert.equal(div.inert, true); + + flushSync(() => btn2?.click()); + assert.htmlEqual(div.innerHTML, ''); + assert.deepEqual(logs, ['123']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/if-nested-template/main.svelte b/packages/svelte/tests/runtime-runes/samples/if-nested-template/main.svelte new file mode 100644 index 0000000000..04afa7d664 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/if-nested-template/main.svelte @@ -0,0 +1,23 @@ + + +{#if outer} +
+ {#if inner} + {@const text = inner.toString()} + {text} {inner.toString()} + + {/if} +
+{/if} + + + From 6636f748ae770e905ca9e88e9a921b4bc854164c Mon Sep 17 00:00:00 2001 From: 7nik Date: Sat, 14 Jun 2025 18:19:42 +0300 Subject: [PATCH 006/173] fix: prevent memory leaking signals in legacy mode (#16145) * fix: prevent memory leaking signals in legacy mode * format * fix --------- Co-authored-by: 7nik --- .changeset/soft-oranges-approve.md | 5 +++++ packages/svelte/src/internal/client/dom/blocks/await.js | 6 ++++-- packages/svelte/src/internal/client/dom/blocks/each.js | 2 +- packages/svelte/src/internal/client/reactivity/sources.js | 4 ++-- packages/svelte/src/legacy/legacy-client.js | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 .changeset/soft-oranges-approve.md diff --git a/.changeset/soft-oranges-approve.md b/.changeset/soft-oranges-approve.md new file mode 100644 index 0000000000..f759c128c0 --- /dev/null +++ b/.changeset/soft-oranges-approve.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent memory leaking signals in legacy mode diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 99bdc0000c..47df5fc9a5 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -58,8 +58,10 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) { /** @type {Effect | null} */ var catch_effect; - var input_source = (runes ? source : mutable_source)(/** @type {V} */ (undefined)); - var error_source = (runes ? source : mutable_source)(undefined); + var input_source = runes + ? source(/** @type {V} */ (undefined)) + : mutable_source(/** @type {V} */ (undefined), false, false); + var error_source = runes ? source(undefined) : mutable_source(undefined, false, false); var resolved = false; /** diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 954dcb2214..b638a6d2da 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -521,7 +521,7 @@ function create_item( var reactive = (flags & EACH_ITEM_REACTIVE) !== 0; var mutable = (flags & EACH_ITEM_IMMUTABLE) === 0; - var v = reactive ? (mutable ? mutable_source(value) : source(value)) : value; + var v = reactive ? (mutable ? mutable_source(value, false, false) : source(value)) : value; var i = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); if (DEV && reactive) { diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 40a3e4e77f..56f4138252 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -97,7 +97,7 @@ export function state(v, stack) { * @returns {Source} */ /*#__NO_SIDE_EFFECTS__*/ -export function mutable_source(initial_value, immutable = false) { +export function mutable_source(initial_value, immutable = false, trackable = true) { const s = source(initial_value); if (!immutable) { s.equals = safe_equals; @@ -105,7 +105,7 @@ export function mutable_source(initial_value, immutable = false) { // bind the signal to the component context, in case we need to // track updates to trigger beforeUpdate/afterUpdate callbacks - if (legacy_mode_flag && component_context !== null && component_context.l !== null) { + if (legacy_mode_flag && trackable && component_context !== null && component_context.l !== null) { (component_context.l.s ??= []).push(s); } diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index bb9a5a9c03..45c478ecab 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -82,7 +82,7 @@ class Svelte4Component { * @param {unknown} value */ var add_source = (key, value) => { - var s = mutable_source(value); + var s = mutable_source(value, false, false); sources.set(key, s); return s; }; From 5c5536eaa158b4404f49ede3401e7cdee99be165 Mon Sep 17 00:00:00 2001 From: 7nik Date: Sat, 14 Jun 2025 18:48:32 +0300 Subject: [PATCH 007/173] docs: clarify style precedence (#16158) Co-authored-by: 7nik --- documentation/docs/03-template-syntax/17-style.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/documentation/docs/03-template-syntax/17-style.md b/documentation/docs/03-template-syntax/17-style.md index 749376c6e2..aa61cdcde3 100644 --- a/documentation/docs/03-template-syntax/17-style.md +++ b/documentation/docs/03-template-syntax/17-style.md @@ -34,8 +34,10 @@ To mark a style as important, use the `|important` modifier:
...
``` -When `style:` directives are combined with `style` attributes, the directives will take precedence: +When `style:` directives are combined with `style` attributes, the directives will take precedence, +even over `!important` properties: ```svelte -
This will be red
+
This will be red
+
This will still be red
``` From 546608636a0018a78c3441adb60246c49fa86468 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 11:48:47 -0400 Subject: [PATCH 008/173] Version Packages (#16157) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/poor-pumpkins-exercise.md | 5 ----- .changeset/soft-oranges-approve.md | 5 ----- .changeset/unlucky-plants-jump.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/poor-pumpkins-exercise.md delete mode 100644 .changeset/soft-oranges-approve.md delete mode 100644 .changeset/unlucky-plants-jump.md diff --git a/.changeset/poor-pumpkins-exercise.md b/.changeset/poor-pumpkins-exercise.md deleted file mode 100644 index cd5d08a9b5..0000000000 --- a/.changeset/poor-pumpkins-exercise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"svelte": patch ---- - -fix: don't eagerly execute deriveds on resume diff --git a/.changeset/soft-oranges-approve.md b/.changeset/soft-oranges-approve.md deleted file mode 100644 index f759c128c0..0000000000 --- a/.changeset/soft-oranges-approve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prevent memory leaking signals in legacy mode diff --git a/.changeset/unlucky-plants-jump.md b/.changeset/unlucky-plants-jump.md deleted file mode 100644 index 814cb99b69..0000000000 --- a/.changeset/unlucky-plants-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't define `error.message` if it's not configurable diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 563086d8d9..020942f5fd 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.34.3 + +### Patch Changes + +- fix: don't eagerly execute deriveds on resume ([#16150](https://github.com/sveltejs/svelte/pull/16150)) + +- fix: prevent memory leaking signals in legacy mode ([#16145](https://github.com/sveltejs/svelte/pull/16145)) + +- fix: don't define `error.message` if it's not configurable ([#16149](https://github.com/sveltejs/svelte/pull/16149)) + ## 5.34.2 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 2e4234fcaf..d2fbdb32f7 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.2", + "version": "5.34.3", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index c1df14d295..01888eaa78 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.2'; +export const VERSION = '5.34.3'; export const PUBLIC_VERSION = '5'; From c91e69b15b4c36c245f1777e19570cb3337f7ad2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 15 Jun 2025 15:35:21 -0400 Subject: [PATCH 009/173] chore: remove some unnecessary code (#16160) --- .../svelte/src/internal/client/reactivity/effects.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 625c0d1822..03d073781d 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -599,15 +599,6 @@ function resume_children(effect, local) { if ((effect.f & INERT) === 0) return; effect.f ^= INERT; - // If a dependency of this effect changed while it was paused, - // schedule the effect to update. we don't use `check_dirtiness` - // here because we don't want to eagerly recompute a derived like - // `{#if foo}{foo.bar()}{/if}` if `foo` is now `undefined - if ((effect.f & CLEAN) !== 0) { - set_signal_status(effect, DIRTY); - schedule_effect(effect); - } - var child = effect.first; while (child !== null) { From 931f211b253e13d7bb8fe6db441cdc3800da5f3a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 15 Jun 2025 15:40:31 -0400 Subject: [PATCH 010/173] chore: fix `$.attachment` prop (#16172) --- .../phases/3-transform/client/visitors/shared/component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index a1c4025d60..19ed9cdeb8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -275,7 +275,7 @@ export function build_component(node, component_name, context) { ); } - push_prop(b.prop('get', b.call('$.attachment'), expression, true)); + push_prop(b.prop('init', b.call('$.attachment'), expression, true)); } } From 7e588857c2b92a98ebc25237112e29f6f8988992 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Mon, 16 Jun 2025 17:49:43 +0200 Subject: [PATCH 011/173] fix: don't set state withing `with_parent` in proxy (#16176) Closes #16164 We can't set everywhere within with_parent otherwise if it's the first time we are reading a derived it could look like we are setting state in a derived (which you are not). --- .changeset/beige-plants-laugh.md | 5 ++++ packages/svelte/src/internal/client/proxy.js | 27 ++++++++----------- .../samples/proxy-set-with-parent/_config.js | 5 ++++ .../samples/proxy-set-with-parent/main.svelte | 15 +++++++++++ 4 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 .changeset/beige-plants-laugh.md create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-set-with-parent/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-set-with-parent/main.svelte diff --git a/.changeset/beige-plants-laugh.md b/.changeset/beige-plants-laugh.md new file mode 100644 index 0000000000..d0c771736a --- /dev/null +++ b/.changeset/beige-plants-laugh.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't set state withing `with_parent` in proxy diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 4870506699..60eba6aa87 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -93,21 +93,19 @@ export function proxy(value) { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor#invariants e.state_descriptors_fixed(); } - - with_parent(() => { - var s = sources.get(prop); - - if (s === undefined) { - s = source(descriptor.value, stack); + var s = sources.get(prop); + if (s === undefined) { + s = with_parent(() => { + var s = source(descriptor.value, stack); sources.set(prop, s); - if (DEV && typeof prop === 'string') { tag(s, get_label(path, prop)); } - } else { - set(s, descriptor.value, true); - } - }); + return s; + }); + } else { + set(s, descriptor.value, true); + } return true; }, @@ -268,11 +266,8 @@ export function proxy(value) { // object property before writing to that property. if (s === undefined) { if (!has || get_descriptor(target, prop)?.writable) { - s = with_parent(() => { - var s = source(undefined, stack); - set(s, proxy(value)); - return s; - }); + s = with_parent(() => source(undefined, stack)); + set(s, proxy(value)); sources.set(prop, s); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-set-with-parent/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-set-with-parent/_config.js new file mode 100644 index 0000000000..2e4a27cf09 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-set-with-parent/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + async test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-set-with-parent/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-set-with-parent/main.svelte new file mode 100644 index 0000000000..7450eff3fa --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-set-with-parent/main.svelte @@ -0,0 +1,15 @@ + + + From d99d87251905abd57404f46f16df4d299ddf9d3b Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Mon, 16 Jun 2025 23:54:12 -0600 Subject: [PATCH 012/173] fix: Make docs example not infinitely recurse (#16183) --- documentation/docs/07-misc/02-testing.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/documentation/docs/07-misc/02-testing.md b/documentation/docs/07-misc/02-testing.md index 64bf49d77a..db99b70770 100644 --- a/documentation/docs/07-misc/02-testing.md +++ b/documentation/docs/07-misc/02-testing.md @@ -129,12 +129,12 @@ test('Effect', () => { // effects normally run after a microtask, // use flushSync to execute all pending effects synchronously flushSync(); - expect(log.value).toEqual([0]); + expect(log).toEqual([0]); count = 1; flushSync(); - expect(log.value).toEqual([0, 1]); + expect(log).toEqual([0, 1]); }); cleanup(); @@ -148,17 +148,13 @@ test('Effect', () => { */ export function logger(getValue) { /** @type {any[]} */ - let log = $state([]); + let log = []; $effect(() => { log.push(getValue()); }); - return { - get value() { - return log; - } - }; + return log; } ``` From b224c3fb4b019880a20f4286e60f078e1dcbacc0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Jun 2025 12:43:52 -0400 Subject: [PATCH 013/173] fix: coarse reactivity, alternative approach (#16100) Make sure we track statically visible dependencies and untrack indirect dependencies Fixes #14351 --------- Co-authored-by: 7nik Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/popular-dancers-switch.md | 5 ++ .../src/compiler/phases/1-parse/state/tag.js | 29 +++++++-- .../visitors/AssignmentExpression.js | 4 ++ .../phases/2-analyze/visitors/AwaitBlock.js | 5 +- .../phases/2-analyze/visitors/ConstTag.js | 5 +- .../phases/2-analyze/visitors/HtmlTag.js | 2 +- .../phases/2-analyze/visitors/Identifier.js | 1 + .../phases/2-analyze/visitors/IfBlock.js | 8 ++- .../phases/2-analyze/visitors/KeyBlock.js | 3 +- .../2-analyze/visitors/MemberExpression.js | 5 +- .../phases/2-analyze/visitors/RenderTag.js | 2 +- .../2-analyze/visitors/UpdateExpression.js | 4 ++ .../2-analyze/visitors/shared/function.js | 10 ++++ .../3-transform/client/visitors/AttachTag.js | 13 +--- .../3-transform/client/visitors/AwaitBlock.js | 5 +- .../3-transform/client/visitors/ConstTag.js | 24 ++++---- .../3-transform/client/visitors/EachBlock.js | 21 ++++--- .../3-transform/client/visitors/HtmlTag.js | 4 +- .../3-transform/client/visitors/IfBlock.js | 5 +- .../3-transform/client/visitors/KeyBlock.js | 3 +- .../client/visitors/RegularElement.js | 2 +- .../3-transform/client/visitors/RenderTag.js | 12 +++- .../client/visitors/TitleElement.js | 3 +- .../client/visitors/shared/element.js | 6 +- .../client/visitors/shared/fragment.js | 28 ++++----- .../client/visitors/shared/utils.js | 59 ++++++++++++++++--- packages/svelte/src/compiler/phases/nodes.js | 5 +- packages/svelte/src/compiler/types/index.d.ts | 8 ++- .../svelte/src/compiler/types/template.d.ts | 21 +++++++ .../block-expression-assign/_config.js | 12 ++++ .../block-expression-assign/main.svelte | 45 ++++++++++++++ .../block-expression-fn-call/_config.js | 12 ++++ .../block-expression-fn-call/main.svelte | 36 +++++++++++ .../block-expression-member-access/_config.js | 12 ++++ .../main.svelte | 46 +++++++++++++++ .../Item.svelte | 4 +- .../main.svelte | 4 +- .../purity/_expected/client/index.svelte.js | 6 +- 38 files changed, 392 insertions(+), 87 deletions(-) create mode 100644 .changeset/popular-dancers-switch.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/block-expression-assign/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/block-expression-assign/main.svelte create mode 100644 packages/svelte/tests/runtime-legacy/samples/block-expression-fn-call/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/block-expression-fn-call/main.svelte create mode 100644 packages/svelte/tests/runtime-legacy/samples/block-expression-member-access/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/block-expression-member-access/main.svelte diff --git a/.changeset/popular-dancers-switch.md b/.changeset/popular-dancers-switch.md new file mode 100644 index 0000000000..b8c26c210e --- /dev/null +++ b/.changeset/popular-dancers-switch.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: use compiler-driven reactivity in legacy mode template expressions diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 4153463c83..5d77d6a8f4 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -63,7 +63,10 @@ function open(parser) { end: -1, test: read_expression(parser), consequent: create_fragment(), - alternate: null + alternate: null, + metadata: { + expression: create_expression_metadata() + } }); parser.allow_whitespace(); @@ -244,7 +247,10 @@ function open(parser) { error: null, pending: null, then: null, - catch: null + catch: null, + metadata: { + expression: create_expression_metadata() + } }); if (parser.eat('then')) { @@ -326,7 +332,10 @@ function open(parser) { start, end: -1, expression, - fragment: create_fragment() + fragment: create_fragment(), + metadata: { + expression: create_expression_metadata() + } }); parser.stack.push(block); @@ -461,7 +470,10 @@ function next(parser) { elseif: true, test: expression, consequent: create_fragment(), - alternate: null + alternate: null, + metadata: { + expression: create_expression_metadata() + } }); parser.stack.push(child); @@ -624,7 +636,10 @@ function special(parser) { type: 'HtmlTag', start, end: parser.index, - expression + expression, + metadata: { + expression: create_expression_metadata() + } }); return; @@ -699,6 +714,9 @@ function special(parser) { declarations: [{ type: 'VariableDeclarator', id, init, start: id.start, end: init.end }], start: start + 2, // start at const, not at @const end: parser.index - 1 + }, + metadata: { + expression: create_expression_metadata() } }); } @@ -725,6 +743,7 @@ function special(parser) { end: parser.index, expression: /** @type {AST.RenderTag['expression']} */ (expression), metadata: { + expression: create_expression_metadata(), dynamic: false, arguments: [], path: [], diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js index 673c79f2df..39358f72fc 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js @@ -23,5 +23,9 @@ export function AssignmentExpression(node, context) { } } + if (context.state.expression) { + context.state.expression.has_assignment = true; + } + context.next(); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js index a71f325154..5aa04ba3b9 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js @@ -41,5 +41,8 @@ export function AwaitBlock(node, context) { mark_subtree_dynamic(context.path); - context.next(); + context.visit(node.expression, { ...context.state, expression: node.metadata.expression }); + if (node.pending) context.visit(node.pending); + if (node.then) context.visit(node.then); + if (node.catch) context.visit(node.catch); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js index f723f8447c..d5f5f7b2e0 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js @@ -32,5 +32,8 @@ export function ConstTag(node, context) { e.const_tag_invalid_placement(node); } - context.next(); + const declaration = node.declaration.declarations[0]; + + context.visit(declaration.id); + context.visit(declaration.init, { ...context.state, expression: node.metadata.expression }); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js index c89b11ad36..7b0e501760 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js @@ -15,5 +15,5 @@ export function HtmlTag(node, context) { // unfortunately this is necessary in order to fix invalid HTML mark_subtree_dynamic(context.path); - context.next(); + context.next({ ...context.state, expression: node.metadata.expression }); } 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 abf70769c0..cced326f9b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -90,6 +90,7 @@ export function Identifier(node, context) { if (binding) { if (context.state.expression) { context.state.expression.dependencies.add(binding); + context.state.expression.references.add(binding); context.state.expression.has_state ||= binding.kind !== 'static' && !binding.is_function() && diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js index a65771bcfc..dcdae3587f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js @@ -17,5 +17,11 @@ export function IfBlock(node, context) { mark_subtree_dynamic(context.path); - context.next(); + context.visit(node.test, { + ...context.state, + expression: node.metadata.expression + }); + + context.visit(node.consequent); + if (node.alternate) context.visit(node.alternate); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js index 88bb6a98e7..09e604ea66 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js @@ -16,5 +16,6 @@ export function KeyBlock(node, context) { mark_subtree_dynamic(context.path); - context.next(); + context.visit(node.expression, { ...context.state, expression: node.metadata.expression }); + context.visit(node.fragment); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js index 245a164c71..0a3b386198 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js @@ -15,8 +15,9 @@ export function MemberExpression(node, context) { } } - if (context.state.expression && !is_pure(node, context)) { - context.state.expression.has_state = true; + if (context.state.expression) { + context.state.expression.has_member_expression = true; + context.state.expression.has_state ||= !is_pure(node, context); } if (!is_safe_identifier(node, context.state.scope)) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js index a8c9d408bd..1230ef6b04 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js @@ -54,7 +54,7 @@ export function RenderTag(node, context) { mark_subtree_dynamic(context.path); - context.visit(callee); + context.visit(callee, { ...context.state, expression: node.metadata.expression }); for (const arg of expression.arguments) { const metadata = create_expression_metadata(); diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/UpdateExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/UpdateExpression.js index 13f4b9019e..ed48e026ac 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/UpdateExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/UpdateExpression.js @@ -21,5 +21,9 @@ export function UpdateExpression(node, context) { } } + if (context.state.expression) { + context.state.expression.has_assignment = true; + } + context.next(); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js index c892efd421..1776167850 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js @@ -13,6 +13,16 @@ export function visit_function(node, context) { scope: context.state.scope }; + if (context.state.expression) { + for (const [name] of context.state.scope.references) { + const binding = context.state.scope.get(name); + + if (binding && binding.scope.function_depth < context.state.scope.function_depth) { + context.state.expression.references.add(binding); + } + } + } + context.next({ ...context.state, function_depth: context.state.function_depth + 1, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AttachTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AttachTag.js index 062604cacc..8b1570c7dc 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AttachTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AttachTag.js @@ -1,21 +1,14 @@ -/** @import { Expression } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '../../../../utils/builders.js'; +import { build_expression } from './shared/utils.js'; /** * @param {AST.AttachTag} node * @param {ComponentContext} context */ export function AttachTag(node, context) { - context.state.init.push( - b.stmt( - b.call( - '$.attach', - context.state.node, - b.thunk(/** @type {Expression} */ (context.visit(node.expression))) - ) - ) - ); + const expression = build_expression(context, node.expression, node.metadata.expression); + context.state.init.push(b.stmt(b.call('$.attach', context.state.node, b.thunk(expression)))); context.next(); } 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 30e370327f..7873cf3ddb 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,10 +1,11 @@ -/** @import { BlockStatement, Expression, Pattern, Statement } from 'estree' */ +/** @import { BlockStatement, Pattern, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */ import { extract_identifiers } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { create_derived } from '../utils.js'; import { get_value } from './shared/declarations.js'; +import { build_expression } from './shared/utils.js'; /** * @param {AST.AwaitBlock} node @@ -14,7 +15,7 @@ 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(/** @type {Expression} */ (context.visit(node.expression))); + const expression = b.thunk(build_expression(context, node.expression, node.metadata.expression)); let then_block; let catch_block; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js index 2f3c0b3d0e..c1be1e3220 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js @@ -1,4 +1,4 @@ -/** @import { Expression, Pattern } from 'estree' */ +/** @import { Pattern } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import { dev } from '../../../../state.js'; @@ -6,6 +6,7 @@ import { extract_identifiers } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { create_derived } from '../utils.js'; import { get_value } from './shared/declarations.js'; +import { build_expression } from './shared/utils.js'; /** * @param {AST.ConstTag} node @@ -15,15 +16,8 @@ export function ConstTag(node, context) { const declaration = node.declaration.declarations[0]; // TODO we can almost certainly share some code with $derived(...) if (declaration.id.type === 'Identifier') { - context.state.init.push( - b.const( - declaration.id, - create_derived( - context.state, - b.thunk(/** @type {Expression} */ (context.visit(declaration.init))) - ) - ) - ); + const init = build_expression(context, declaration.init, node.metadata.expression); + context.state.init.push(b.const(declaration.id, create_derived(context.state, b.thunk(init)))); context.state.transform[declaration.id.name] = { read: get_value }; @@ -48,13 +42,15 @@ export function ConstTag(node, context) { // TODO optimise the simple `{ x } = y` case — we can just return `y` // instead of destructuring it only to return a new object + const init = build_expression( + { ...context, state: child_state }, + declaration.init, + node.metadata.expression + ); const fn = b.arrow( [], b.block([ - b.const( - /** @type {Pattern} */ (context.visit(declaration.id, child_state)), - /** @type {Expression} */ (context.visit(declaration.init, child_state)) - ), + b.const(/** @type {Pattern} */ (context.visit(declaration.id, child_state)), init), b.return(b.object(identifiers.map((node) => b.prop('init', node, node)))) ]) ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index 6c651464f1..201c4b278f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -1,4 +1,4 @@ -/** @import { BlockStatement, Expression, Identifier, Pattern, SequenceExpression, Statement } from 'estree' */ +/** @import { BlockStatement, Expression, Identifier, Pattern, Statement } from 'estree' */ /** @import { AST, Binding } from '#compiler' */ /** @import { ComponentContext } from '../types' */ /** @import { Scope } from '../../../scope' */ @@ -12,8 +12,8 @@ import { import { dev } from '../../../../state.js'; import { extract_paths, object } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; -import { build_getter } from '../utils.js'; import { get_value } from './shared/declarations.js'; +import { build_expression } from './shared/utils.js'; /** * @param {AST.EachBlock} node @@ -24,11 +24,18 @@ export function EachBlock(node, context) { // expression should be evaluated in the parent scope, not the scope // created by the each block itself - const collection = /** @type {Expression} */ ( - context.visit(node.expression, { - ...context.state, - scope: /** @type {Scope} */ (context.state.scope.parent) - }) + const parent_scope_state = { + ...context.state, + scope: /** @type {Scope} */ (context.state.scope.parent) + }; + + const collection = build_expression( + { + ...context, + state: parent_scope_state + }, + node.expression, + node.metadata.expression ); if (!each_node_meta.is_controlled) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js index 405b400b42..fb59967996 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js @@ -1,8 +1,8 @@ -/** @import { Expression } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import { is_ignored } from '../../../../state.js'; import * as b from '#compiler/builders'; +import { build_expression } from './shared/utils.js'; /** * @param {AST.HtmlTag} node @@ -11,7 +11,7 @@ import * as b from '#compiler/builders'; export function HtmlTag(node, context) { context.state.template.push_comment(); - const expression = /** @type {Expression} */ (context.visit(node.expression)); + const expression = build_expression(context, node.expression, node.metadata.expression); const is_svg = context.state.metadata.namespace === 'svg'; const is_mathml = context.state.metadata.namespace === 'mathml'; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js index 3702a47bc9..deab040e50 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js @@ -2,6 +2,7 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '#compiler/builders'; +import { build_expression } from './shared/utils.js'; /** * @param {AST.IfBlock} node @@ -31,6 +32,8 @@ export function IfBlock(node, context) { statements.push(b.var(b.id(alternate_id), b.arrow(alternate_args, alternate))); } + const test = build_expression(context, node.test, node.metadata.expression); + /** @type {Expression[]} */ const args = [ node.elseif ? b.id('$$anchor') : context.state.node, @@ -38,7 +41,7 @@ export function IfBlock(node, context) { [b.id('$$render')], b.block([ b.if( - /** @type {Expression} */ (context.visit(node.test)), + test, b.stmt(b.call(b.id('$$render'), b.id(consequent_id))), alternate_id ? b.stmt(b.call(b.id('$$render'), b.id(alternate_id), b.false)) : undefined ) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js index 5e63f7e872..2f17479c7e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js @@ -2,6 +2,7 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '#compiler/builders'; +import { build_expression } from './shared/utils.js'; /** * @param {AST.KeyBlock} node @@ -10,7 +11,7 @@ import * as b from '#compiler/builders'; export function KeyBlock(node, context) { context.state.template.push_comment(); - const key = /** @type {Expression} */ (context.visit(node.expression)); + const key = build_expression(context, node.expression, node.metadata.expression); const body = /** @type {Expression} */ (context.visit(node.fragment)); context.state.init.push( 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 e823792993..1aefff0db0 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 @@ -331,7 +331,7 @@ export function RegularElement(node, context) { trimmed.some((node) => node.type === 'ExpressionTag'); if (use_text_content) { - const { value } = build_template_chunk(trimmed, context.visit, child_state); + const { value } = build_template_chunk(trimmed, context, child_state); const empty_string = value.type === 'Literal' && value.value === ''; if (!empty_string) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js index fec7b5762a..c3615d9d50 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js @@ -3,6 +3,7 @@ /** @import { ComponentContext } from '../types' */ import { unwrap_optional } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; +import { build_expression } from './shared/utils.js'; /** * @param {AST.RenderTag} node @@ -19,7 +20,10 @@ export function RenderTag(node, context) { /** @type {Expression[]} */ let args = []; for (let i = 0; i < raw_args.length; i++) { - let thunk = b.thunk(/** @type {Expression} */ (context.visit(raw_args[i]))); + let thunk = b.thunk( + build_expression(context, /** @type {Expression} */ (raw_args[i]), node.metadata.arguments[i]) + ); + const { has_call } = node.metadata.arguments[i]; if (has_call) { @@ -31,7 +35,11 @@ export function RenderTag(node, context) { } } - let snippet_function = /** @type {Expression} */ (context.visit(callee)); + let snippet_function = build_expression( + context, + /** @type {Expression} */ (callee), + node.metadata.expression + ); if (node.metadata.dynamic) { // If we have a chain expression then ensure a nullish snippet function gets turned into an empty one diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js index 7bfdaf1850..e6f4202a01 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js @@ -10,8 +10,7 @@ import { build_template_chunk } from './shared/utils.js'; export function TitleElement(node, context) { const { has_state, value } = build_template_chunk( /** @type {any} */ (node.fragment.nodes), - context.visit, - context.state + context ); const statement = b.stmt(b.assignment('=', b.id('$.document.title'), value)); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index 67de25b770..10f942b7d4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -7,7 +7,7 @@ import { is_ignored } from '../../../../../state.js'; import { is_event_attribute } from '../../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { build_class_directives_object, build_style_directives_object } from '../RegularElement.js'; -import { build_template_chunk, get_expression_id } from './utils.js'; +import { build_expression, build_template_chunk, get_expression_id } from './utils.js'; /** * @param {Array} attributes @@ -121,7 +121,7 @@ export function build_attribute_value(value, context, memoize = (value) => value return { value: b.literal(chunk.data), has_state: false }; } - let expression = /** @type {Expression} */ (context.visit(chunk.expression)); + let expression = build_expression(context, chunk.expression, chunk.metadata.expression); return { value: memoize(expression, chunk.metadata.expression), @@ -129,7 +129,7 @@ export function build_attribute_value(value, context, memoize = (value) => value }; } - return build_template_chunk(value, context.visit, context.state, memoize); + return build_template_chunk(value, context, context.state, memoize); } /** diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index 7af2c2d4aa..62d07014ee 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -16,8 +16,8 @@ import { build_template_chunk } from './utils.js'; * @param {boolean} is_element * @param {ComponentContext} context */ -export function process_children(nodes, initial, is_element, { visit, state }) { - const within_bound_contenteditable = state.metadata.bound_contenteditable; +export function process_children(nodes, initial, is_element, context) { + const within_bound_contenteditable = context.state.metadata.bound_contenteditable; let prev = initial; let skipped = 0; @@ -48,8 +48,8 @@ export function process_children(nodes, initial, is_element, { visit, state }) { let id = expression; if (id.type !== 'Identifier') { - id = b.id(state.scope.generate(name)); - state.init.push(b.var(id, expression)); + id = b.id(context.state.scope.generate(name)); + context.state.init.push(b.var(id, expression)); } prev = () => id; @@ -64,13 +64,13 @@ export function process_children(nodes, initial, is_element, { visit, state }) { function flush_sequence(sequence) { if (sequence.every((node) => node.type === 'Text')) { skipped += 1; - state.template.push_text(sequence); + context.state.template.push_text(sequence); return; } - state.template.push_text([{ type: 'Text', data: ' ', raw: ' ', start: -1, end: -1 }]); + context.state.template.push_text([{ type: 'Text', data: ' ', raw: ' ', start: -1, end: -1 }]); - const { has_state, value } = build_template_chunk(sequence, visit, state); + const { has_state, value } = build_template_chunk(sequence, context); // if this is a standalone `{expression}`, make sure we handle the case where // no text node was created because the expression was empty during SSR @@ -80,9 +80,9 @@ export function process_children(nodes, initial, is_element, { visit, state }) { const update = b.stmt(b.call('$.set_text', id, value)); if (has_state && !within_bound_contenteditable) { - state.update.push(update); + context.state.update.push(update); } else { - state.init.push(b.stmt(b.assignment('=', b.member(id, 'nodeValue'), value))); + context.state.init.push(b.stmt(b.assignment('=', b.member(id, 'nodeValue'), value))); } } @@ -95,18 +95,18 @@ export function process_children(nodes, initial, is_element, { visit, state }) { sequence = []; } - let child_state = state; + let child_state = context.state; - if (is_static_element(node, state)) { + if (is_static_element(node, context.state)) { skipped += 1; } else if (node.type === 'EachBlock' && nodes.length === 1 && is_element) { node.metadata.is_controlled = true; } else { const id = flush_node(false, node.type === 'RegularElement' ? node.name : 'node'); - child_state = { ...state, node: id }; + child_state = { ...context.state, node: id }; } - visit(node, child_state); + context.visit(node, child_state); } } @@ -118,7 +118,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) { // traverse to the last (n - 1) one when hydrating if (skipped > 1) { skipped -= 1; - state.init.push(b.stmt(b.call('$.next', skipped !== 1 && b.literal(skipped)))); + context.state.init.push(b.stmt(b.call('$.next', skipped !== 1 && b.literal(skipped)))); } } 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 ebf88e878f..15982899c9 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 @@ -1,6 +1,6 @@ -/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression } from 'estree' */ +/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression, Pattern } from 'estree' */ /** @import { AST, ExpressionMetadata } from '#compiler' */ -/** @import { ComponentClientTransformState, Context } from '../../types' */ +/** @import { ComponentClientTransformState, ComponentContext, Context } from '../../types' */ import { walk } from 'zimmerframe'; import { object } from '../../../../../utils/ast.js'; import * as b from '#compiler/builders'; @@ -8,7 +8,7 @@ import { sanitize_template_string } from '../../../../../utils/sanitize_template import { regex_is_valid_identifier } from '../../../../patterns.js'; import is_reference from 'is-reference'; import { dev, is_ignored, locator } from '../../../../../state.js'; -import { create_derived } from '../../utils.js'; +import { build_getter, create_derived } from '../../utils.js'; /** * @param {ComponentClientTransformState} state @@ -31,15 +31,15 @@ export function get_expression_id(expressions, value) { /** * @param {Array} values - * @param {(node: AST.SvelteNode, state: any) => any} visit + * @param {ComponentContext} context * @param {ComponentClientTransformState} state * @param {(value: Expression, metadata: ExpressionMetadata) => Expression} memoize * @returns {{ value: Expression, has_state: boolean }} */ export function build_template_chunk( values, - visit, - state, + context, + state = context.state, memoize = (value, metadata) => metadata.has_call ? get_expression_id(state.expressions, value) : value ) { @@ -66,7 +66,7 @@ export function build_template_chunk( state.scope.get('undefined') ) { let value = memoize( - /** @type {Expression} */ (visit(node.expression, state)), + build_expression(context, node.expression, node.metadata.expression, state), node.metadata.expression ); @@ -360,3 +360,48 @@ export function validate_mutation(node, context, expression) { loc && b.literal(loc.column) ); } + +/** + * + * @param {ComponentContext} context + * @param {Expression} expression + * @param {ExpressionMetadata} metadata + */ +export function build_expression(context, expression, metadata, state = context.state) { + const value = /** @type {Expression} */ (context.visit(expression, state)); + + if (context.state.analysis.runes) { + return value; + } + + if (!metadata.has_call && !metadata.has_member_expression && !metadata.has_assignment) { + return value; + } + + // Legacy reactivity is coarse-grained, looking at the statically visible dependencies. Replicate that here + const sequence = b.sequence([]); + + for (const binding of metadata.references) { + if (binding.kind === 'normal' && binding.declaration_kind !== 'import') { + continue; + } + + var getter = build_getter({ ...binding.node }, state); + + if ( + binding.kind === 'bindable_prop' || + binding.kind === 'template' || + binding.declaration_kind === 'import' || + binding.node.name === '$$props' || + binding.node.name === '$$restProps' + ) { + getter = b.call('$.deep_read_state', getter); + } + + sequence.expressions.push(getter); + } + + sequence.expressions.push(b.call('$.untrack', b.thunk(value))); + + return sequence; +} diff --git a/packages/svelte/src/compiler/phases/nodes.js b/packages/svelte/src/compiler/phases/nodes.js index 2043747ed0..c35f194b75 100644 --- a/packages/svelte/src/compiler/phases/nodes.js +++ b/packages/svelte/src/compiler/phases/nodes.js @@ -62,8 +62,11 @@ export function create_attribute(name, start, end, value) { export function create_expression_metadata() { return { dependencies: new Set(), + references: new Set(), has_state: false, - has_call: false + has_call: false, + has_member_expression: false, + has_assignment: false }; } diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index fdd6024726..558ee558f7 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -279,12 +279,18 @@ export type DeclarationKind = | 'synthetic'; export interface ExpressionMetadata { - /** All the bindings that are referenced inside this expression */ + /** All the bindings that are referenced eagerly (not inside functions) in this expression */ dependencies: Set; + /** All the bindings that are referenced inside this expression, including inside functions */ + references: Set; /** True if the expression references state directly, or _might_ (via member/call expressions) */ has_state: boolean; /** True if the expression involves a call expression (often, it will need to be wrapped in a derived) */ has_call: boolean; + /** True if the expression includes a member expression */ + has_member_expression: boolean; + /** True if the expression includes an assignment or an update */ + has_assignment: boolean; } export interface StateField { diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index cefc7fa7a2..2a7ec7b5c6 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -135,6 +135,10 @@ export namespace AST { export interface HtmlTag extends BaseNode { type: 'HtmlTag'; expression: Expression; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } /** An HTML comment */ @@ -151,6 +155,10 @@ export namespace AST { declaration: VariableDeclaration & { declarations: [VariableDeclarator & { id: Pattern; init: Expression }]; }; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } /** A `{@debug ...}` tag */ @@ -165,6 +173,7 @@ export namespace AST { expression: SimpleCallExpression | (ChainExpression & { expression: SimpleCallExpression }); /** @internal */ metadata: { + expression: ExpressionMetadata; dynamic: boolean; arguments: ExpressionMetadata[]; path: SvelteNode[]; @@ -447,6 +456,10 @@ export namespace AST { test: Expression; consequent: Fragment; alternate: Fragment | null; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } /** An `{#await ...}` block */ @@ -461,12 +474,20 @@ export namespace AST { pending: Fragment | null; then: Fragment | null; catch: Fragment | null; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } export interface KeyBlock extends BaseNode { type: 'KeyBlock'; expression: Expression; fragment: Fragment; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } export interface SnippetBlock extends BaseNode { diff --git a/packages/svelte/tests/runtime-legacy/samples/block-expression-assign/_config.js b/packages/svelte/tests/runtime-legacy/samples/block-expression-assign/_config.js new file mode 100644 index 0000000000..15adef2c9b --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/block-expression-assign/_config.js @@ -0,0 +1,12 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const button = target.querySelector('button'); + + assert.htmlEqual(target.innerHTML, `
[0,0,0,0,0,0,0,0,0]`); + flushSync(() => button?.click()); + assert.htmlEqual(target.innerHTML, `
[0,0,0,0,0,0,0,0,0]`); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/block-expression-assign/main.svelte b/packages/svelte/tests/runtime-legacy/samples/block-expression-assign/main.svelte new file mode 100644 index 0000000000..67190669ed --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/block-expression-assign/main.svelte @@ -0,0 +1,45 @@ + + +{#if a = 0}{/if} + +{#each [b = 0] as x}{x,''}{/each} + +{#key c = 0}{/key} + +{#await d = 0}{/await} + +{#snippet snip()}{/snippet} + +{@render (e = 0, snip)()} + +{@html f = 0, ''} + +
+ +{#key 1} + {@const x = (h = 0)} + {x, ''} +{/key} + +{#if 1} + {@const x = (i = 0)} + {x, ''} +{/if} + + +[{a},{b},{c},{d},{e},{f},{g},{h},{i}] + + diff --git a/packages/svelte/tests/runtime-legacy/samples/block-expression-fn-call/_config.js b/packages/svelte/tests/runtime-legacy/samples/block-expression-fn-call/_config.js new file mode 100644 index 0000000000..523dcd625d --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/block-expression-fn-call/_config.js @@ -0,0 +1,12 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const button = target.querySelector('button'); + + assert.htmlEqual(target.innerHTML, `
12 - 12`); + flushSync(() => button?.click()); + assert.htmlEqual(target.innerHTML, `
13 - 12`); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/block-expression-fn-call/main.svelte b/packages/svelte/tests/runtime-legacy/samples/block-expression-fn-call/main.svelte new file mode 100644 index 0000000000..37838f091f --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/block-expression-fn-call/main.svelte @@ -0,0 +1,36 @@ + + +{#if fn(false)}{:else if fn(true)}{/if} + +{#each fn([]) as x}{x, ''}{/each} + +{#key fn(1)}{/key} + +{#await fn(Promise.resolve())}{/await} + +{#snippet snip()}{/snippet} + +{@render fn(snip)()} + +{@html fn('')} + +
{})}>
+ +{#key 1} + {@const x = fn('')} + {x} +{/key} + + +{count1} - {count2} + + diff --git a/packages/svelte/tests/runtime-legacy/samples/block-expression-member-access/_config.js b/packages/svelte/tests/runtime-legacy/samples/block-expression-member-access/_config.js new file mode 100644 index 0000000000..0e1a5a8150 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/block-expression-member-access/_config.js @@ -0,0 +1,12 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const button = target.querySelector('button'); + + assert.htmlEqual(target.innerHTML, `
10 - 10`); + flushSync(() => button?.click()); + assert.htmlEqual(target.innerHTML, `
11 - 10`); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/block-expression-member-access/main.svelte b/packages/svelte/tests/runtime-legacy/samples/block-expression-member-access/main.svelte new file mode 100644 index 0000000000..4041be4f6f --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/block-expression-member-access/main.svelte @@ -0,0 +1,46 @@ + + +{#if obj.false}{:else if obj.true}{/if} + +{#each obj.array as x}{x, ''}{/each} + +{#key obj.string}{/key} + +{#await obj.promise}{/await} + +{#snippet snip()}{/snippet} + +{@render obj.snippet()} + +{@html obj.string} + +
+ +{#key 1} + {@const x = obj.string} + {x} +{/key} + + +{count1} - {count2} + + diff --git a/packages/svelte/tests/runtime-legacy/samples/lifecycle-render-order-for-children/Item.svelte b/packages/svelte/tests/runtime-legacy/samples/lifecycle-render-order-for-children/Item.svelte index b2e6cd046c..4127e857d5 100644 --- a/packages/svelte/tests/runtime-legacy/samples/lifecycle-render-order-for-children/Item.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/lifecycle-render-order-for-children/Item.svelte @@ -5,7 +5,7 @@ export let index; export let n; - function logRender () { + function logRender (n) { order.push(`${index}: render ${n}`); return index; } @@ -24,5 +24,5 @@
  • - {logRender()} + {logRender(n)}
  • diff --git a/packages/svelte/tests/runtime-legacy/samples/lifecycle-render-order-for-children/main.svelte b/packages/svelte/tests/runtime-legacy/samples/lifecycle-render-order-for-children/main.svelte index b05b1476fd..51dee3bc0c 100644 --- a/packages/svelte/tests/runtime-legacy/samples/lifecycle-render-order-for-children/main.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/lifecycle-render-order-for-children/main.svelte @@ -5,7 +5,7 @@ export let n = 0; - function logRender () { + function logRender (n) { order.push(`parent: render ${n}`); return 'parent'; } @@ -23,7 +23,7 @@ }) -{logRender()} +{logRender(n)}