From 8ea3ee8c62aed631d5c4bbc0870d491059ed7d03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:52:21 -0400 Subject: [PATCH 01/16] chore(deps-dev): bump esbuild from 0.25.11 to 0.28.1 (#18427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [esbuild](https://github.com/evanw/esbuild) from 0.25.11 to 0.28.1.
Release notes

Sourced from esbuild's releases.

v0.28.1

... (truncated)

Changelog

Sourced from esbuild's changelog.

Changelog: 2025

This changelog documents all esbuild versions published in the year 2025 (versions 0.25.0 through 0.27.2).

0.27.2

... (truncated)

Commits
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for esbuild since your current version.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=esbuild&package-manager=npm_and_yarn&previous-version=0.25.11&new-version=0.28.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/sveltejs/svelte/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/svelte/package.json | 2 +- pnpm-lock.yaml | 342 ++++++++++++++++++----------------- 2 files changed, 178 insertions(+), 166 deletions(-) diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 6bca1bd4a8..eeca8e032e 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -161,7 +161,7 @@ "@types/node": "^20.11.5", "baseline-browser-mapping": "^2.10.32", "dts-buddy": "^0.5.5", - "esbuild": "^0.25.10", + "esbuild": "^0.28.1", "rollup": "^4.59.0", "source-map": "^0.7.4", "tinyglobby": "^0.2.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a619c24ad6..e86c927efe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,8 +148,8 @@ importers: specifier: ^0.5.5 version: 0.5.5(typescript@5.5.4) esbuild: - specifier: ^0.25.10 - version: 0.25.11 + specifier: ^0.28.1 + version: 0.28.1 rollup: specifier: ^4.59.0 version: 4.60.1 @@ -285,23 +285,17 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@esbuild/aix-ppc64@0.25.11': - resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.11': - resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [android] + cpu: [ppc64] + os: [aix] '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} @@ -309,10 +303,10 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.11': - resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} - cpu: [arm] + cpu: [arm64] os: [android] '@esbuild/android-arm@0.27.7': @@ -321,10 +315,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.11': - resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm] os: [android] '@esbuild/android-x64@0.27.7': @@ -333,11 +327,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.11': - resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] + cpu: [x64] + os: [android] '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} @@ -345,10 +339,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.11': - resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.27.7': @@ -357,11 +351,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.11': - resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] + cpu: [x64] + os: [darwin] '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} @@ -369,10 +363,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.11': - resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.27.7': @@ -381,11 +375,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.11': - resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [linux] + cpu: [x64] + os: [freebsd] '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} @@ -393,10 +387,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.11': - resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} - cpu: [arm] + cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.27.7': @@ -405,10 +399,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.11': - resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.27.7': @@ -417,10 +411,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.11': - resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} - cpu: [loong64] + cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.27.7': @@ -429,10 +423,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.11': - resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} - cpu: [mips64el] + cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.27.7': @@ -441,10 +435,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.11': - resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} - cpu: [ppc64] + cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.27.7': @@ -453,10 +447,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.11': - resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} - cpu: [riscv64] + cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.27.7': @@ -465,10 +459,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.11': - resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} - cpu: [s390x] + cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.27.7': @@ -477,10 +471,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.11': - resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} - cpu: [x64] + cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.27.7': @@ -489,11 +483,11 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.11': - resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] + cpu: [x64] + os: [linux] '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} @@ -501,10 +495,10 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.11': - resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.27.7': @@ -513,11 +507,11 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.11': - resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] + cpu: [x64] + os: [netbsd] '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} @@ -525,10 +519,10 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.11': - resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.27.7': @@ -537,11 +531,11 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.11': - resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] + cpu: [x64] + os: [openbsd] '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} @@ -549,11 +543,11 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.11': - resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} - cpu: [x64] - os: [sunos] + cpu: [arm64] + os: [openharmony] '@esbuild/sunos-x64@0.27.7': resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} @@ -561,11 +555,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.11': - resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [win32] + cpu: [x64] + os: [sunos] '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} @@ -573,10 +567,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.11': - resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.27.7': @@ -585,10 +579,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.11': - resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} - cpu: [x64] + cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.27.7': @@ -597,6 +591,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -936,6 +936,7 @@ packages: '@svitejs/changesets-changelog-github-compact@1.1.0': resolution: {integrity: sha512-qhUGGDHcpbY2zpjW3SwqchuW8J/5EzlPFud7xNntHKA7f3a/mx5+g+ruJKFHSAiVZYo30PALt+AyhmPUNKH/Og==} engines: {node: ^14.13.1 || ^16.0.0 || >=18} + deprecated: unmaintained '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1090,6 +1091,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} @@ -1287,8 +1293,8 @@ packages: peerDependencies: typescript: '>=5.0.4 <5.8' - enhanced-resolve@5.22.1: - resolution: {integrity: sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==} + enhanced-resolve@5.24.0: + resolution: {integrity: sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==} engines: {node: '>=10.13.0'} enquirer@2.4.1: @@ -1305,13 +1311,13 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - esbuild@0.25.11: - resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} hasBin: true - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true @@ -2095,8 +2101,8 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.8.1: - resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} engines: {node: '>=10'} hasBin: true @@ -2663,162 +2669,162 @@ snapshots: human-id: 4.1.1 prettier: 2.8.8 - '@esbuild/aix-ppc64@0.25.11': - optional: true - '@esbuild/aix-ppc64@0.27.7': optional: true - '@esbuild/android-arm64@0.25.11': + '@esbuild/aix-ppc64@0.28.1': optional: true '@esbuild/android-arm64@0.27.7': optional: true - '@esbuild/android-arm@0.25.11': + '@esbuild/android-arm64@0.28.1': optional: true '@esbuild/android-arm@0.27.7': optional: true - '@esbuild/android-x64@0.25.11': + '@esbuild/android-arm@0.28.1': optional: true '@esbuild/android-x64@0.27.7': optional: true - '@esbuild/darwin-arm64@0.25.11': + '@esbuild/android-x64@0.28.1': optional: true '@esbuild/darwin-arm64@0.27.7': optional: true - '@esbuild/darwin-x64@0.25.11': + '@esbuild/darwin-arm64@0.28.1': optional: true '@esbuild/darwin-x64@0.27.7': optional: true - '@esbuild/freebsd-arm64@0.25.11': + '@esbuild/darwin-x64@0.28.1': optional: true '@esbuild/freebsd-arm64@0.27.7': optional: true - '@esbuild/freebsd-x64@0.25.11': + '@esbuild/freebsd-arm64@0.28.1': optional: true '@esbuild/freebsd-x64@0.27.7': optional: true - '@esbuild/linux-arm64@0.25.11': + '@esbuild/freebsd-x64@0.28.1': optional: true '@esbuild/linux-arm64@0.27.7': optional: true - '@esbuild/linux-arm@0.25.11': + '@esbuild/linux-arm64@0.28.1': optional: true '@esbuild/linux-arm@0.27.7': optional: true - '@esbuild/linux-ia32@0.25.11': + '@esbuild/linux-arm@0.28.1': optional: true '@esbuild/linux-ia32@0.27.7': optional: true - '@esbuild/linux-loong64@0.25.11': + '@esbuild/linux-ia32@0.28.1': optional: true '@esbuild/linux-loong64@0.27.7': optional: true - '@esbuild/linux-mips64el@0.25.11': + '@esbuild/linux-loong64@0.28.1': optional: true '@esbuild/linux-mips64el@0.27.7': optional: true - '@esbuild/linux-ppc64@0.25.11': + '@esbuild/linux-mips64el@0.28.1': optional: true '@esbuild/linux-ppc64@0.27.7': optional: true - '@esbuild/linux-riscv64@0.25.11': + '@esbuild/linux-ppc64@0.28.1': optional: true '@esbuild/linux-riscv64@0.27.7': optional: true - '@esbuild/linux-s390x@0.25.11': + '@esbuild/linux-riscv64@0.28.1': optional: true '@esbuild/linux-s390x@0.27.7': optional: true - '@esbuild/linux-x64@0.25.11': + '@esbuild/linux-s390x@0.28.1': optional: true '@esbuild/linux-x64@0.27.7': optional: true - '@esbuild/netbsd-arm64@0.25.11': + '@esbuild/linux-x64@0.28.1': optional: true '@esbuild/netbsd-arm64@0.27.7': optional: true - '@esbuild/netbsd-x64@0.25.11': + '@esbuild/netbsd-arm64@0.28.1': optional: true '@esbuild/netbsd-x64@0.27.7': optional: true - '@esbuild/openbsd-arm64@0.25.11': + '@esbuild/netbsd-x64@0.28.1': optional: true '@esbuild/openbsd-arm64@0.27.7': optional: true - '@esbuild/openbsd-x64@0.25.11': + '@esbuild/openbsd-arm64@0.28.1': optional: true '@esbuild/openbsd-x64@0.27.7': optional: true - '@esbuild/openharmony-arm64@0.25.11': + '@esbuild/openbsd-x64@0.28.1': optional: true '@esbuild/openharmony-arm64@0.27.7': optional: true - '@esbuild/sunos-x64@0.25.11': + '@esbuild/openharmony-arm64@0.28.1': optional: true '@esbuild/sunos-x64@0.27.7': optional: true - '@esbuild/win32-arm64@0.25.11': + '@esbuild/sunos-x64@0.28.1': optional: true '@esbuild/win32-arm64@0.27.7': optional: true - '@esbuild/win32-ia32@0.25.11': + '@esbuild/win32-arm64@0.28.1': optional: true '@esbuild/win32-ia32@0.27.7': optional: true - '@esbuild/win32-x64@0.25.11': + '@esbuild/win32-ia32@0.28.1': optional: true '@esbuild/win32-x64@0.27.7': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.0)': dependencies: eslint: 10.0.0 @@ -3062,7 +3068,7 @@ snapshots: '@stylistic/eslint-plugin-js@1.8.0(eslint@10.0.0)': dependencies: '@types/eslint': 8.56.12 - acorn: 8.16.0 + acorn: 8.17.0 escape-string-regexp: 4.0.0 eslint: 10.0.0 eslint-visitor-keys: 3.4.3 @@ -3303,8 +3309,14 @@ snapshots: dependencies: acorn: 8.16.0 + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + acorn@8.16.0: {} + acorn@8.17.0: {} + agent-base@7.1.1: dependencies: debug: 4.4.3 @@ -3475,7 +3487,7 @@ snapshots: ts-api-utils: 1.3.0(typescript@5.5.4) typescript: 5.5.4 - enhanced-resolve@5.22.1: + enhanced-resolve@5.24.0: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -3491,35 +3503,6 @@ snapshots: es-module-lexer@2.1.0: {} - esbuild@0.25.11: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.11 - '@esbuild/android-arm': 0.25.11 - '@esbuild/android-arm64': 0.25.11 - '@esbuild/android-x64': 0.25.11 - '@esbuild/darwin-arm64': 0.25.11 - '@esbuild/darwin-x64': 0.25.11 - '@esbuild/freebsd-arm64': 0.25.11 - '@esbuild/freebsd-x64': 0.25.11 - '@esbuild/linux-arm': 0.25.11 - '@esbuild/linux-arm64': 0.25.11 - '@esbuild/linux-ia32': 0.25.11 - '@esbuild/linux-loong64': 0.25.11 - '@esbuild/linux-mips64el': 0.25.11 - '@esbuild/linux-ppc64': 0.25.11 - '@esbuild/linux-riscv64': 0.25.11 - '@esbuild/linux-s390x': 0.25.11 - '@esbuild/linux-x64': 0.25.11 - '@esbuild/netbsd-arm64': 0.25.11 - '@esbuild/netbsd-x64': 0.25.11 - '@esbuild/openbsd-arm64': 0.25.11 - '@esbuild/openbsd-x64': 0.25.11 - '@esbuild/openharmony-arm64': 0.25.11 - '@esbuild/sunos-x64': 0.25.11 - '@esbuild/win32-arm64': 0.25.11 - '@esbuild/win32-ia32': 0.25.11 - '@esbuild/win32-x64': 0.25.11 - esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -3549,12 +3532,41 @@ snapshots: '@esbuild/win32-ia32': 0.27.7 '@esbuild/win32-x64': 0.27.7 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escape-string-regexp@4.0.0: {} eslint-compat-utils@0.5.1(eslint@10.0.0): dependencies: eslint: 10.0.0 - semver: 7.8.1 + semver: 7.8.4 eslint-config-prettier@9.1.0(eslint@10.0.0): dependencies: @@ -3574,14 +3586,14 @@ snapshots: eslint-plugin-n@17.24.0(eslint@10.0.0)(typescript@5.5.4): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0) - enhanced-resolve: 5.22.1 + enhanced-resolve: 5.24.0 eslint: 10.0.0 eslint-plugin-es-x: 7.8.0(eslint@10.0.0) get-tsconfig: 4.14.0 globals: 15.15.0 globrex: 0.1.2 ignore: 5.3.2 - semver: 7.8.1 + semver: 7.8.4 ts-declaration-location: 1.0.7(typescript@5.5.4) transitivePeerDependencies: - typescript @@ -3673,8 +3685,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -4312,7 +4324,7 @@ snapshots: semver@7.7.4: {} - semver@7.8.1: {} + semver@7.8.4: {} serialize-javascript@6.0.2: dependencies: From 0510174bc0c908c9d4980274717191fdf46a38c6 Mon Sep 17 00:00:00 2001 From: Nic Polumeyv Date: Mon, 15 Jun 2026 12:08:45 -0400 Subject: [PATCH 02/16] fix: make `SvelteURLSearchParams` notifications accurate (#18425) `SvelteURLSearchParams` has two notification bugs that surface through `SvelteURL`. The first is over-notification. Setting `url.href` always rebuilds the params and bumps their version signal, even when the search string didn't change at all. Effects that read `size` (or any params method) re-run on every unrelated `href` write, which is #17218, where it caused infinite effect loops in a router. The fix is a guard in the internal replace path that bails out when the incoming params serialize identically to the current ones. The second is under-notification, and it's sneakier. Every read method tracks the version signal, `get`, `getAll`, `has`, `keys`, `values`, `entries`, `toString`, `size`, the iterator... except `forEach`, which was never overridden. The inherited platform method reads the internal list directly and subscribes to nothing, so a template that renders params via `forEach` never updates: ```js $effect(() => { url.searchParams.forEach((value, key) => entries.push(`${key}=${value}`)); // never re-runs }); ``` We found this one in the wild while making `page.url` a `SvelteURL` in sveltejs/kit#16031. Kit's own test suite renders query params with `forEach`, and the coarse object-identity signal that previously masked the gap went away, leaving stale UI after client-side navigations. The fix is the same one-line tracking pattern every other read method already uses. Fixes #17218 --- - [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [x] This message body should clearly illustrate what problems it solves. - [x] Ideally, include a test that fails without this PR but passes with it. - [x] If this PR changes code within `packages/svelte/src`, add a changeset (`npx changeset`). ### Tests and linting - [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` --- .changeset/quick-meals-shout.md | 5 ++ .../src/reactivity/url-search-params.js | 15 ++++ .../src/reactivity/url-search-params.test.ts | 21 ++++++ packages/svelte/src/reactivity/url.test.ts | 74 +++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 .changeset/quick-meals-shout.md diff --git a/.changeset/quick-meals-shout.md b/.changeset/quick-meals-shout.md new file mode 100644 index 0000000000..21871edd67 --- /dev/null +++ b/.changeset/quick-meals-shout.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't notify `searchParams` subscribers when the URL changes without affecting the search string diff --git a/packages/svelte/src/reactivity/url-search-params.js b/packages/svelte/src/reactivity/url-search-params.js index 38cf3ebe4f..d745bb6b08 100644 --- a/packages/svelte/src/reactivity/url-search-params.js +++ b/packages/svelte/src/reactivity/url-search-params.js @@ -54,6 +54,11 @@ export class SvelteURLSearchParams extends URLSearchParams { */ [REPLACE](params) { if (this.#updating) return; + + // the URL may have changed in a way that leaves the search string untouched — + // don't rebuild the params or notify readers if nothing changed + if (params.toString() === super.toString()) return; + this.#updating = true; for (const key of [...super.keys()]) { @@ -126,6 +131,16 @@ export class SvelteURLSearchParams extends URLSearchParams { return super.keys(); } + /** + * @param {(value: string, key: string, parent: URLSearchParams) => void} callback + * @param {any} [this_arg] + * @returns {void} + */ + forEach(callback, this_arg) { + get(this.#version); + super.forEach(callback, this_arg); + } + /** * @param {string} name * @param {string} value diff --git a/packages/svelte/src/reactivity/url-search-params.test.ts b/packages/svelte/src/reactivity/url-search-params.test.ts index b0c84872b0..765b4020f3 100644 --- a/packages/svelte/src/reactivity/url-search-params.test.ts +++ b/packages/svelte/src/reactivity/url-search-params.test.ts @@ -244,3 +244,24 @@ test('URLSearchParams.toString', () => { test('SvelteURLSearchParams instanceof URLSearchParams', () => { assert.ok(new SvelteURLSearchParams() instanceof URLSearchParams); }); + +test('params.forEach is reactive', () => { + const params = new SvelteURLSearchParams('foo=1'); + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + const entries: string[] = []; + params.forEach((value, key) => entries.push(`${key}=${value}`)); + log.push(entries.join('&')); + }); + }); + + flushSync(() => { + params.set('foo', '2'); + }); + + assert.deepEqual(log, ['foo=1', 'foo=2']); + + cleanup(); +}); diff --git a/packages/svelte/src/reactivity/url.test.ts b/packages/svelte/src/reactivity/url.test.ts index a79aa32315..d698116421 100644 --- a/packages/svelte/src/reactivity/url.test.ts +++ b/packages/svelte/src/reactivity/url.test.ts @@ -166,3 +166,77 @@ test('url.search normalizes value', () => { test('SvelteURL instanceof URL', () => { assert.ok(new SvelteURL('https://svelte.dev') instanceof URL); }); + +test('url.searchParams subscribers are not notified by changes that leave the search string untouched', () => { + const url = new SvelteURL('https://svelte.dev/a?foo=bar'); + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push(url.searchParams.toString()); + }); + }); + + flushSync(() => { + // does not affect the search string + url.pathname = '/b'; + }); + + flushSync(() => { + // neither does this + url.href = 'https://svelte.dev/c?foo=bar#hash'; + }); + + flushSync(() => { + // but this does + url.search = '?foo=baz'; + }); + + assert.deepEqual(log, ['foo=bar', 'foo=baz']); + + cleanup(); +}); + +test('url.searchParams.size is not notified by unrelated href changes', () => { + const url = new SvelteURL('https://svelte.dev/?foo=bar'); + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push(url.searchParams.size); + }); + }); + + flushSync(() => { + url.href = 'https://svelte.dev/other?foo=bar'; + }); + + flushSync(() => { + url.searchParams.append('baz', 'qux'); + }); + + assert.deepEqual(log, [1, 2]); + + cleanup(); +}); + +test('url.searchParams.forEach re-runs when the search string changes via the URL', () => { + const url = new SvelteURL('https://svelte.dev/?foo=1'); + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + const entries: string[] = []; + url.searchParams.forEach((value, key) => entries.push(`${key}=${value}`)); + log.push(entries.join('&')); + }); + }); + + flushSync(() => { + url.href = 'https://svelte.dev/?bar=2'; + }); + + assert.deepEqual(log, ['foo=1', 'bar=2']); + + cleanup(); +}); From c4daa490bbbf483ebba117c1afb6f71b4b19e63c Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Tue, 16 Jun 2026 01:18:02 +0900 Subject: [PATCH 03/16] fix: include wrapping parentheses in `{@const}` declarator end (#18436) `{@const x = (a)}` (and any `{@const}` whose initializer is wrapped in parentheses, e.g. `(a = b)`, `({ ... })`) produced a `VariableDeclarator` whose `end` fell before the closing `)`. `read_expression` strips the wrapping parens from the returned node but advances the parser past them, so `init.end` excludes the `)` while the parser position does not. Use the parser position for the declarator `end` instead. Fixes source slices / tooling that rely on the node range (the range previously yielded invalid, unbalanced source). image ### Before submitting the PR, please make sure you do the following - [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [x] This message body should clearly illustrate what problems it solves. - [x] Ideally, include a test that fails without this PR but passes with it. - [x] If this PR changes code within `packages/svelte/src`, add a changeset (`npx changeset`). ### Tests and linting - [ ] Run the tests with `pnpm test` and lint the project with `pnpm lint` --- .changeset/clean-melons-attack.md | 5 + .../src/compiler/phases/1-parse/state/tag.js | 6 +- .../const-tag-parenthesized-init/input.svelte | 6 + .../const-tag-parenthesized-init/output.json | 527 ++++++++++++++++++ 4 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 .changeset/clean-melons-attack.md create mode 100644 packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/output.json diff --git a/.changeset/clean-melons-attack.md b/.changeset/clean-melons-attack.md new file mode 100644 index 0000000000..8b5a9f2066 --- /dev/null +++ b/.changeset/clean-melons-attack.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: include wrapping parentheses in `{@const}` declarator `end` position diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 15c79e0353..f5314d04ed 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -783,6 +783,8 @@ function special(parser) { const expression_start = parser.index; const init = read_expression(parser); + // parser is past wrapping parens, but `init.end` is not — use the parser position + const declarator_end = parser.index; if ( init.type === 'SequenceExpression' && !parser.template.substring(expression_start, init.start).includes('(') @@ -801,7 +803,9 @@ function special(parser) { declaration: { type: 'VariableDeclaration', kind: 'const', - declarations: [{ type: 'VariableDeclarator', id, init, start: id.start, end: init.end }], + declarations: [ + { type: 'VariableDeclarator', id, init, start: id.start, end: declarator_end } + ], start: start + 2, // start at const, not at @const end: parser.index - 1 }, diff --git a/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/input.svelte b/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/input.svelte new file mode 100644 index 0000000000..39bf04093e --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/input.svelte @@ -0,0 +1,6 @@ +{#each items as item} + {@const x = (item.value)} + {@const y = (a = item.n)} + {@const z = ({ a: item.a })} + {x}{y}{z.a} +{/each} diff --git a/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/output.json b/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/output.json new file mode 100644 index 0000000000..3e8d8b23d5 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/output.json @@ -0,0 +1,527 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 126, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "EachBlock", + "start": 0, + "end": 126, + "expression": { + "type": "Identifier", + "start": 7, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "items" + }, + "body": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 21, + "end": 23, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "ConstTag", + "start": 23, + "end": 48, + "declaration": { + "type": "VariableDeclaration", + "kind": "const", + "declarations": [ + { + "type": "VariableDeclarator", + "id": { + "type": "Identifier", + "name": "x", + "start": 31, + "end": 32, + "loc": { + "start": { + "line": 2, + "column": 9, + "character": 31 + }, + "end": { + "line": 2, + "column": 10, + "character": 32 + } + } + }, + "init": { + "type": "MemberExpression", + "start": 36, + "end": 46, + "loc": { + "start": { + "line": 2, + "column": 14 + }, + "end": { + "line": 2, + "column": 24 + } + }, + "object": { + "type": "Identifier", + "start": 36, + "end": 40, + "loc": { + "start": { + "line": 2, + "column": 14 + }, + "end": { + "line": 2, + "column": 18 + } + }, + "name": "item" + }, + "property": { + "type": "Identifier", + "start": 41, + "end": 46, + "loc": { + "start": { + "line": 2, + "column": 19 + }, + "end": { + "line": 2, + "column": 24 + } + }, + "name": "value" + }, + "computed": false, + "optional": false + }, + "start": 31, + "end": 47 + } + ], + "start": 25, + "end": 47 + } + }, + { + "type": "Text", + "start": 48, + "end": 50, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "ConstTag", + "start": 50, + "end": 75, + "declaration": { + "type": "VariableDeclaration", + "kind": "const", + "declarations": [ + { + "type": "VariableDeclarator", + "id": { + "type": "Identifier", + "name": "y", + "start": 58, + "end": 59, + "loc": { + "start": { + "line": 3, + "column": 9, + "character": 58 + }, + "end": { + "line": 3, + "column": 10, + "character": 59 + } + } + }, + "init": { + "type": "AssignmentExpression", + "start": 63, + "end": 73, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 24 + } + }, + "operator": "=", + "left": { + "type": "Identifier", + "start": 63, + "end": 64, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 15 + } + }, + "name": "a" + }, + "right": { + "type": "MemberExpression", + "start": 67, + "end": 73, + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 24 + } + }, + "object": { + "type": "Identifier", + "start": 67, + "end": 71, + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 22 + } + }, + "name": "item" + }, + "property": { + "type": "Identifier", + "start": 72, + "end": 73, + "loc": { + "start": { + "line": 3, + "column": 23 + }, + "end": { + "line": 3, + "column": 24 + } + }, + "name": "n" + }, + "computed": false, + "optional": false + } + }, + "start": 58, + "end": 74 + } + ], + "start": 52, + "end": 74 + } + }, + { + "type": "Text", + "start": 75, + "end": 77, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "ConstTag", + "start": 77, + "end": 105, + "declaration": { + "type": "VariableDeclaration", + "kind": "const", + "declarations": [ + { + "type": "VariableDeclarator", + "id": { + "type": "Identifier", + "name": "z", + "start": 85, + "end": 86, + "loc": { + "start": { + "line": 4, + "column": 9, + "character": 85 + }, + "end": { + "line": 4, + "column": 10, + "character": 86 + } + } + }, + "init": { + "type": "ObjectExpression", + "start": 90, + "end": 103, + "loc": { + "start": { + "line": 4, + "column": 14 + }, + "end": { + "line": 4, + "column": 27 + } + }, + "properties": [ + { + "type": "Property", + "start": 92, + "end": 101, + "loc": { + "start": { + "line": 4, + "column": 16 + }, + "end": { + "line": 4, + "column": 25 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 92, + "end": 93, + "loc": { + "start": { + "line": 4, + "column": 16 + }, + "end": { + "line": 4, + "column": 17 + } + }, + "name": "a" + }, + "value": { + "type": "MemberExpression", + "start": 95, + "end": 101, + "loc": { + "start": { + "line": 4, + "column": 19 + }, + "end": { + "line": 4, + "column": 25 + } + }, + "object": { + "type": "Identifier", + "start": 95, + "end": 99, + "loc": { + "start": { + "line": 4, + "column": 19 + }, + "end": { + "line": 4, + "column": 23 + } + }, + "name": "item" + }, + "property": { + "type": "Identifier", + "start": 100, + "end": 101, + "loc": { + "start": { + "line": 4, + "column": 24 + }, + "end": { + "line": 4, + "column": 25 + } + }, + "name": "a" + }, + "computed": false, + "optional": false + }, + "kind": "init" + } + ] + }, + "start": 85, + "end": 104 + } + ], + "start": 79, + "end": 104 + } + }, + { + "type": "Text", + "start": 105, + "end": 107, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "ExpressionTag", + "start": 107, + "end": 110, + "expression": { + "type": "Identifier", + "start": 108, + "end": 109, + "loc": { + "start": { + "line": 5, + "column": 2 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "name": "x" + } + }, + { + "type": "ExpressionTag", + "start": 110, + "end": 113, + "expression": { + "type": "Identifier", + "start": 111, + "end": 112, + "loc": { + "start": { + "line": 5, + "column": 5 + }, + "end": { + "line": 5, + "column": 6 + } + }, + "name": "y" + } + }, + { + "type": "ExpressionTag", + "start": 113, + "end": 118, + "expression": { + "type": "MemberExpression", + "start": 114, + "end": 117, + "loc": { + "start": { + "line": 5, + "column": 8 + }, + "end": { + "line": 5, + "column": 11 + } + }, + "object": { + "type": "Identifier", + "start": 114, + "end": 115, + "loc": { + "start": { + "line": 5, + "column": 8 + }, + "end": { + "line": 5, + "column": 9 + } + }, + "name": "z" + }, + "property": { + "type": "Identifier", + "start": 116, + "end": 117, + "loc": { + "start": { + "line": 5, + "column": 10 + }, + "end": { + "line": 5, + "column": 11 + } + }, + "name": "a" + }, + "computed": false, + "optional": false + } + }, + { + "type": "Text", + "start": 118, + "end": 119, + "raw": "\n", + "data": "\n" + } + ] + }, + "context": { + "type": "Identifier", + "name": "item", + "start": 16, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 16, + "character": 16 + }, + "end": { + "line": 1, + "column": 20, + "character": 20 + } + } + } + } + ] + }, + "options": null +} From a6985bcd24faa8ec4a8de671b2013d98b0bc629f Mon Sep 17 00:00:00 2001 From: "jyc.dev" Date: Mon, 22 Jun 2026 13:03:18 +0200 Subject: [PATCH 04/16] fix: strip `?` from optional parameters in svelte lang ts (#18448) In `esrap@2.2.12` we fixed the missing `?` in ts files https://github.com/sveltejs/esrap/pull/139 It's good for `ts`, but bad for svelte compiler that was not removing it as it was a esrap issue not printing it :/ This issue came: https://github.com/sveltejs/esrap/issues/141 This PR is fixing the issue in the compiler stripping the `?` --- .changeset/strip-optional-param-marker.md | 5 +++++ packages/svelte/package.json | 2 +- .../phases/1-parse/remove_typescript_nodes.js | 6 +++++ .../_expected/client/index.svelte.js | 22 +++++++++++++++++++ .../_expected/server/index.svelte.js | 15 +++++++++++++ .../index.svelte | 14 ++++++++++++ pnpm-lock.yaml | 12 +++++----- 7 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 .changeset/strip-optional-param-marker.md create mode 100644 packages/svelte/tests/snapshot/samples/typescript-optional-parameter/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/typescript-optional-parameter/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/typescript-optional-parameter/index.svelte diff --git a/.changeset/strip-optional-param-marker.md b/.changeset/strip-optional-param-marker.md new file mode 100644 index 0000000000..a6b759747c --- /dev/null +++ b/.changeset/strip-optional-param-marker.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: strip `?` from optional parameters in ` + +{v} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e86c927efe..6331fd0789 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,8 +102,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 esrap: - specifier: ^2.2.11 - version: 2.2.11(@typescript-eslint/types@8.59.4) + specifier: ^2.2.12 + version: 2.2.12(@typescript-eslint/types@8.59.4) is-reference: specifier: ^3.0.3 version: 3.0.3 @@ -1418,8 +1418,8 @@ packages: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} - esrap@2.2.11: - resolution: {integrity: sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ==} + esrap@2.2.12: + resolution: {integrity: sha512-On0QbLyaiAkVC4eXtgnXK9Kh2opit+3rcUSOc45DqJ2s/X2eXAHsGOKRSJ6IDagQEW5vPyivANfXUiqgXC67Rw==} peerDependencies: '@typescript-eslint/types': ^8.2.0 peerDependenciesMeta: @@ -2886,7 +2886,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.4': @@ -3695,7 +3695,7 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.11(@typescript-eslint/types@8.59.4): + esrap@2.2.12(@typescript-eslint/types@8.59.4): dependencies: '@jridgewell/sourcemap-codec': 1.5.5 optionalDependencies: From 36ae0622a8da4c6e60d32cde199af3d7c3121cd3 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 22 Jun 2026 20:09:04 +0200 Subject: [PATCH 05/16] fix: always unset reactivity context after restoring it (#18453) When calling `save`, we restore the context after the promise resolves. But we do not unset it after the subsequent synchronous execution. That means that until `unset_context` runs in `async_derived` we will not have the correct (nulled) context. That causes problems if the `save` isn't the last promise contributing to the `async_derived`, because it means the context is not properly unset until the promise _after_ the `save` within the `async_derived` settles. This can cause all sorts of mixups, including a wrong mutation error. fixes #18441 --- .changeset/late-geese-fix.md | 5 +++ .../src/internal/client/reactivity/async.js | 2 + .../_config.js | 44 +++++++++++++++++++ .../main.svelte | 28 ++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 .changeset/late-geese-fix.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-parallel-derived-template-mutation/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-parallel-derived-template-mutation/main.svelte diff --git a/.changeset/late-geese-fix.md b/.changeset/late-geese-fix.md new file mode 100644 index 0000000000..b8e83fc8cd --- /dev/null +++ b/.changeset/late-geese-fix.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: always unset reactivity context after restoring it diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 13b6e42b3e..2e7df92dc0 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -25,6 +25,7 @@ import { set_reactivity_loss_tracker } from './deriveds.js'; import { aborted } from './effects.js'; +import { queue_micro_task } from '../dom/task.js'; /** * @param {Blocker[]} blockers @@ -169,6 +170,7 @@ export async function save(promise) { return () => { restore(); + queue_micro_task(unset_context); return value; }; } diff --git a/packages/svelte/tests/runtime-runes/samples/async-parallel-derived-template-mutation/_config.js b/packages/svelte/tests/runtime-runes/samples/async-parallel-derived-template-mutation/_config.js new file mode 100644 index 0000000000..9650e88993 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-parallel-derived-template-mutation/_config.js @@ -0,0 +1,44 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + + compileOptions: { + dev: true + }, + + async test({ assert, target, errors }) { + await tick(); + const [increment, update, resolve] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + + resolve.click(); + await tick(); + + update.click(); + await tick(); + + resolve.click(); + await tick(); + + assert.deepEqual( + errors.filter((error) => error.includes('state_unsafe_mutation')), + [] + ); + assert.htmlEqual( + target.innerHTML, + ` + + + +

count: 1

+

submits: 1

+

pending: 0

+

2

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-parallel-derived-template-mutation/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-parallel-derived-template-mutation/main.svelte new file mode 100644 index 0000000000..d9c804b314 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-parallel-derived-template-mutation/main.svelte @@ -0,0 +1,28 @@ + + + + + + +

count: {count}

+

submits: {submits}

+

pending: {$effect.pending()}

+

{await a + await b}

From eae50dfd1c2269e37258ef5c09527003bcf61573 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:44:52 -0400 Subject: [PATCH 06/16] Version Packages (#18435) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## svelte@5.56.4 ### Patch Changes - fix: include wrapping parentheses in `{@const}` declarator `end` position ([#18436](https://github.com/sveltejs/svelte/pull/18436)) - fix: always unset reactivity context after restoring it ([#18453](https://github.com/sveltejs/svelte/pull/18453)) - fix: don't notify `searchParams` subscribers when the URL changes without affecting the search string ([#18425](https://github.com/sveltejs/svelte/pull/18425)) - fix: strip `?` from optional parameters in ` + + From c2b7642263ec1f9d89f5d027d68f272742c449a7 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 10 Jul 2026 15:24:35 +0200 Subject: [PATCH 12/16] chore: add benchmarks (#18523) Add two kairo variants to better measure of `#traverse` / block effects perf --- .../tests/kairo_broad_block.bench.js | 47 ++++++++++++++++++ .../tests/kairo_deep_block.bench.js | 48 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 benchmarking/benchmarks/reactivity/tests/kairo_broad_block.bench.js create mode 100644 benchmarking/benchmarks/reactivity/tests/kairo_deep_block.bench.js diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_broad_block.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_broad_block.bench.js new file mode 100644 index 0000000000..e8451ec3fa --- /dev/null +++ b/benchmarking/benchmarks/reactivity/tests/kairo_broad_block.bench.js @@ -0,0 +1,47 @@ +import assert from 'node:assert'; +import * as $ from 'svelte/internal/client'; +import { block } from '../../../../packages/svelte/src/internal/client/reactivity/effects.js'; + +// Like `kairo_broad`, but each derived is also read by a block effect, as +// happens with e.g. `{#if derived}` in a component. Measures our #traverse perf better. +export default () => { + let head = $.state(0); + let last = head; + let counter = 0; + + const destroy = $.effect_root(() => { + for (let i = 0; i < 50; i++) { + let current = $.derived(() => { + return $.get(head) + i; + }); + let current2 = $.derived(() => { + return $.get(current) + 1; + }); + block(() => { + $.get(current2); + }); + $.render_effect(() => { + $.get(current2); + counter++; + }); + last = current2; + } + }); + + return { + destroy, + run() { + $.flush(() => { + $.set(head, 1); + }); + counter = 0; + for (let i = 0; i < 50; i++) { + $.flush(() => { + $.set(head, i); + }); + assert.equal($.get(last), i + 50); + } + assert.equal(counter, 50 * 50); + } + }; +}; diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_deep_block.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_deep_block.bench.js new file mode 100644 index 0000000000..a8d9fa8c35 --- /dev/null +++ b/benchmarking/benchmarks/reactivity/tests/kairo_deep_block.bench.js @@ -0,0 +1,48 @@ +import assert from 'node:assert'; +import * as $ from 'svelte/internal/client'; +import { block } from '../../../../packages/svelte/src/internal/client/reactivity/effects.js'; + +let len = 50; +const iter = 50; + +// Like `kairo_deep`, but the derived chain is also read by a block effect, as +// happens with e.g. `{#if derived}` in a component. Measures our #traverse perf better. +export default () => { + let head = $.state(0); + let current = head; + for (let i = 0; i < len; i++) { + let c = current; + current = $.derived(() => { + return $.get(c) + 1; + }); + } + let counter = 0; + + const destroy = $.effect_root(() => { + block(() => { + $.get(current); + }); + + $.render_effect(() => { + $.get(current); + counter++; + }); + }); + + return { + destroy, + run() { + $.flush(() => { + $.set(head, 1); + }); + counter = 0; + for (let i = 0; i < iter; i++) { + $.flush(() => { + $.set(head, i); + }); + assert.equal($.get(current), len + i); + } + assert.equal(counter, iter); + } + }; +}; From bfbb026f2f7db6ced0d86ba0feb40587c0e8f598 Mon Sep 17 00:00:00 2001 From: JY Wey <34165386+JaiWey@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:42:01 +1200 Subject: [PATCH 13/16] fix: skip unnecessary derived effect in earlier batch (#18525) fixes #18438 Currently the `mark` method inside `#merge` for `earlier_batch` will schedule effect for undirty derived. Add the condition for derived to prevent unnecessary effect be scheduled. ``` if ((flags & DERIVED) !== 0) { mark(/** @type {Derived} */ (reaction)); } ``` --- .changeset/curly-wasps-hide.md | 5 ++++ .../src/internal/client/reactivity/batch.js | 6 ++++ .../samples/async-batch-derived/_config.js | 28 ++++++++++++++++++ .../samples/async-batch-derived/main.svelte | 29 +++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 .changeset/curly-wasps-hide.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-batch-derived/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-batch-derived/main.svelte diff --git a/.changeset/curly-wasps-hide.md b/.changeset/curly-wasps-hide.md new file mode 100644 index 0000000000..7e55d77ba0 --- /dev/null +++ b/.changeset/curly-wasps-hide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: skip unnecessary derived effect in earlier batch diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 7d14b80519..5becae2dc9 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -530,6 +530,12 @@ export class Batch { const mark = (value) => { var reactions = value.reactions; if (reactions === null) return; + // skip if value is derived and is neither dirty nor maybe dirty. transitive + // deriveds (a derived depending on another derived) are only MAYBE_DIRTY, so + // we must continue traversing them to reach the effects that depend on them + if ((value.f & DERIVED) !== 0 && (value.f & (DIRTY | MAYBE_DIRTY)) === 0) { + return; + } for (const reaction of reactions) { var flags = reaction.f; diff --git a/packages/svelte/tests/runtime-runes/samples/async-batch-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/async-batch-derived/_config.js new file mode 100644 index 0000000000..d5286d9d95 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-batch-derived/_config.js @@ -0,0 +1,28 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [increment, pop] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + `

Loading...

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

Loading...

` + ); + pop.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ` 2 2 1`); + + pop.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ` 2 2 1`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-batch-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-batch-derived/main.svelte new file mode 100644 index 0000000000..af7f0f468c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-batch-derived/main.svelte @@ -0,0 +1,29 @@ + + + + +{#snippet defaultPending()} +

Loading...

+{/snippet} + +{#if count > 0} + + {await push(count)} {count} {other} + +{/if} From 08a9e9e7e43453e2146357dd72d44cca1e41e29e Mon Sep 17 00:00:00 2001 From: Rabindra Kumar Meher Date: Fri, 10 Jul 2026 19:29:34 +0530 Subject: [PATCH 14/16] fix: prevent derived connection leak in untracked contexts (#18517) Fixes #18501. Problem is that is_updating_effect was also set to true for branch/root effects which are not reactive --------- Co-authored-by: Simon Holthausen --- packages/svelte/src/internal/client/runtime.js | 5 ++++- packages/svelte/tests/signals/test.ts | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 227203523e..188d16a820 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -61,6 +61,9 @@ import { without_reactive_context } from './dom/elements/bindings/shared.js'; import { set_signal_status, update_derived_status } from './reactivity/status.js'; import * as w from './warnings.js'; +/** + * True if updating in an effect context that is reactive (i.e. not branch/root effects) + */ let is_updating_effect = false; export let is_destroying_effect = false; @@ -444,7 +447,7 @@ export function update_effect(effect) { var was_updating_effect = is_updating_effect; active_effect = effect; - is_updating_effect = true; + is_updating_effect = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0; // Branch/root effects are not reactive contexts if (DEV) { var previous_component_fn = dev_current_component_function; diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 927ce2e665..07f79bd395 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1503,4 +1503,22 @@ describe('signals', () => { assert.deepEqual(log, ['inner destroyed', 'inner destroyed']); }; }); + + test('derived read in an untracked context should not leak in deps reactions', () => { + return () => { + let s = state('hello'); + let a = derived(() => $.get(s)); + let b = derived(() => $.get(a)); + + let destroy = effect_root(() => { + $.get(b); + }); + + destroy(); + + // a was spuriously added to s.reactions via is_updating_effect + // even though the entire derived chain was read in an untracked context + assert.equal(s.reactions, null); + }; + }); }); From 8e4f26552c7dd40cc056a28357f6171542d2549d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:01:20 +0200 Subject: [PATCH 15/16] chore: changeset (#18527) for #18517 --- .changeset/common-ways-deny.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/common-ways-deny.md diff --git a/.changeset/common-ways-deny.md b/.changeset/common-ways-deny.md new file mode 100644 index 0000000000..75e9375965 --- /dev/null +++ b/.changeset/common-ways-deny.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't (re)connect deriveds when read inside branch/root effects From b4d1583ae20f3869a88a731d9a265c546c099f66 Mon Sep 17 00:00:00 2001 From: Thribhuvan Date: Fri, 10 Jul 2026 19:31:48 +0530 Subject: [PATCH 16/16] fix: transform computed keys in keyed each block destructuring patterns (#18521) fixes #18519 --- .changeset/shiny-keys-dance.md | 5 ++++ .../3-transform/client/visitors/EachBlock.js | 5 +++- .../Child.svelte | 7 +++++ .../_config.js | 29 +++++++++++++++++++ .../main.svelte | 12 ++++++++ 5 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 .changeset/shiny-keys-dance.md create mode 100644 packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/main.svelte diff --git a/.changeset/shiny-keys-dance.md b/.changeset/shiny-keys-dance.md new file mode 100644 index 0000000000..20cf754212 --- /dev/null +++ b/.changeset/shiny-keys-dance.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: transform computed keys in keyed `{#each}` destructuring patterns diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index a1371b516a..b33eddd461 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -294,7 +294,10 @@ export function EachBlock(node, context) { let key_function = b.id('$.index'); if (node.metadata.keyed) { - const pattern = /** @type {Pattern} */ (node.context); // can only be keyed when a context is provided + // can only be keyed when a context is provided + const pattern = /** @type {Pattern} */ ( + context.visit(/** @type {Pattern} */ (node.context), key_state) + ); const expression = /** @type {Expression} */ ( context.visit(/** @type {Expression} */ (node.key), key_state) ); diff --git a/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/Child.svelte b/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/Child.svelte new file mode 100644 index 0000000000..35544d0a55 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/Child.svelte @@ -0,0 +1,7 @@ + + +{#each options as { [labelKey]: label, [valueKey]: value } (value)} +

{label}: {value}

+{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/_config.js b/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/_config.js new file mode 100644 index 0000000000..8a8789d712 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/_config.js @@ -0,0 +1,29 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +// https://github.com/sveltejs/svelte/issues/18519 +export default test({ + html: ` + +

1: a1

+

2: a2

+

3: a3

+ `, + + test({ assert, target }) { + const btn = target.querySelector('button'); + ok(btn); + + flushSync(() => btn.click()); + + assert.htmlEqual( + target.innerHTML, + ` + +

3: a3

+

2: a2

+

1: a1

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/main.svelte new file mode 100644 index 0000000000..6885a6918e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-keyed-computed-destructuring-key/main.svelte @@ -0,0 +1,12 @@ + + + +