From c544a5935131817123ffa6b405260f53b21ba6b3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 16:56:35 +0100 Subject: [PATCH 01/14] Version Packages (#15665) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/kind-elephants-behave.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/kind-elephants-behave.md diff --git a/.changeset/kind-elephants-behave.md b/.changeset/kind-elephants-behave.md deleted file mode 100644 index f52dc1fd36..0000000000 --- a/.changeset/kind-elephants-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure clearing of old values happens independent of root flushes diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 15dc5e2234..3ac52f5dc7 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.25.7 + +### Patch Changes + +- fix: ensure clearing of old values happens independent of root flushes ([#15664](https://github.com/sveltejs/svelte/pull/15664)) + ## 5.25.6 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index eeb2211151..8abad8a39b 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.25.6", + "version": "5.25.7", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 8bcf91ce39..2c39c2357f 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.25.6'; +export const VERSION = '5.25.7'; export const PUBLIC_VERSION = '5'; From 3d0bc3414920811a7e3fcf065d48b8e15b51bcdf Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sun, 6 Apr 2025 12:32:03 +0100 Subject: [PATCH 02/14] fix: address untracked_writes memory leak (#15694) * fix-untracked-writes-leak * fix * fix * Update packages/svelte/src/internal/client/runtime.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/pretty-dingos-unite.md | 5 +++++ packages/svelte/src/internal/client/runtime.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/pretty-dingos-unite.md diff --git a/.changeset/pretty-dingos-unite.md b/.changeset/pretty-dingos-unite.md new file mode 100644 index 0000000000..5703244670 --- /dev/null +++ b/.changeset/pretty-dingos-unite.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: address untracked_writes memory leak diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index a69181da98..c1c91f3551 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -476,7 +476,7 @@ export function update_reaction(reaction) { // we need to increment the read version to ensure that // any dependencies in this reaction aren't marked with // the same version - if (previous_reaction !== null) { + if (previous_reaction !== reaction) { read_version++; if (untracked_writes !== null) { From 7b850d3fba8a3edb1f7c028cb80efeae7932c7c2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:30:11 +0100 Subject: [PATCH 03/14] Version Packages (#15696) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/pretty-dingos-unite.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/pretty-dingos-unite.md diff --git a/.changeset/pretty-dingos-unite.md b/.changeset/pretty-dingos-unite.md deleted file mode 100644 index 5703244670..0000000000 --- a/.changeset/pretty-dingos-unite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: address untracked_writes memory leak diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 3ac52f5dc7..361de202b1 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.25.8 + +### Patch Changes + +- fix: address untracked_writes memory leak ([#15694](https://github.com/sveltejs/svelte/pull/15694)) + ## 5.25.7 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 8abad8a39b..8a225a798d 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.25.7", + "version": "5.25.8", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 2c39c2357f..2c8140e365 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.25.7'; +export const VERSION = '5.25.8'; export const PUBLIC_VERSION = '5'; From ab4e71a8099d1b7fb699c6ee5027e12313526812 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:47:27 -0400 Subject: [PATCH 04/14] chore(deps-dev): bump vite from 5.4.16 to 5.4.17 (#15692) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.16 to 5.4.17. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.17/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.17/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 5.4.17 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- playgrounds/sandbox/package.json | 2 +- pnpm-lock.yaml | 212 +++++++++++++++---------------- 2 files changed, 107 insertions(+), 107 deletions(-) diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json index cb44905309..d392349b60 100644 --- a/playgrounds/sandbox/package.json +++ b/playgrounds/sandbox/package.json @@ -18,7 +18,7 @@ "polka": "^1.0.0-next.25", "svelte": "workspace:*", "tinyglobby": "^0.2.12", - "vite": "^5.4.16", + "vite": "^5.4.17", "vite-plugin-inspect": "^0.8.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1f276d23f..f0e66d1681 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,7 +152,7 @@ importers: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^4.0.0-next.6 - version: 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + version: 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) polka: specifier: ^1.0.0-next.25 version: 1.0.0-next.25 @@ -163,11 +163,11 @@ importers: specifier: ^0.2.12 version: 0.2.12 vite: - specifier: ^5.4.16 - version: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + specifier: ^5.4.17 + version: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) vite-plugin-inspect: specifier: ^0.8.4 - version: 0.8.4(rollup@4.38.0)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + version: 0.8.4(rollup@4.39.0)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) packages: @@ -551,8 +551,8 @@ packages: cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.38.0': - resolution: {integrity: sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==} + '@rollup/rollup-android-arm-eabi@4.39.0': + resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} cpu: [arm] os: [android] @@ -561,8 +561,8 @@ packages: cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.38.0': - resolution: {integrity: sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==} + '@rollup/rollup-android-arm64@4.39.0': + resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} cpu: [arm64] os: [android] @@ -571,8 +571,8 @@ packages: cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.38.0': - resolution: {integrity: sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==} + '@rollup/rollup-darwin-arm64@4.39.0': + resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} cpu: [arm64] os: [darwin] @@ -581,18 +581,18 @@ packages: cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.38.0': - resolution: {integrity: sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==} + '@rollup/rollup-darwin-x64@4.39.0': + resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.38.0': - resolution: {integrity: sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==} + '@rollup/rollup-freebsd-arm64@4.39.0': + resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.38.0': - resolution: {integrity: sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==} + '@rollup/rollup-freebsd-x64@4.39.0': + resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} cpu: [x64] os: [freebsd] @@ -601,8 +601,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.38.0': - resolution: {integrity: sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==} + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} cpu: [arm] os: [linux] @@ -611,8 +611,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.38.0': - resolution: {integrity: sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==} + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} cpu: [arm] os: [linux] @@ -621,8 +621,8 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.38.0': - resolution: {integrity: sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==} + '@rollup/rollup-linux-arm64-gnu@4.39.0': + resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} cpu: [arm64] os: [linux] @@ -631,13 +631,13 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.38.0': - resolution: {integrity: sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==} + '@rollup/rollup-linux-arm64-musl@4.39.0': + resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.38.0': - resolution: {integrity: sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==} + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} cpu: [loong64] os: [linux] @@ -646,8 +646,8 @@ packages: cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.38.0': - resolution: {integrity: sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} cpu: [ppc64] os: [linux] @@ -656,13 +656,13 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.38.0': - resolution: {integrity: sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==} + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.38.0': - resolution: {integrity: sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==} + '@rollup/rollup-linux-riscv64-musl@4.39.0': + resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} cpu: [riscv64] os: [linux] @@ -671,8 +671,8 @@ packages: cpu: [s390x] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.38.0': - resolution: {integrity: sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==} + '@rollup/rollup-linux-s390x-gnu@4.39.0': + resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} cpu: [s390x] os: [linux] @@ -681,8 +681,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.38.0': - resolution: {integrity: sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==} + '@rollup/rollup-linux-x64-gnu@4.39.0': + resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} cpu: [x64] os: [linux] @@ -691,8 +691,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.38.0': - resolution: {integrity: sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==} + '@rollup/rollup-linux-x64-musl@4.39.0': + resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} cpu: [x64] os: [linux] @@ -701,8 +701,8 @@ packages: cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.38.0': - resolution: {integrity: sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==} + '@rollup/rollup-win32-arm64-msvc@4.39.0': + resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} cpu: [arm64] os: [win32] @@ -711,8 +711,8 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.38.0': - resolution: {integrity: sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==} + '@rollup/rollup-win32-ia32-msvc@4.39.0': + resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} cpu: [ia32] os: [win32] @@ -721,8 +721,8 @@ packages: cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.38.0': - resolution: {integrity: sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==} + '@rollup/rollup-win32-x64-msvc@4.39.0': + resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} cpu: [x64] os: [win32] @@ -1974,8 +1974,8 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rollup@4.38.0: - resolution: {integrity: sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==} + rollup@4.39.0: + resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2310,8 +2310,8 @@ packages: terser: optional: true - vite@5.4.16: - resolution: {integrity: sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==} + vite@5.4.17: + resolution: {integrity: sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2860,120 +2860,120 @@ snapshots: optionalDependencies: rollup: 4.22.4 - '@rollup/pluginutils@5.1.0(rollup@4.38.0)': + '@rollup/pluginutils@5.1.0(rollup@4.39.0)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.38.0 + rollup: 4.39.0 '@rollup/rollup-android-arm-eabi@4.22.4': optional: true - '@rollup/rollup-android-arm-eabi@4.38.0': + '@rollup/rollup-android-arm-eabi@4.39.0': optional: true '@rollup/rollup-android-arm64@4.22.4': optional: true - '@rollup/rollup-android-arm64@4.38.0': + '@rollup/rollup-android-arm64@4.39.0': optional: true '@rollup/rollup-darwin-arm64@4.22.4': optional: true - '@rollup/rollup-darwin-arm64@4.38.0': + '@rollup/rollup-darwin-arm64@4.39.0': optional: true '@rollup/rollup-darwin-x64@4.22.4': optional: true - '@rollup/rollup-darwin-x64@4.38.0': + '@rollup/rollup-darwin-x64@4.39.0': optional: true - '@rollup/rollup-freebsd-arm64@4.38.0': + '@rollup/rollup-freebsd-arm64@4.39.0': optional: true - '@rollup/rollup-freebsd-x64@4.38.0': + '@rollup/rollup-freebsd-x64@4.39.0': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.22.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.38.0': + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': optional: true '@rollup/rollup-linux-arm-musleabihf@4.22.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.38.0': + '@rollup/rollup-linux-arm-musleabihf@4.39.0': optional: true '@rollup/rollup-linux-arm64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.38.0': + '@rollup/rollup-linux-arm64-gnu@4.39.0': optional: true '@rollup/rollup-linux-arm64-musl@4.22.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.38.0': + '@rollup/rollup-linux-arm64-musl@4.39.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.38.0': + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.22.4': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.38.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': optional: true '@rollup/rollup-linux-riscv64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.38.0': + '@rollup/rollup-linux-riscv64-gnu@4.39.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.38.0': + '@rollup/rollup-linux-riscv64-musl@4.39.0': optional: true '@rollup/rollup-linux-s390x-gnu@4.22.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.38.0': + '@rollup/rollup-linux-s390x-gnu@4.39.0': optional: true '@rollup/rollup-linux-x64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.38.0': + '@rollup/rollup-linux-x64-gnu@4.39.0': optional: true '@rollup/rollup-linux-x64-musl@4.22.4': optional: true - '@rollup/rollup-linux-x64-musl@4.38.0': + '@rollup/rollup-linux-x64-musl@4.39.0': optional: true '@rollup/rollup-win32-arm64-msvc@4.22.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.38.0': + '@rollup/rollup-win32-arm64-msvc@4.39.0': optional: true '@rollup/rollup-win32-ia32-msvc@4.22.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.38.0': + '@rollup/rollup-win32-ia32-msvc@4.39.0': optional: true '@rollup/rollup-win32-x64-msvc@4.22.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.38.0': + '@rollup/rollup-win32-x64-msvc@4.39.0': optional: true '@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1)': @@ -3000,25 +3000,25 @@ snapshots: typescript: 5.5.4 typescript-eslint: 8.26.0(eslint@9.9.1)(typescript@5.5.4) - '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) debug: 4.4.0 svelte: link:packages/svelte - vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': + '@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: link:packages/svelte - vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) - vitefu: 0.2.5(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vitefu: 0.2.5(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) transitivePeerDependencies: - supports-color @@ -4292,30 +4292,30 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.22.4 fsevents: 2.3.3 - rollup@4.38.0: + rollup@4.39.0: dependencies: '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.38.0 - '@rollup/rollup-android-arm64': 4.38.0 - '@rollup/rollup-darwin-arm64': 4.38.0 - '@rollup/rollup-darwin-x64': 4.38.0 - '@rollup/rollup-freebsd-arm64': 4.38.0 - '@rollup/rollup-freebsd-x64': 4.38.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.38.0 - '@rollup/rollup-linux-arm-musleabihf': 4.38.0 - '@rollup/rollup-linux-arm64-gnu': 4.38.0 - '@rollup/rollup-linux-arm64-musl': 4.38.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.38.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.38.0 - '@rollup/rollup-linux-riscv64-gnu': 4.38.0 - '@rollup/rollup-linux-riscv64-musl': 4.38.0 - '@rollup/rollup-linux-s390x-gnu': 4.38.0 - '@rollup/rollup-linux-x64-gnu': 4.38.0 - '@rollup/rollup-linux-x64-musl': 4.38.0 - '@rollup/rollup-win32-arm64-msvc': 4.38.0 - '@rollup/rollup-win32-ia32-msvc': 4.38.0 - '@rollup/rollup-win32-x64-msvc': 4.38.0 + '@rollup/rollup-android-arm-eabi': 4.39.0 + '@rollup/rollup-android-arm64': 4.39.0 + '@rollup/rollup-darwin-arm64': 4.39.0 + '@rollup/rollup-darwin-x64': 4.39.0 + '@rollup/rollup-freebsd-arm64': 4.39.0 + '@rollup/rollup-freebsd-x64': 4.39.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 + '@rollup/rollup-linux-arm-musleabihf': 4.39.0 + '@rollup/rollup-linux-arm64-gnu': 4.39.0 + '@rollup/rollup-linux-arm64-musl': 4.39.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-musl': 4.39.0 + '@rollup/rollup-linux-s390x-gnu': 4.39.0 + '@rollup/rollup-linux-x64-gnu': 4.39.0 + '@rollup/rollup-linux-x64-musl': 4.39.0 + '@rollup/rollup-win32-arm64-msvc': 4.39.0 + '@rollup/rollup-win32-ia32-msvc': 4.39.0 + '@rollup/rollup-win32-x64-msvc': 4.39.0 fsevents: 2.3.3 rrweb-cssom@0.7.1: {} @@ -4562,7 +4562,7 @@ snapshots: debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 1.1.2 - vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - '@types/node' - less @@ -4574,10 +4574,10 @@ snapshots: - supports-color - terser - vite-plugin-inspect@0.8.4(rollup@4.38.0)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): + vite-plugin-inspect@0.8.4(rollup@4.39.0)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): dependencies: '@antfu/utils': 0.7.8 - '@rollup/pluginutils': 5.1.0(rollup@4.38.0) + '@rollup/pluginutils': 5.1.0(rollup@4.39.0) debug: 4.4.0 error-stack-parser-es: 0.1.1 fs-extra: 11.2.0 @@ -4585,7 +4585,7 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.1.1 sirv: 2.0.4 - vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - rollup - supports-color @@ -4602,11 +4602,11 @@ snapshots: sass: 1.70.0 terser: 5.27.0 - vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): + vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: esbuild: 0.21.5 postcss: 8.5.3 - rollup: 4.38.0 + rollup: 4.39.0 optionalDependencies: '@types/node': 20.12.7 fsevents: 2.3.3 @@ -4614,9 +4614,9 @@ snapshots: sass: 1.70.0 terser: 5.27.0 - vitefu@0.2.5(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): + vitefu@0.2.5(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): optionalDependencies: - vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: From 8e21c41c27fd062fed2b9d53ab3f19ac1705142f Mon Sep 17 00:00:00 2001 From: James Scott-Brown Date: Mon, 7 Apr 2025 21:01:48 +0100 Subject: [PATCH 05/14] Link from $host directive docs to custom elements docs (#15686) --- documentation/docs/02-runes/08-$host.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/02-runes/08-$host.md b/documentation/docs/02-runes/08-$host.md index 7b5e041e5e..ba6f0a5b5b 100644 --- a/documentation/docs/02-runes/08-$host.md +++ b/documentation/docs/02-runes/08-$host.md @@ -2,7 +2,7 @@ title: $host --- -When compiling a component as a custom element, the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)): +When compiling a component as a [custom element](custom-elements), the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)): ```svelte From 6a668c4daf553211a2315f6ef0bfd84c8ef344f0 Mon Sep 17 00:00:00 2001 From: Simon Lund <41208671+simon-lund@users.noreply.github.com> Date: Mon, 7 Apr 2025 22:33:23 +0200 Subject: [PATCH 06/14] docs:Update 02-context.md (#15700) * Update 02-context.md use `const` for key variable and assign it a symbol instead of empty of object. * Update documentation/docs/06-runtime/02-context.md --------- Co-authored-by: Rich Harris --- documentation/docs/06-runtime/02-context.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md index b698323a04..4204bcfe6d 100644 --- a/documentation/docs/06-runtime/02-context.md +++ b/documentation/docs/06-runtime/02-context.md @@ -94,7 +94,7 @@ interface User {} // ---cut--- import { getContext, setContext } from 'svelte'; -let key = {}; +const key = {}; /** @param {User} user */ export function setUserContext(user) { From caf62ee6872fc0c9944aea98585597d2d1712e21 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 8 Apr 2025 07:20:36 -0400 Subject: [PATCH 07/14] fix: allow `$.state` and `$.derived` to be treeshaken (#15702) --- .changeset/clever-news-enjoy.md | 5 +++++ packages/svelte/src/internal/client/reactivity/deriveds.js | 1 + packages/svelte/src/internal/client/reactivity/sources.js | 1 + 3 files changed, 7 insertions(+) create mode 100644 .changeset/clever-news-enjoy.md diff --git a/.changeset/clever-news-enjoy.md b/.changeset/clever-news-enjoy.md new file mode 100644 index 0000000000..2ff3dcbe56 --- /dev/null +++ b/.changeset/clever-news-enjoy.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow `$.state` and `$.derived` to be treeshaken diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index cd7bbba02f..86171c2b2d 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -67,6 +67,7 @@ export function derived(fn) { * @param {() => V} fn * @returns {Derived} */ +/*#__NO_SIDE_EFFECTS__*/ export function user_derived(fn) { const d = derived(fn); diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index e4834902fe..ceab59b37c 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -78,6 +78,7 @@ export function source(v, stack) { * @param {V} v * @param {Error | null} [stack] */ +/*#__NO_SIDE_EFFECTS__*/ export function state(v, stack) { const s = source(v, stack); From 98d14ece6688280f98b185cc9e69fbb309e1afa8 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:41:54 +0200 Subject: [PATCH 08/14] fix: rework binding ownership validation (#15678) * remove old validation * fix: rework binding ownership validation Previously we were doing stack-based retrieval of the owner, which while catching more cases was costly (performance-wise) and prone to errors (as shown by many issues over the months). This drastically simplifies the ownership validation - we now only do simple static analysis to check which props are mutated and wrap them with runtime checks to see if a binding was established. Besides making the implementation simpler and more performant, this also follows an insight we had over the months: Most people don't really know what to do with this warning when it's shown beyond very simple cases. Either it's not actionable because they don't really know how to fix it or they question if they should at all (in some cases rightfully so). Now that the warning is only shown in simple and easy-to-reason-about cases, it has a much better signal-to-noise-ratio and will hopefully guide people in the right direction early on (learn from the obvious cases to not write spaghetti code in more complex cases). closes #15532 closes #15210 closes #14893 closes #13607 closes #13139 closes #11861 * remove some now obsolete tests * fix * better warnings now that we have more info * fix * hoist * we only care about mutation, not reassignment * tidy * handle prop aliases * mutation validation is only tangentially linked to context requirement * no need for two vars, one will do * update warning, include mutation location * tweak --------- Co-authored-by: Rich Harris --- .changeset/sweet-ants-care.md | 5 + .../.generated/client-warnings.md | 8 +- .../messages/client-warnings/warnings.md | 6 +- .../src/compiler/phases/2-analyze/index.js | 1 + .../3-transform/client/transform-client.js | 6 + .../phases/3-transform/client/utils.js | 33 ++- .../client/visitors/AssignmentExpression.js | 7 +- .../3-transform/client/visitors/ClassBody.js | 27 -- .../client/visitors/UpdateExpression.js | 6 +- .../client/visitors/shared/component.js | 34 ++- .../client/visitors/shared/utils.js | 63 ++++- .../svelte/src/compiler/phases/types.d.ts | 1 + .../svelte/src/internal/client/constants.js | 1 - .../svelte/src/internal/client/context.js | 10 - .../src/internal/client/dev/ownership.js | 247 ++++-------------- packages/svelte/src/internal/client/index.js | 10 +- packages/svelte/src/internal/client/proxy.js | 81 +----- .../src/internal/client/reactivity/sources.js | 2 +- .../svelte/src/internal/client/types.d.ts | 8 - .../svelte/src/internal/client/warnings.js | 19 +- packages/svelte/src/utils.js | 6 +- .../non-local-mutation-discouraged/_config.js | 2 +- .../non-local-mutation-global-2/_config.js | 11 - .../non-local-mutation-global-2/child.svelte | 18 -- .../non-local-mutation-global-2/main.svelte | 5 - .../_config.js | 6 +- .../main.svelte | 9 + .../sub.svelte | 0 .../_config.js | 33 +-- .../main.svelte | 11 +- .../state.svelte.js | 15 +- .../sub.svelte | 0 .../Child.svelte | 0 .../_config.js | 33 +-- .../main.svelte | 14 +- .../_config.js | 37 --- .../main.svelte | 11 - .../state.svelte.js | 13 - .../sub.svelte | 8 - .../_config.js | 24 -- .../main.svelte | 8 - .../state.svelte.js | 14 - .../Child.svelte | 9 - .../_config.js | 37 --- .../main.svelte | 17 -- .../_config.js | 24 -- .../main.svelte | 13 - .../Counter.svelte | 7 - .../main.svelte | 9 - .../state.svelte.js | 3 - .../_config.js | 2 +- .../_config.js | 2 +- .../_config.js | 1 - .../CounterBinding.svelte | 7 - .../CounterContext.svelte | 13 - .../_config.js | 34 --- .../main.svelte | 46 ---- 57 files changed, 271 insertions(+), 806 deletions(-) create mode 100644 .changeset/sweet-ants-care.md delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte rename packages/svelte/tests/runtime-runes/samples/{non-local-mutation-inherited-owner => non-local-mutation-inherited-owner-1}/_config.js (74%) create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte rename packages/svelte/tests/runtime-runes/samples/{non-local-mutation-inherited-owner-3 => non-local-mutation-inherited-owner-1}/sub.svelte (100%) rename packages/svelte/tests/runtime-runes/samples/{non-local-mutation-inherited-owner-5 => non-local-mutation-inherited-owner-2}/sub.svelte (100%) rename packages/svelte/tests/runtime-runes/samples/{non-local-mutation-inherited-owner-7 => non-local-mutation-inherited-owner-3}/Child.svelte (100%) delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte diff --git a/.changeset/sweet-ants-care.md b/.changeset/sweet-ants-care.md new file mode 100644 index 0000000000..b4805626ab --- /dev/null +++ b/.changeset/sweet-ants-care.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: rework binding ownership validation diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index 284e9a7c3e..77d1df4cdd 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -161,7 +161,7 @@ Tried to unmount a component that was not mounted ### ownership_invalid_binding ``` -%parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent% +%parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`) ``` Consider three components `GrandParent`, `Parent` and `Child`. If you do ``, inside `GrandParent` pass on the variable via `` (note the missing `bind:`) and then do `` inside `Parent`, this warning is thrown. @@ -171,11 +171,7 @@ To fix it, `bind:` to the value instead of just passing a property (i.e. in this ### ownership_invalid_mutation ``` -Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead -``` - -``` -%component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead +Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead ``` Consider the following code: diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md index 943cf6f01f..f8e9ebd8a0 100644 --- a/packages/svelte/messages/client-warnings/warnings.md +++ b/packages/svelte/messages/client-warnings/warnings.md @@ -132,7 +132,7 @@ During development, this error is often preceeded by a `console.error` detailing ## ownership_invalid_binding -> %parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent% +> %parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`) Consider three components `GrandParent`, `Parent` and `Child`. If you do ``, inside `GrandParent` pass on the variable via `` (note the missing `bind:`) and then do `` inside `Parent`, this warning is thrown. @@ -140,9 +140,7 @@ To fix it, `bind:` to the value instead of just passing a property (i.e. in this ## ownership_invalid_mutation -> Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead - -> %component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead +> Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead Consider the following code: diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 1f636c32df..c62fb03e8f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -432,6 +432,7 @@ export function analyze_component(root, source, options) { uses_component_bindings: false, uses_render_tags: false, needs_context: false, + needs_mutation_validation: false, needs_props: false, event_directive_node: null, uses_event_attributes: false, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 0bdfbae746..38fcee8d6f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -393,6 +393,12 @@ export function client_component(analysis, options) { ); } + if (analysis.needs_mutation_validation) { + component_block.body.unshift( + b.var('$$ownership_validator', b.call('$.create_ownership_validator', b.id('$$props'))) + ); + } + const should_inject_context = dev || analysis.needs_context || diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 28e3fabb19..a37ecd31cc 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -1,10 +1,10 @@ -/** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Pattern, PrivateIdentifier, Statement } from 'estree' */ -/** @import { AST, Binding } from '#compiler' */ +/** @import { ArrowFunctionExpression, AssignmentExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Node, Pattern, UpdateExpression } from 'estree' */ +/** @import { Binding } from '#compiler' */ /** @import { ClientTransformState, ComponentClientTransformState, ComponentContext } from './types.js' */ /** @import { Analysis } from '../../types.js' */ /** @import { Scope } from '../../scope.js' */ import * as b from '../../../utils/builders.js'; -import { extract_identifiers, is_simple_expression } from '../../../utils/ast.js'; +import { is_simple_expression } from '../../../utils/ast.js'; import { PROPS_IS_LAZY_INITIAL, PROPS_IS_IMMUTABLE, @@ -13,7 +13,8 @@ import { PROPS_IS_BINDABLE } from '../../../../constants.js'; import { dev } from '../../../state.js'; -import { get_value } from './visitors/shared/declarations.js'; +import { walk } from 'zimmerframe'; +import { validate_mutation } from './visitors/shared/utils.js'; /** * @param {Binding} binding @@ -110,6 +111,30 @@ function get_hoisted_params(node, context) { } } } + + if (dev) { + // this is a little hacky, but necessary for ownership validation + // to work inside hoisted event handlers + + /** + * @param {AssignmentExpression | UpdateExpression} node + * @param {{ next: () => void, stop: () => void }} context + */ + function visit(node, { next, stop }) { + if (validate_mutation(node, /** @type {any} */ (context), node) !== node) { + params.push(b.id('$$ownership_validator')); + stop(); + } else { + next(); + } + } + + walk(/** @type {Node} */ (node), null, { + AssignmentExpression: visit, + UpdateExpression: visit + }); + } + return params; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js index 850cd83b15..4baa1c8e6c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js @@ -7,9 +7,10 @@ import { get_attribute_expression, is_event_attribute } from '../../../../utils/ast.js'; -import { dev, is_ignored, locate_node } from '../../../../state.js'; +import { dev, locate_node } from '../../../../state.js'; import { should_proxy } from '../utils.js'; import { visit_assignment_expression } from '../../shared/assignments.js'; +import { validate_mutation } from './shared/utils.js'; /** * @param {AssignmentExpression} node @@ -20,9 +21,7 @@ export function AssignmentExpression(node, context) { visit_assignment_expression(node, context, build_assignment) ?? context.next() ); - return is_ignored(node, 'ownership_invalid_mutation') - ? b.call('$.skip_ownership_validation', b.thunk(expression)) - : expression; + return validate_mutation(node, context, expression); } /** diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js index 24c20d7f94..efc3c95c3c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js @@ -161,33 +161,6 @@ export function ClassBody(node, context) { body.push(/** @type {MethodDefinition} **/ (context.visit(definition, child_state))); } - if (dev && public_state.size > 0) { - // add an `[$.ADD_OWNER]` method so that a class with state fields can widen ownership - body.push( - b.method( - 'method', - b.id('$.ADD_OWNER'), - [b.id('owner')], - [ - b.stmt( - b.call( - '$.add_owner_to_class', - b.this, - b.id('owner'), - b.array( - Array.from(public_state).map(([name]) => - b.thunk(b.call('$.get', b.member(b.this, b.private_id(name)))) - ) - ), - is_ignored(node, 'ownership_invalid_binding') && b.true - ) - ) - ], - true - ) - ); - } - return { ...node, body }; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js index 13c1b4bc51..63c03b0eb6 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js @@ -1,8 +1,8 @@ /** @import { AssignmentExpression, Expression, UpdateExpression } from 'estree' */ /** @import { Context } from '../types' */ -import { is_ignored } from '../../../../state.js'; import { object } from '../../../../utils/ast.js'; import * as b from '../../../../utils/builders.js'; +import { validate_mutation } from './shared/utils.js'; /** * @param {UpdateExpression} node @@ -51,7 +51,5 @@ export function UpdateExpression(node, context) { ); } - return is_ignored(node, 'ownership_invalid_mutation') - ? b.call('$.skip_ownership_validation', b.thunk(update)) - : update; + return validate_mutation(node, context, update); } 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 2bae4486dc..2ea68e206e 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 @@ -179,19 +179,29 @@ export function build_component(node, component_name, context, anchor = context. } else if (attribute.type === 'BindDirective') { const expression = /** @type {Expression} */ (context.visit(attribute.expression)); - if (dev && attribute.name !== 'this') { - binding_initializers.push( - b.stmt( - b.call( - b.id('$.add_owner_effect'), - expression.type === 'SequenceExpression' - ? expression.expressions[0] - : b.thunk(expression), - b.id(component_name), - is_ignored(node, 'ownership_invalid_binding') && b.true + if ( + dev && + attribute.name !== 'this' && + !is_ignored(node, 'ownership_invalid_binding') && + // bind:x={() => x.y, y => x.y = y} will be handled by the assignment expression binding validation + attribute.expression.type !== 'SequenceExpression' + ) { + const left = object(attribute.expression); + const binding = left && context.state.scope.get(left.name); + + if (binding?.kind === 'bindable_prop' || binding?.kind === 'prop') { + context.state.analysis.needs_mutation_validation = true; + binding_initializers.push( + b.stmt( + b.call( + '$$ownership_validator.binding', + b.literal(binding.node.name), + b.id(component_name), + b.thunk(expression) + ) ) - ) - ); + ); + } } if (expression.type === 'SequenceExpression') { 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 df6308d631..af6e56f70c 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,13 +1,13 @@ -/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */ +/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression } from 'estree' */ /** @import { AST, ExpressionMetadata } from '#compiler' */ -/** @import { ComponentClientTransformState } from '../../types' */ +/** @import { ComponentClientTransformState, Context } from '../../types' */ import { walk } from 'zimmerframe'; import { object } from '../../../../../utils/ast.js'; import * as b from '../../../../../utils/builders.js'; import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js'; import { regex_is_valid_identifier } from '../../../../patterns.js'; import is_reference from 'is-reference'; -import { locator } from '../../../../../state.js'; +import { dev, is_ignored, locator } from '../../../../../state.js'; import { create_derived } from '../../utils.js'; /** @@ -295,3 +295,60 @@ export function validate_binding(state, binding, expression) { ) ); } + +/** + * In dev mode validate mutations to props + * @param {AssignmentExpression | UpdateExpression} node + * @param {Context} context + * @param {Expression} expression + */ +export function validate_mutation(node, context, expression) { + let left = /** @type {Expression | Super} */ ( + node.type === 'AssignmentExpression' ? node.left : node.argument + ); + + if (!dev || left.type !== 'MemberExpression' || is_ignored(node, 'ownership_invalid_mutation')) { + return expression; + } + + const name = object(left); + if (!name) return expression; + + const binding = context.state.scope.get(name.name); + if (binding?.kind !== 'prop' && binding?.kind !== 'bindable_prop') return expression; + + const state = /** @type {ComponentClientTransformState} */ (context.state); + state.analysis.needs_mutation_validation = true; + + /** @type {Array} */ + const path = []; + + while (left.type === 'MemberExpression') { + if (left.property.type === 'Literal') { + path.unshift(left.property); + } else if (left.property.type === 'Identifier') { + if (left.computed) { + path.unshift(left.property); + } else { + path.unshift(b.literal(left.property.name)); + } + } else { + return expression; + } + + left = left.object; + } + + path.unshift(b.literal(name.name)); + + const loc = locator(/** @type {number} */ (left.start)); + + return b.call( + '$$ownership_validator.mutation', + b.literal(binding.prop_alias), + b.array(path), + expression, + loc && b.literal(loc.line), + loc && b.literal(loc.column) + ); +} diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index abe2b115de..f09b881303 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -53,6 +53,7 @@ export interface ComponentAnalysis extends Analysis { uses_component_bindings: boolean; uses_render_tags: boolean; needs_context: boolean; + needs_mutation_validation: boolean; needs_props: boolean; /** Set to the first event directive (on:x) found on a DOM element in the code */ event_directive_node: AST.OnDirective | null; diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 21377c1cc8..7e5196c606 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -23,6 +23,5 @@ export const EFFECT_HAS_DERIVED = 1 << 20; export const EFFECT_IS_UPDATING = 1 << 21; export const STATE_SYMBOL = Symbol('$state'); -export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); export const LEGACY_PROPS = Symbol('legacy props'); export const LOADING_ATTR_SYMBOL = Symbol(''); diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index bfca9d5e6a..7a2fdd0edb 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -1,7 +1,6 @@ /** @import { ComponentContext } from '#client' */ import { DEV } from 'esm-env'; -import { add_owner } from './dev/ownership.js'; import { lifecycle_outside_component } from '../shared/errors.js'; import { source } from './reactivity/sources.js'; import { @@ -67,15 +66,6 @@ export function getContext(key) { */ export function setContext(key, context) { const context_map = get_or_init_context_map('setContext'); - - if (DEV) { - // When state is put into context, we treat as if it's global from now on. - // We do for performance reasons (it's for example very expensive to call - // getContext on a big object many times when part of a list component) - // and danger of false positives. - untrack(() => add_owner(context, null, true)); - } - context_map.set(key, context); return context; } diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index 62119b36db..6c40a744df 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -1,12 +1,11 @@ -/** @import { ProxyMetadata } from '#client' */ /** @typedef {{ file: string, line: number, column: number }} Location */ -import { STATE_SYMBOL_METADATA } from '../constants.js'; -import { render_effect, user_pre_effect } from '../reactivity/effects.js'; -import { dev_current_component_function } from '../context.js'; -import { get_prototype_of } from '../../shared/utils.js'; +import { get_descriptor } from '../../shared/utils.js'; +import { LEGACY_PROPS, STATE_SYMBOL } from '../constants.js'; +import { FILENAME } from '../../../constants.js'; +import { component_context } from '../context.js'; import * as w from '../warnings.js'; -import { FILENAME, UNINITIALIZED } from '../../../constants.js'; +import { sanitize_location } from '../../../utils.js'; /** @type {Record>} */ const boundaries = {}; @@ -71,8 +70,6 @@ export function get_component() { return null; } -export const ADD_OWNER = Symbol('ADD_OWNER'); - /** * Together with `mark_module_end`, this function establishes the boundaries of a `.svelte` file, * such that subsequent calls to `get_component` can tell us which component is responsible @@ -108,197 +105,69 @@ export function mark_module_end(component) { } /** - * @param {any} object - * @param {any | null} owner - * @param {boolean} [global] - * @param {boolean} [skip_warning] + * Sets up a validator that + * - traverses the path of a prop to find out if it is allowed to be mutated + * - checks that the binding chain is not interrupted + * @param {Record} props */ -export function add_owner(object, owner, global = false, skip_warning = false) { - if (object && !global) { - const component = dev_current_component_function; - const metadata = object[STATE_SYMBOL_METADATA]; - if (metadata && !has_owner(metadata, component)) { - let original = get_owner(metadata); - - if (owner && owner[FILENAME] !== component[FILENAME] && !skip_warning) { - w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]); +export function create_ownership_validator(props) { + const component = component_context?.function; + const parent = component_context?.p?.function; + + return { + /** + * @param {string} prop + * @param {any[]} path + * @param {any} result + * @param {number} line + * @param {number} column + */ + mutation: (prop, path, result, line, column) => { + const name = path[0]; + if (is_bound(props, name) || !parent) { + return result; } - } - } - add_owner_to_object(object, owner, new Set()); -} - -/** - * @param {() => unknown} get_object - * @param {any} Component - * @param {boolean} [skip_warning] - */ -export function add_owner_effect(get_object, Component, skip_warning = false) { - user_pre_effect(() => { - add_owner(get_object(), Component, false, skip_warning); - }); -} - -/** - * @param {any} _this - * @param {Function} owner - * @param {Array<() => any>} getters - * @param {boolean} skip_warning - */ -export function add_owner_to_class(_this, owner, getters, skip_warning) { - _this[ADD_OWNER].current ||= getters.map(() => UNINITIALIZED); - - for (let i = 0; i < getters.length; i += 1) { - const current = getters[i](); - // For performance reasons we only re-add the owner if the state has changed - if (current !== _this[ADD_OWNER][i]) { - _this[ADD_OWNER].current[i] = current; - add_owner(current, owner, false, skip_warning); - } - } -} - -/** - * @param {ProxyMetadata | null} from - * @param {ProxyMetadata} to - */ -export function widen_ownership(from, to) { - if (to.owners === null) { - return; - } - - while (from) { - if (from.owners === null) { - to.owners = null; - break; - } - - for (const owner of from.owners) { - to.owners.add(owner); - } - - from = from.parent; - } -} - -/** - * @param {any} object - * @param {Function | null} owner If `null`, then the object is globally owned and will not be checked - * @param {Set} seen - */ -function add_owner_to_object(object, owner, seen) { - const metadata = /** @type {ProxyMetadata} */ (object?.[STATE_SYMBOL_METADATA]); + let value = props[name]; - if (metadata) { - // this is a state proxy, add owner directly, if not globally shared - if ('owners' in metadata && metadata.owners != null) { - if (owner) { - metadata.owners.add(owner); - } else { - metadata.owners = null; + for (let i = 1; i < path.length - 1; i++) { + if (!value?.[STATE_SYMBOL]) { + return result; + } + value = value[path[i]]; } - } - } else if (object && typeof object === 'object') { - if (seen.has(object)) return; - seen.add(object); - if (ADD_OWNER in object && object[ADD_OWNER]) { - // this is a class with state fields. we put this in a render effect - // so that if state is replaced (e.g. `instance.name = { first, last }`) - // the new state is also co-owned by the caller of `getContext` - render_effect(() => { - object[ADD_OWNER](owner); - }); - } else { - var proto = get_prototype_of(object); - if (proto === Object.prototype) { - // recurse until we find a state proxy - for (const key in object) { - if (Object.getOwnPropertyDescriptor(object, key)?.get) { - // Similar to the class case; the getter could update with a new state - let current = UNINITIALIZED; - render_effect(() => { - const next = object[key]; - if (current !== next) { - current = next; - add_owner_to_object(next, owner, seen); - } - }); - } else { - add_owner_to_object(object[key], owner, seen); - } - } - } else if (proto === Array.prototype) { - // recurse until we find a state proxy - for (let i = 0; i < object.length; i += 1) { - add_owner_to_object(object[i], owner, seen); - } + const location = sanitize_location(`${component[FILENAME]}:${line}:${column}`); + + w.ownership_invalid_mutation(name, location, prop, parent[FILENAME]); + + return result; + }, + /** + * @param {any} key + * @param {any} child_component + * @param {() => any} value + */ + binding: (key, child_component, value) => { + if (!is_bound(props, key) && parent && value()?.[STATE_SYMBOL]) { + w.ownership_invalid_binding( + component[FILENAME], + key, + child_component[FILENAME], + parent[FILENAME] + ); } } - } + }; } /** - * @param {ProxyMetadata} metadata - * @param {Function} component - * @returns {boolean} + * @param {Record} props + * @param {string} prop_name */ -function has_owner(metadata, component) { - if (metadata.owners === null) { - return true; - } - - return ( - metadata.owners.has(component) || - // This helps avoid false positives when using HMR, where the component function is replaced - (FILENAME in component && - [...metadata.owners].some( - (owner) => /** @type {any} */ (owner)[FILENAME] === component[FILENAME] - )) || - (metadata.parent !== null && has_owner(metadata.parent, component)) - ); -} - -/** - * @param {ProxyMetadata} metadata - * @returns {any} - */ -function get_owner(metadata) { - return ( - metadata?.owners?.values().next().value ?? - get_owner(/** @type {ProxyMetadata} */ (metadata.parent)) - ); -} - -let skip = false; - -/** - * @param {() => any} fn - */ -export function skip_ownership_validation(fn) { - skip = true; - fn(); - skip = false; -} - -/** - * @param {ProxyMetadata} metadata - */ -export function check_ownership(metadata) { - if (skip) return; - - const component = get_component(); - - if (component && !has_owner(metadata, component)) { - let original = get_owner(metadata); - - // @ts-expect-error - if (original[FILENAME] !== component[FILENAME]) { - // @ts-expect-error - w.ownership_invalid_mutation(component[FILENAME], original[FILENAME]); - } else { - w.ownership_invalid_mutation(); - } - } +function is_bound(props, prop_name) { + // Can be the case when someone does `mount(Component, props)` with `let props = $state({...})` + // or `createClassComponent(Component, props)` + const is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props; + return !!get_descriptor(props, prop_name)?.set || (is_entry_props && prop_name in props); } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index a5f93e8b17..7eed8a744a 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -4,15 +4,7 @@ export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js'; export { cleanup_styles } from './dev/css.js'; export { add_locations } from './dev/elements.js'; export { hmr } from './dev/hmr.js'; -export { - ADD_OWNER, - add_owner, - mark_module_start, - mark_module_end, - add_owner_effect, - add_owner_to_class, - skip_ownership_validation -} from './dev/ownership.js'; +export { mark_module_start, mark_module_end, create_ownership_validator } from './dev/ownership.js'; export { check_target, legacy_api } from './dev/legacy.js'; export { trace } from './dev/tracing.js'; export { inspect } from './dev/inspect.js'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index fab271c916..5e0aa3dbc3 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -1,7 +1,6 @@ -/** @import { ProxyMetadata, Source } from '#client' */ +/** @import { Source } from '#client' */ import { DEV } from 'esm-env'; import { get, active_effect, active_reaction, set_active_reaction } from './runtime.js'; -import { component_context } from './context.js'; import { array_prototype, get_descriptor, @@ -9,24 +8,19 @@ import { is_array, object_prototype } from '../shared/utils.js'; -import { check_ownership, widen_ownership } from './dev/ownership.js'; import { state as source, set } from './reactivity/sources.js'; -import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js'; +import { STATE_SYMBOL } from './constants.js'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; import { get_stack } from './dev/tracing.js'; import { tracing_mode_flag } from '../flags/index.js'; -/** @type {ProxyMetadata | null} */ -var parent_metadata = null; - /** * @template T * @param {T} value - * @param {Source} [prev] dev mode only * @returns {T} */ -export function proxy(value, prev) { +export function proxy(value) { // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) { return value; @@ -55,16 +49,7 @@ export function proxy(value, prev) { set_active_reaction(reaction); /** @type {T} */ - var result; - - if (DEV) { - var previous_metadata = parent_metadata; - parent_metadata = metadata; - result = fn(); - parent_metadata = previous_metadata; - } else { - result = fn(); - } + var result = fn(); set_active_reaction(previous_reaction); return result; @@ -76,31 +61,6 @@ export function proxy(value, prev) { sources.set('length', source(/** @type {any[]} */ (value).length, stack)); } - /** @type {ProxyMetadata} */ - var metadata; - - if (DEV) { - metadata = { - parent: parent_metadata, - owners: null - }; - - if (prev) { - // Reuse owners from previous state; necessary because reassignment is not guaranteed to have correct component context. - // If no previous proxy exists we play it safe and assume ownerless state - // @ts-expect-error - const prev_owners = prev.v?.[STATE_SYMBOL_METADATA]?.owners; - metadata.owners = prev_owners ? new Set(prev_owners) : null; - } else { - metadata.owners = - parent_metadata === null - ? component_context !== null - ? new Set([component_context.function]) - : null - : new Set(); - } - } - return new Proxy(/** @type {any} */ (value), { defineProperty(_, prop, descriptor) { if ( @@ -160,10 +120,6 @@ export function proxy(value, prev) { }, get(target, prop, receiver) { - if (DEV && prop === STATE_SYMBOL_METADATA) { - return metadata; - } - if (prop === STATE_SYMBOL) { return value; } @@ -179,22 +135,6 @@ export function proxy(value, prev) { if (s !== undefined) { var v = get(s); - - // In case of something like `foo = bar.map(...)`, foo would have ownership - // of the array itself, while the individual items would have ownership - // of the component that created bar. That means if we later do `foo[0].baz = 42`, - // we could get a false-positive ownership violation, since the two proxies - // are not connected to each other via the parent metadata relationship. - // For this reason, we need to widen the ownership of the children - // upon access when we detect they are not connected. - if (DEV) { - /** @type {ProxyMetadata | undefined} */ - var prop_metadata = v?.[STATE_SYMBOL_METADATA]; - if (prop_metadata && prop_metadata?.parent !== metadata) { - widen_ownership(metadata, prop_metadata); - } - } - return v === UNINITIALIZED ? undefined : v; } @@ -225,10 +165,6 @@ export function proxy(value, prev) { }, has(target, prop) { - if (DEV && prop === STATE_SYMBOL_METADATA) { - return true; - } - if (prop === STATE_SYMBOL) { return true; } @@ -295,15 +231,6 @@ export function proxy(value, prev) { ); } - if (DEV) { - /** @type {ProxyMetadata | undefined} */ - var prop_metadata = value?.[STATE_SYMBOL_METADATA]; - if (prop_metadata && prop_metadata?.parent !== metadata) { - widen_ownership(metadata, prop_metadata); - } - check_ownership(metadata); - } - var descriptor = Reflect.getOwnPropertyDescriptor(target, prop); // Set the new value before updating any signals so that any listeners get the new value diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index ceab59b37c..2361762519 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -140,7 +140,7 @@ export function set(source, value, should_proxy = false) { e.state_unsafe_mutation(); } - let new_value = should_proxy ? proxy(value, source) : value; + let new_value = should_proxy ? proxy(value) : value; return internal_set(source, new_value); } diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 0c260a0a9f..b46bdf2013 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -179,14 +179,6 @@ export type TaskCallback = (now: number) => boolean | void; export type TaskEntry = { c: TaskCallback; f: () => void }; -/** Dev-only */ -export interface ProxyMetadata { - /** The components that 'own' this state, if any. `null` means no owners, i.e. everyone can mutate this state. */ - owners: null | Set; - /** The parent metadata object */ - parent: null | ProxyMetadata; -} - export type ProxyStateObject> = T & { [STATE_SYMBOL]: T; }; diff --git a/packages/svelte/src/internal/client/warnings.js b/packages/svelte/src/internal/client/warnings.js index 250c6eca2f..c84b487e28 100644 --- a/packages/svelte/src/internal/client/warnings.js +++ b/packages/svelte/src/internal/client/warnings.js @@ -129,27 +129,30 @@ export function lifecycle_double_unmount() { } /** - * %parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent% + * %parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`) * @param {string} parent + * @param {string} prop * @param {string} child * @param {string} owner */ -export function ownership_invalid_binding(parent, child, owner) { +export function ownership_invalid_binding(parent, prop, child, owner) { if (DEV) { - console.warn(`%c[svelte] ownership_invalid_binding\n%c${parent} passed a value to ${child} with \`bind:\`, but the value is owned by ${owner}. Consider creating a binding between ${owner} and ${parent}\nhttps://svelte.dev/e/ownership_invalid_binding`, bold, normal); + console.warn(`%c[svelte] ownership_invalid_binding\n%c${parent} passed property \`${prop}\` to ${child} with \`bind:\`, but its parent component ${owner} did not declare \`${prop}\` as a binding. Consider creating a binding between ${owner} and ${parent} (e.g. \`bind:${prop}={...}\` instead of \`${prop}={...}\`)\nhttps://svelte.dev/e/ownership_invalid_binding`, bold, normal); } else { console.warn(`https://svelte.dev/e/ownership_invalid_binding`); } } /** - * %component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead - * @param {string | undefined | null} [component] - * @param {string | undefined | null} [owner] + * Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead + * @param {string} name + * @param {string} location + * @param {string} prop + * @param {string} parent */ -export function ownership_invalid_mutation(component, owner) { +export function ownership_invalid_mutation(name, location, prop, parent) { if (DEV) { - console.warn(`%c[svelte] ownership_invalid_mutation\n%c${component ? `${component} mutated a value owned by ${owner}. This is strongly discouraged. Consider passing values to child components with \`bind:\`, or use a callback instead` : 'Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead'}\nhttps://svelte.dev/e/ownership_invalid_mutation`, bold, normal); + console.warn(`%c[svelte] ownership_invalid_mutation\n%cMutating unbound props (\`${name}\`, at ${location}) is strongly discouraged. Consider using \`bind:${prop}={...}\` in ${parent} (or using a callback) instead\nhttps://svelte.dev/e/ownership_invalid_mutation`, bold, normal); } else { console.warn(`https://svelte.dev/e/ownership_invalid_mutation`); } diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index d4d106d56d..ada318e85a 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -465,8 +465,10 @@ export function is_raw_text_element(name) { /** * Prevent devtools trying to make `location` a clickable link by inserting a zero-width space - * @param {string | undefined} location + * @template {string | undefined} T + * @param {T} location + * @returns {T}; */ export function sanitize_location(location) { - return location?.replace(/\//g, '/\u200b'); + return /** @type {T} */ (location?.replace(/\//g, '/\u200b')); } diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js index 62c6961242..8452661026 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js @@ -10,7 +10,7 @@ export default test({ test({ assert, target, warnings }) { const warning = - 'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead'; + 'Mutating unbound props (`object`, at Counter.svelte:5:23) is strongly discouraged. Consider using `bind:object={...}` in main.svelte (or using a callback) instead'; const [btn1, btn2] = target.querySelectorAll('button'); btn1.click(); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js deleted file mode 100644 index b4864154c3..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - async test({ assert, warnings }) { - assert.deepEqual(warnings, []); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte deleted file mode 100644 index 13de753647..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte deleted file mode 100644 index 8a6922e9e2..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js similarity index 74% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js index c07b9ce129..96b18d1854 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js @@ -8,7 +8,7 @@ let warn; let warnings = []; export default test({ - html: ``, + html: ``, compileOptions: { dev: true @@ -34,8 +34,8 @@ export default test({ btn?.click(); }); - assert.htmlEqual(target.innerHTML, ``); + assert.htmlEqual(target.innerHTML, ``); - assert.deepEqual(warnings, []); + assert.deepEqual(warnings, [], 'expected getContext to have widened ownership'); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte new file mode 100644 index 0000000000..2dd7cab141 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte @@ -0,0 +1,9 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/sub.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/sub.svelte rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/sub.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js index c07b9ce129..66f1726a2a 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js @@ -1,41 +1,24 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; -/** @type {typeof console.warn} */ -let warn; - -/** @type {any[]} */ -let warnings = []; - export default test({ - html: ``, - compileOptions: { dev: true }, - before_test: () => { - warn = console.warn; - - console.warn = (...args) => { - warnings.push(...args); - }; - }, + test({ assert, target, warnings }) { + const [btn1, btn2] = target.querySelectorAll('button'); - after_test: () => { - console.warn = warn; - warnings = []; - }, + flushSync(() => { + btn1.click(); + }); - test({ assert, target }) { - const btn = target.querySelector('button'); + assert.deepEqual(warnings.length, 0); flushSync(() => { - btn?.click(); + btn2.click(); }); - assert.htmlEqual(target.innerHTML, ``); - - assert.deepEqual(warnings, []); + assert.deepEqual(warnings.length, 1); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte index ad450a937e..0be7e434e4 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte @@ -1,9 +1,8 @@ - - + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js index 3e7a68cf97..2906b9bce5 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js @@ -1 +1,14 @@ -export let global = $state({}); +export function create_my_state() { + const my_state = $state({ + a: 0 + }); + + function inc() { + my_state.a++; + } + + return { + my_state, + inc + }; +} diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/sub.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/sub.svelte rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/sub.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/Child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/Child.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/Child.svelte rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/Child.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js index 96b18d1854..ab7327ab8b 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js @@ -1,41 +1,24 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; -/** @type {typeof console.warn} */ -let warn; - -/** @type {any[]} */ -let warnings = []; - export default test({ - html: ``, - compileOptions: { dev: true }, - before_test: () => { - warn = console.warn; - - console.warn = (...args) => { - warnings.push(...args); - }; - }, + async test({ assert, target, warnings }) { + const [btn1, btn2] = target.querySelectorAll('button'); - after_test: () => { - console.warn = warn; - warnings = []; - }, + flushSync(() => { + btn1.click(); + }); - test({ assert, target }) { - const btn = target.querySelector('button'); + assert.deepEqual(warnings.length, 0); flushSync(() => { - btn?.click(); + btn2.click(); }); - assert.htmlEqual(target.innerHTML, ``); - - assert.deepEqual(warnings, [], 'expected getContext to have widened ownership'); + assert.deepEqual(warnings.length, 1); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte index 2dd7cab141..8e8343790b 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte @@ -1,9 +1,13 @@ - + + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js deleted file mode 100644 index aeb3740dfe..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js +++ /dev/null @@ -1,37 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -/** @type {typeof console.warn} */ -let warn; - -/** @type {any[]} */ -let warnings = []; - -export default test({ - compileOptions: { - dev: true - }, - - before_test: () => { - warn = console.warn; - - console.warn = (...args) => { - warnings.push(...args); - }; - }, - - after_test: () => { - console.warn = warn; - warnings = []; - }, - - test({ assert, target }) { - const btn = target.querySelector('button'); - - flushSync(() => { - btn?.click(); - }); - - assert.deepEqual(warnings, []); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte deleted file mode 100644 index 2d40c13949..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js deleted file mode 100644 index 4079059171..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js +++ /dev/null @@ -1,13 +0,0 @@ -class Global { - state = $state({}); - - add_a(a) { - this.state.a = a; - } - - increment_a_b() { - this.state.a.b++; - } -} - -export const global = new Global(); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte deleted file mode 100644 index 044904aa18..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js deleted file mode 100644 index 66f1726a2a..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js +++ /dev/null @@ -1,24 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - test({ assert, target, warnings }) { - const [btn1, btn2] = target.querySelectorAll('button'); - - flushSync(() => { - btn1.click(); - }); - - assert.deepEqual(warnings.length, 0); - - flushSync(() => { - btn2.click(); - }); - - assert.deepEqual(warnings.length, 1); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte deleted file mode 100644 index 0be7e434e4..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js deleted file mode 100644 index 2906b9bce5..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js +++ /dev/null @@ -1,14 +0,0 @@ -export function create_my_state() { - const my_state = $state({ - a: 0 - }); - - function inc() { - my_state.a++; - } - - return { - my_state, - inc - }; -} diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte deleted file mode 100644 index aa31fd7606..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js deleted file mode 100644 index cc9ea715f0..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js +++ /dev/null @@ -1,37 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -/** @type {typeof console.warn} */ -let warn; - -/** @type {any[]} */ -let warnings = []; - -export default test({ - compileOptions: { - dev: true - }, - - before_test: () => { - warn = console.warn; - - console.warn = (...args) => { - warnings.push(...args); - }; - }, - - after_test: () => { - console.warn = warn; - warnings = []; - }, - - async test({ assert, target }) { - const btn = target.querySelector('button'); - - flushSync(() => { - btn?.click(); - }); - - assert.deepEqual(warnings.length, 0); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte deleted file mode 100644 index 92d7dbd2db..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js deleted file mode 100644 index ab7327ab8b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js +++ /dev/null @@ -1,24 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - async test({ assert, target, warnings }) { - const [btn1, btn2] = target.querySelectorAll('button'); - - flushSync(() => { - btn1.click(); - }); - - assert.deepEqual(warnings.length, 0); - - flushSync(() => { - btn2.click(); - }); - - assert.deepEqual(warnings.length, 1); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte deleted file mode 100644 index 8e8343790b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte deleted file mode 100644 index ffe6ef75c4..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte deleted file mode 100644 index 5f1c7461f6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js deleted file mode 100644 index 6881c2faf6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js +++ /dev/null @@ -1,3 +0,0 @@ -export let global = $state({ - object: { count: -1 } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js index 87474a05cc..39fa80c55a 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js @@ -8,6 +8,6 @@ export default test({ }, warnings: [ - 'Intermediate.svelte passed a value to Counter.svelte with `bind:`, but the value is owned by main.svelte. Consider creating a binding between main.svelte and Intermediate.svelte' + 'Intermediate.svelte passed property `object` to Counter.svelte with `bind:`, but its parent component main.svelte did not declare `object` as a binding. Consider creating a binding between main.svelte and Intermediate.svelte (e.g. `bind:object={...}` instead of `object={...}`)' ] }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js index 66e5184380..7b8cc676d5 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js @@ -33,7 +33,7 @@ export default test({ assert.htmlEqual(target.innerHTML, ``); assert.deepEqual(warnings, [ - 'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead' + 'Mutating unbound props (`notshared`, at Counter.svelte:10:23) is strongly discouraged. Consider using `bind:notshared={...}` in main.svelte (or using a callback) instead' ]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js index e766a946d0..bd2ecc28b6 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js @@ -1,7 +1,6 @@ import { flushSync } from 'svelte'; import { ok, test } from '../../test'; -// Tests that proxies widen ownership correctly even if not directly connected to each other export default test({ compileOptions: { dev: true diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte deleted file mode 100644 index d6da559fb1..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -

Binding

- - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte deleted file mode 100644 index b935f0a472..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - -

Context

- - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js deleted file mode 100644 index d6d12d01cd..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js +++ /dev/null @@ -1,34 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -// Tests that ownership is widened with $derived (on class or on its own) that contains $state -export default test({ - compileOptions: { - dev: true - }, - - test({ assert, target, warnings }) { - const [root, counter_context1, counter_context2, counter_binding1, counter_binding2] = - target.querySelectorAll('button'); - - counter_context1.click(); - counter_context2.click(); - counter_binding1.click(); - counter_binding2.click(); - flushSync(); - - assert.equal(warnings.length, 0); - - root.click(); - flushSync(); - counter_context1.click(); - counter_context2.click(); - counter_binding1.click(); - counter_binding2.click(); - flushSync(); - - assert.equal(warnings.length, 0); - }, - - warnings: [] -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte deleted file mode 100644 index aaade26e16..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - -

Parent

- - - - From 7694818f9cf70cf802058c2a49571aaa69956d8a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:09:22 -0400 Subject: [PATCH 09/14] Version Packages (#15705) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/clever-news-enjoy.md | 5 ----- .changeset/sweet-ants-care.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-news-enjoy.md delete mode 100644 .changeset/sweet-ants-care.md diff --git a/.changeset/clever-news-enjoy.md b/.changeset/clever-news-enjoy.md deleted file mode 100644 index 2ff3dcbe56..0000000000 --- a/.changeset/clever-news-enjoy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: allow `$.state` and `$.derived` to be treeshaken diff --git a/.changeset/sweet-ants-care.md b/.changeset/sweet-ants-care.md deleted file mode 100644 index b4805626ab..0000000000 --- a/.changeset/sweet-ants-care.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: rework binding ownership validation diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 361de202b1..eb76e9a9e9 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.25.9 + +### Patch Changes + +- fix: allow `$.state` and `$.derived` to be treeshaken ([#15702](https://github.com/sveltejs/svelte/pull/15702)) + +- fix: rework binding ownership validation ([#15678](https://github.com/sveltejs/svelte/pull/15678)) + ## 5.25.8 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 8a225a798d..3fb843b3a2 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.25.8", + "version": "5.25.9", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 2c8140e365..13a69857a7 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.25.8'; +export const VERSION = '5.25.9'; export const PUBLIC_VERSION = '5'; From 708f541ad8ed2e9426cedb03e0bc1ad5d84cb787 Mon Sep 17 00:00:00 2001 From: 7nik Date: Wed, 9 Apr 2025 00:39:11 +0300 Subject: [PATCH 10/14] fix: better scope `:global()` with nesting selector `&` (#15671) Co-authored-by: 7nik --- .changeset/stupid-vans-draw.md | 5 +++++ .../phases/2-analyze/css/css-prune.js | 20 +++++++++++++++---- .../samples/global-with-nesting/expected.css | 7 ++++++- .../samples/global-with-nesting/input.svelte | 7 ++++++- 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 .changeset/stupid-vans-draw.md diff --git a/.changeset/stupid-vans-draw.md b/.changeset/stupid-vans-draw.md new file mode 100644 index 0000000000..24892f1e8f --- /dev/null +++ b/.changeset/stupid-vans-draw.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: better scope `:global()` with nesting selector `&` diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index 0646c6341a..fbe6ca1cd3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -1,6 +1,11 @@ /** @import * as Compiler from '#compiler' */ import { walk } from 'zimmerframe'; -import { get_parent_rules, get_possible_values, is_outer_global } from './utils.js'; +import { + get_parent_rules, + get_possible_values, + is_outer_global, + is_unscoped_pseudo_class +} from './utils.js'; import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../patterns.js'; import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js'; @@ -286,20 +291,26 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi * a global selector * @param {Compiler.AST.CSS.RelativeSelector} selector * @param {Compiler.AST.CSS.Rule} rule + * @returns {boolean} */ function is_global(selector, rule) { if (selector.metadata.is_global || selector.metadata.is_global_like) { return true; } + let explicitly_global = false; + for (const s of selector.selectors) { /** @type {Compiler.AST.CSS.SelectorList | null} */ let selector_list = null; + let can_be_global = false; let owner = rule; if (s.type === 'PseudoClassSelector') { if ((s.name === 'is' || s.name === 'where') && s.args) { selector_list = s.args; + } else { + can_be_global = is_unscoped_pseudo_class(s); } } @@ -308,18 +319,19 @@ function is_global(selector, rule) { selector_list = owner.prelude; } - const has_global_selectors = selector_list?.children.some((complex_selector) => { + const has_global_selectors = !!selector_list?.children.some((complex_selector) => { return complex_selector.children.every((relative_selector) => is_global(relative_selector, owner) ); }); + explicitly_global ||= has_global_selectors; - if (!has_global_selectors) { + if (!has_global_selectors && !can_be_global) { return false; } } - return true; + return explicitly_global || selector.selectors.length === 0; } const regex_backslash_and_following_character = /\\(.)/g; diff --git a/packages/svelte/tests/css/samples/global-with-nesting/expected.css b/packages/svelte/tests/css/samples/global-with-nesting/expected.css index dcb8a0e481..1863c57d85 100644 --- a/packages/svelte/tests/css/samples/global-with-nesting/expected.css +++ b/packages/svelte/tests/css/samples/global-with-nesting/expected.css @@ -1,5 +1,10 @@ div.svelte-xyz { &.class{ - color: red; + color: green; + } + } + * { + &:hover .class.svelte-xyz { + color: green; } } \ No newline at end of file diff --git a/packages/svelte/tests/css/samples/global-with-nesting/input.svelte b/packages/svelte/tests/css/samples/global-with-nesting/input.svelte index 0c73ed7a78..2c1d2b5ebd 100644 --- a/packages/svelte/tests/css/samples/global-with-nesting/input.svelte +++ b/packages/svelte/tests/css/samples/global-with-nesting/input.svelte @@ -1,7 +1,12 @@ From 966ccfbe7451b7c00fc63ee43ea65bd7f286b5cf Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 8 Apr 2025 23:41:53 +0200 Subject: [PATCH 11/14] fix: set deriveds as `CLEAN` if they are assigned to (#15592) * fix: set deriveds as `CLEAN` if they are assigned to * chore: remove only * chore: remove unnecessary typecast * fix: set unowned as `MAYBE_DIRTY` instead of `CLEAN` * fix: visit the derived function when to update the dependencies even when it's reassigned * fix: use `execute_derived` instead of `update_reaction` * fix: execute deriveds eagerly when they are set if DIRTY --- .changeset/nervous-kids-shake.md | 5 +++ .../internal/client/reactivity/deriveds.js | 2 +- .../src/internal/client/reactivity/sources.js | 12 +++++-- .../svelte/src/internal/client/runtime.js | 2 +- packages/svelte/tests/signals/test.ts | 32 +++++++++++++++++++ 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 .changeset/nervous-kids-shake.md diff --git a/.changeset/nervous-kids-shake.md b/.changeset/nervous-kids-shake.md new file mode 100644 index 0000000000..3fc6429797 --- /dev/null +++ b/.changeset/nervous-kids-shake.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: set deriveds as `CLEAN` if they are assigned to diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 86171c2b2d..c9a8f7674a 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -131,7 +131,7 @@ function get_derived_parent_effect(derived) { * @param {Derived} derived * @returns {T} */ -function execute_derived(derived) { +export function execute_derived(derived) { var value; var prev_active_effect = active_effect; diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 2361762519..cae49c1832 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -28,14 +28,14 @@ import { UNOWNED, MAYBE_DIRTY, BLOCK_EFFECT, - ROOT_EFFECT, - EFFECT_IS_UPDATING + ROOT_EFFECT } from '../constants.js'; import * as e from '../errors.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; import { get_stack } from '../dev/tracing.js'; import { component_context, is_runes } from '../context.js'; import { proxy } from '../proxy.js'; +import { execute_derived } from './deriveds.js'; export let inspect_effects = new Set(); export const old_values = new Map(); @@ -172,6 +172,14 @@ export function internal_set(source, value) { } } + if ((source.f & DERIVED) !== 0) { + // if we are assigning to a dirty derived we set it to clean/maybe dirty but we also eagerly execute it to track the dependencies + if ((source.f & DIRTY) !== 0) { + execute_derived(/** @type {Derived} */ (source)); + } + set_signal_status(source, (source.f & UNOWNED) === 0 ? CLEAN : MAYBE_DIRTY); + } + mark_reactions(source, DIRTY); // It's possible that the current reaction might not have up-to-date dependencies diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index c1c91f3551..a7662be617 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -27,7 +27,7 @@ import { } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { internal_set, old_values } from './reactivity/sources.js'; -import { destroy_derived_effects, update_derived } from './reactivity/deriveds.js'; +import { destroy_derived_effects, execute_derived, update_derived } from './reactivity/deriveds.js'; import * as e from './errors.js'; import { FILENAME } from '../../constants.js'; import { tracing_mode_flag } from '../flags/index.js'; diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 3977caae36..3a427e9392 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1080,6 +1080,38 @@ describe('signals', () => { }; }); + test("deriveds set after they are DIRTY doesn't get updated on get", () => { + return () => { + const a = state(0); + const b = derived(() => $.get(a)); + + set(b, 1); + assert.equal($.get(b), 1); + + set(a, 2); + assert.equal($.get(b), 2); + set(b, 3); + + assert.equal($.get(b), 3); + }; + }); + + test("unowned deriveds set after they are DIRTY doesn't get updated on get", () => { + return () => { + const a = state(0); + const b = derived(() => $.get(a)); + const c = derived(() => $.get(b)); + + set(b, 1); + assert.equal($.get(c), 1); + + set(a, 2); + + assert.equal($.get(b), 2); + assert.equal($.get(c), 2); + }; + }); + test('deriveds containing effects work correctly when used with untrack', () => { return () => { let a = render_effect(() => {}); From e9a16c4b4209d104e0bef7cf28df51f830de1254 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 8 Apr 2025 18:53:18 -0400 Subject: [PATCH 12/14] chore: revert corepack override (#15197) * Revert "try this (#15196)" This reverts commit f878736f3825e1832fa7344306358e877e20bd7f. * upgrade node * upgrade pnpm --------- Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- .github/workflows/pkg.pr.new.yml | 5 +---- package.json | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml index 90d219faae..b2b521dc6f 100644 --- a/.github/workflows/pkg.pr.new.yml +++ b/.github/workflows/pkg.pr.new.yml @@ -11,13 +11,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: install corepack - run: npm i -g corepack@0.31.0 - - run: corepack enable - uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x cache: pnpm - name: Install dependencies diff --git a/package.json b/package.json index ad69bfc9ca..70e85438f0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "private": true, "type": "module", "license": "MIT", - "packageManager": "pnpm@9.4.0", + "packageManager": "pnpm@10.4.0", "engines": { "pnpm": ">=9.0.0" }, From 0ca1f4a37ec008092fc1798e374c6108308addef Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Tue, 8 Apr 2025 21:26:54 -0400 Subject: [PATCH 13/14] docs: raise importance of global vs local transitions (#15479) * Doc: Raise importance of global vs local transitions * switch order --------- Co-authored-by: Rich Harris --- documentation/docs/03-template-syntax/13-transition.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/docs/03-template-syntax/13-transition.md b/documentation/docs/03-template-syntax/13-transition.md index 51c11e8b34..c51175c272 100644 --- a/documentation/docs/03-template-syntax/13-transition.md +++ b/documentation/docs/03-template-syntax/13-transition.md @@ -22,10 +22,6 @@ The `transition:` directive indicates a _bidirectional_ transition, which means {/if} ``` -## Built-in transitions - -A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module. - ## Local vs global Transitions are local by default. Local transitions only play when the block they belong to is created or destroyed, _not_ when parent blocks are created or destroyed. @@ -40,6 +36,10 @@ Transitions are local by default. Local transitions only play when the block the {/if} ``` +## Built-in transitions + +A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module. + ## Transition parameters Transitions can have parameters. From 0ff3d7452092511a55a63a7639a8e8798fea9fed Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Tue, 8 Apr 2025 19:40:05 -0700 Subject: [PATCH 14/14] docs: update `$effect` examples (#15463) * docs: update effect examples * revert * Update documentation/docs/02-runes/04-$effect.md * update example * revert * update effect root example --------- Co-authored-by: Rich Harris --- documentation/docs/02-runes/04-$effect.md | 55 ++++++++++++++--------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md index ae1a2146c9..46ea9b81e9 100644 --- a/documentation/docs/02-runes/04-$effect.md +++ b/documentation/docs/02-runes/04-$effect.md @@ -25,7 +25,7 @@ You can create an effect with the `$effect` rune ([demo](/playground/untitled#H4 }); - + ``` When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes. @@ -135,19 +135,33 @@ An effect only reruns when the object it reads changes, not when a property insi An effect only depends on the values that it read the last time it ran. This has interesting implications for effects that have conditional code. -For instance, if `a` is `true` in the code snippet below, the code inside the `if` block will run and `b` will be evaluated. As such, changes to either `a` or `b` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE3VQzWrDMAx-FdUU4kBp71li6EPstOxge0ox8-QQK2PD-N1nLy2F0Z2Evj9_chKkP1B04pnYscc3cRCT8xhF95IEf8-Vq0DBr8rzPB_jJ3qumNERH-E2ECNxiRF9tIubWY00lgcYNAywj6wZJS8rtk83wjwgCrXHaULLUrYwKEgVGrnkx-Dx6MNFNstK5OjSbFGbwE0gdXuT_zGYrjmAuco515Hr1p_uXak3K3MgCGS9s-9D2grU-judlQYXIencnzad-tdR79qZrMyvw9wd5Z8Yv1h09dz8mn8AkM7Pfo0BAAA=). +For instance, if `condition` is `true` in the code snippet below, the code inside the `if` block will run and `color` will be evaluated. As such, changes to either `condition` or `color` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE21RQW6DMBD8ytaNBJHaJFLViwNIVZ8RcnBgXVk1xsILTYT4e20TQg89IOPZ2fHM7siMaJBx9tmaWpFqjQNlAKXEihx7YVJpdIyfRkY3G4gB8Pi97cPanRtQU8AuwuF_eNUaQuPlOMtc1SlLRWlKUo1tOwJflUikQHZtA0klzCDc64Imx0ANn8bInV1CDhtHgjClrsftcSXotluLybOUb3g4JJHhOZs5WZpuIS9gjNqkJKQP5e2ClrR4SMdZ13E4xZ8zTPOTJU2A2uE_PQ9COCI926_hTVarIU4hu_REPlBrKq2q73ycrf1N-vS4TMUsulaVg3EtR8H9rFgsg8uUsT1B2F9eshigZHBRpuaD0D3mY8Qm2BfB5N2YyRzdNEYVDy0Ja-WsFjcOUuP1HvFLWA6H3XuHTUSmmDV2--0TXonxsKbp7G9C6R__NONS-MFNvxj_d6mBAgAA). -Conversely, if `a` is `false`, `b` will not be evaluated, and the effect will _only_ re-run when `a` changes. +Conversely, if `condition` is `false`, `color` will not be evaluated, and the effect will _only_ re-run again when `condition` changes. ```ts -let a = false; -let b = false; +// @filename: ambient.d.ts +declare module 'canvas-confetti' { + interface ConfettiOptions { + colors: string[]; + } + + function confetti(opts?: ConfettiOptions): void; + export default confetti; +} + +// @filename: index.js // ---cut--- -$effect(() => { - console.log('running'); +import confetti from 'canvas-confetti'; - if (a) { - console.log('b:', b); +let condition = $state(true); +let color = $state('#ff3e00'); + +$effect(() => { + if (condition) { + confetti({ colors: [color] }); + } else { + confetti(); } }); ``` @@ -211,20 +225,19 @@ It is used to implement abstractions like [`createSubscriber`](/docs/svelte/svel The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for nested effects that you want to manually control. This rune also allows for the creation of effects outside of the component initialisation phase. -```svelte - +// later... +destroy(); ``` ## When not to use `$effect`