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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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 @@ + + + + From af6f1d309b0fc7a9dcde7099b5f231078c9f5738 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:06:53 +0200 Subject: [PATCH 17/33] chore: run every benchmark in its own process (#18542) While benchmarking it became apparent that when running the full `pnpm bench:compare` suite results could be wildly different compared to only comparing single benchmarks. The reason (most likely) is that the heap/GC/JIT state from one benchmark contaminates the others. Therefore this adjusts the runners such that each benchmark entry is run in its own forked process, not just each branch. This should give better, more stable results. Also a little fix to compare results by name; I made the mistake of adding a new benchmark to main which wasn't present on other branches yet, which made the results really confusing. --- benchmarking/compare/generate-report.js | 33 +++- benchmarking/compare/runner.js | 73 +++++++-- benchmarking/run.js | 203 +++++++++++++++--------- 3 files changed, 220 insertions(+), 89 deletions(-) diff --git a/benchmarking/compare/generate-report.js b/benchmarking/compare/generate-report.js index a61f58909b..a06aaf9fc2 100644 --- a/benchmarking/compare/generate-report.js +++ b/benchmarking/compare/generate-report.js @@ -33,11 +33,38 @@ export function generate_report(outdir) { write(''); - for (let i = 0; i < results[0].length; i += 1) { - write(`${results[0][i].benchmark}`); + // match results by benchmark name — branches may have different benchmark + // lists (e.g. a benchmark that only exists on one of the branches), so + // pairing by array index would misattribute results + const by_name = results.map((result) => new Map(result.map((r) => [r.benchmark, r]))); + + /** @type {string[]} */ + const names = []; + + for (const result of results) { + for (const { benchmark } of result) { + if (!names.includes(benchmark)) { + names.push(benchmark); + } + } + } + + for (const name of names) { + const entries = by_name.map((map) => map.get(name)); + const missing = entries + .map((entry, b) => (entry === undefined ? branches[b] : null)) + .filter((branch) => branch !== null); + + write(`${name}`); + + if (missing.length > 0) { + write(` skipped (missing on ${missing.join(', ')})`); + write(''); + continue; + } for (const metric of ['time', 'gc_time']) { - const times = results.map((result) => +result[i][metric]); + const times = entries.map((entry) => +entry[metric]); let min = Infinity; let max = -Infinity; let min_index = -1; diff --git a/benchmarking/compare/runner.js b/benchmarking/compare/runner.js index 31a8e6b44b..fa746f9869 100644 --- a/benchmarking/compare/runner.js +++ b/benchmarking/compare/runner.js @@ -1,18 +1,67 @@ +import { fork } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js'; import { with_cpu_profile } from '../utils.js'; -const results = []; -const PROFILE_DIR = process.env.BENCH_PROFILE_DIR; +const PROFILE_DIR = process.env.BENCH_PROFILE_DIR ?? null; +const single = process.env.BENCH_SINGLE; -for (let i = 0; i < reactivity_benchmarks.length; i += 1) { - const benchmark = reactivity_benchmarks[i]; +if (single) { + // child mode — run a single benchmark and report the result to the parent + const benchmark = reactivity_benchmarks.find((b) => b.label === single); - process.stderr.write(`Running ${i + 1}/${reactivity_benchmarks.length} ${benchmark.label} `); - results.push({ - benchmark: benchmark.label, - ...(await with_cpu_profile(PROFILE_DIR, benchmark.label, () => benchmark.fn())) - }); - process.stderr.write('\x1b[2K\r'); -} + if (!benchmark) { + throw new Error(`Unknown benchmark ${single}`); + } + + const result = await with_cpu_profile(PROFILE_DIR, benchmark.label, () => benchmark.fn()); + + // exit via the callback so the message is guaranteed to be delivered + /** @type {NodeJS.Process} */ (process).send(result, () => process.exit(0)); +} else { + // parent mode — run every benchmark in its own child process, so that + // heap/GC/JIT state from one benchmark cannot contaminate the others + const filename = fileURLToPath(import.meta.url); + const results = []; + + for (let i = 0; i < reactivity_benchmarks.length; i += 1) { + const benchmark = reactivity_benchmarks[i]; + + process.stderr.write(`Running ${i + 1}/${reactivity_benchmarks.length} ${benchmark.label} `); + + const result = await new Promise((fulfil, reject) => { + const child = fork(filename, [], { + env: { + ...process.env, + BENCH_SINGLE: benchmark.label + } + }); + + /** @type {object | null} */ + let message_received = null; -process.send(results); + child.on('message', (message) => { + message_received = /** @type {object} */ (message); + }); + + child.on('error', reject); + + child.on('exit', (code) => { + if (message_received === null) { + reject(new Error(`benchmark ${benchmark.label} exited with code ${code}`)); + } else { + fulfil(message_received); + } + }); + }); + + results.push({ + benchmark: benchmark.label, + .../** @type {object} */ (result) + }); + + process.stderr.write('\x1b[2K\r'); + } + + /** @type {NodeJS.Process} */ (process).send(results); +} diff --git a/benchmarking/run.js b/benchmarking/run.js index 80e40a5ff1..e44e816dd0 100644 --- a/benchmarking/run.js +++ b/benchmarking/run.js @@ -1,94 +1,149 @@ +import { fork } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; import * as $ from '../packages/svelte/src/internal/client/index.js'; import { reactivity_benchmarks } from './benchmarks/reactivity/index.js'; import { ssr_benchmarks } from './benchmarks/ssr/index.js'; import { with_cpu_profile } from './utils.js'; -// e.g. `pnpm bench kairo` to only run the kairo benchmarks -const filters = process.argv.slice(2); - const PROFILE_DIR = './benchmarking/.profiles'; -const suites = [ - { - benchmarks: reactivity_benchmarks.filter( - (b) => filters.length === 0 || filters.some((f) => b.label.includes(f)) - ), - name: 'reactivity benchmarks' - }, - { - benchmarks: ssr_benchmarks.filter( - (b) => filters.length === 0 || filters.some((f) => b.label.includes(f)) - ), - name: 'server-side rendering benchmarks' - } -].filter((suite) => suite.benchmarks.length > 0); - -if (suites.length === 0) { - console.log('No benchmarks matched provided filters'); - process.exit(1); -} - -const COLUMN_WIDTHS = [25, 9, 9]; -const TOTAL_WIDTH = COLUMN_WIDTHS.reduce((a, b) => a + b); - -const pad_right = (str, n) => str + ' '.repeat(n - str.length); -const pad_left = (str, n) => ' '.repeat(n - str.length) + str; +const single = process.env.BENCH_SINGLE; -let total_time = 0; -let total_gc_time = 0; +if (single) { + // child mode — run a single benchmark and report the result to the parent + const benchmark = [...reactivity_benchmarks, ...ssr_benchmarks].find((b) => b.label === single); -$.push({}, true); + if (!benchmark) { + throw new Error(`Unknown benchmark ${single}`); + } -try { - for (const { benchmarks, name } of suites) { - let suite_time = 0; - let suite_gc_time = 0; + $.push({}, true); + + const result = await with_cpu_profile(PROFILE_DIR, benchmark.label, () => benchmark.fn()); + + $.pop(); + + // exit via the callback so the message is guaranteed to be delivered + /** @type {NodeJS.Process} */ (process).send(result, () => process.exit(0)); +} else { + // parent mode — run every benchmark in its own child process, so that + // heap/GC/JIT state from one benchmark cannot contaminate the others + + // e.g. `pnpm bench kairo` to only run the kairo benchmarks + const filters = process.argv.slice(2); + + const suites = [ + { + benchmarks: reactivity_benchmarks.filter( + (b) => filters.length === 0 || filters.some((f) => b.label.includes(f)) + ), + name: 'reactivity benchmarks' + }, + { + benchmarks: ssr_benchmarks.filter( + (b) => filters.length === 0 || filters.some((f) => b.label.includes(f)) + ), + name: 'server-side rendering benchmarks' + } + ].filter((suite) => suite.benchmarks.length > 0); - console.log(`\nRunning ${name}...\n`); - console.log( - pad_right('Benchmark', COLUMN_WIDTHS[0]) + - pad_left('Time', COLUMN_WIDTHS[1]) + - pad_left('GC time', COLUMN_WIDTHS[2]) - ); - console.log('='.repeat(TOTAL_WIDTH)); + if (suites.length === 0) { + console.log('No benchmarks matched provided filters'); + process.exit(1); + } - for (const benchmark of benchmarks) { - const results = await with_cpu_profile(PROFILE_DIR, benchmark.label, () => benchmark.fn()); + const filename = fileURLToPath(import.meta.url); + + /** + * @param {string} label + * @returns {Promise<{ time: number, gc_time: number }>} + */ + const run_benchmark = (label) => { + return new Promise((fulfil, reject) => { + const child = fork(filename, [], { + env: { + ...process.env, + BENCH_SINGLE: label + } + }); + + /** @type {{ time: number, gc_time: number } | null} */ + let result = null; + + child.on('message', (message) => { + result = /** @type {{ time: number, gc_time: number }} */ (message); + }); + + child.on('error', reject); + + child.on('exit', (code) => { + if (result === null) { + reject(new Error(`benchmark ${label} exited with code ${code}`)); + } else { + fulfil(result); + } + }); + }); + }; + + const COLUMN_WIDTHS = [25, 9, 9]; + const TOTAL_WIDTH = COLUMN_WIDTHS.reduce((a, b) => a + b); + + /** @type {(str: string, n: number) => string} */ + const pad_right = (str, n) => str + ' '.repeat(n - str.length); + /** @type {(str: string, n: number) => string} */ + const pad_left = (str, n) => ' '.repeat(n - str.length) + str; + + let total_time = 0; + let total_gc_time = 0; + + try { + for (const { benchmarks, name } of suites) { + let suite_time = 0; + let suite_gc_time = 0; + + console.log(`\nRunning ${name}...\n`); + console.log( + pad_right('Benchmark', COLUMN_WIDTHS[0]) + + pad_left('Time', COLUMN_WIDTHS[1]) + + pad_left('GC time', COLUMN_WIDTHS[2]) + ); + console.log('='.repeat(TOTAL_WIDTH)); + + for (const benchmark of benchmarks) { + const results = await run_benchmark(benchmark.label); + console.log( + pad_right(benchmark.label, COLUMN_WIDTHS[0]) + + pad_left(results.time.toFixed(2), COLUMN_WIDTHS[1]) + + pad_left(results.gc_time.toFixed(2), COLUMN_WIDTHS[2]) + ); + total_time += results.time; + total_gc_time += results.gc_time; + suite_time += results.time; + suite_gc_time += results.gc_time; + } + + console.log('='.repeat(TOTAL_WIDTH)); console.log( - pad_right(benchmark.label, COLUMN_WIDTHS[0]) + - pad_left(results.time.toFixed(2), COLUMN_WIDTHS[1]) + - pad_left(results.gc_time.toFixed(2), COLUMN_WIDTHS[2]) + pad_right('suite', COLUMN_WIDTHS[0]) + + pad_left(suite_time.toFixed(2), COLUMN_WIDTHS[1]) + + pad_left(suite_gc_time.toFixed(2), COLUMN_WIDTHS[2]) ); - total_time += results.time; - total_gc_time += results.gc_time; - suite_time += results.time; - suite_gc_time += results.gc_time; + console.log('='.repeat(TOTAL_WIDTH)); } - console.log('='.repeat(TOTAL_WIDTH)); - console.log( - pad_right('suite', COLUMN_WIDTHS[0]) + - pad_left(suite_time.toFixed(2), COLUMN_WIDTHS[1]) + - pad_left(suite_gc_time.toFixed(2), COLUMN_WIDTHS[2]) - ); - console.log('='.repeat(TOTAL_WIDTH)); - } - - if (PROFILE_DIR !== null) { console.log(`\nCPU profiles written to ${PROFILE_DIR}`); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + process.exit(1); } -} catch (e) { - // eslint-disable-next-line no-console - console.error(e); - process.exit(1); -} -$.pop(); + console.log(''); -console.log(''); - -console.log( - pad_right('total', COLUMN_WIDTHS[0]) + - pad_left(total_time.toFixed(2), COLUMN_WIDTHS[1]) + - pad_left(total_gc_time.toFixed(2), COLUMN_WIDTHS[2]) -); + console.log( + pad_right('total', COLUMN_WIDTHS[0]) + + pad_left(total_time.toFixed(2), COLUMN_WIDTHS[1]) + + pad_left(total_gc_time.toFixed(2), COLUMN_WIDTHS[2]) + ); +} From 4da9f74cd95e838acc56c4aa0d2e926d5f68972d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:07:24 +0200 Subject: [PATCH 18/33] chore: bench compare report as HTML (#18543) Creates a `results.html` file which is much better to look at compared to the terminal output. --- .gitignore | 1 + benchmarking/compare/generate-report.js | 46 +- benchmarking/compare/index.js | 2 +- benchmarking/compare/results.template.html | 741 +++++++++++++++++++++ 4 files changed, 785 insertions(+), 5 deletions(-) create mode 100644 benchmarking/compare/results.template.html diff --git a/.gitignore b/.gitignore index 556cae6344..e4f9f9cf87 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ packages/svelte/scripts/_baseline/ benchmarking/.profiles benchmarking/compare/.results benchmarking/compare/.profiles +benchmarking/compare/results.html diff --git a/benchmarking/compare/generate-report.js b/benchmarking/compare/generate-report.js index a06aaf9fc2..70f7b30fbb 100644 --- a/benchmarking/compare/generate-report.js +++ b/benchmarking/compare/generate-report.js @@ -2,15 +2,27 @@ import fs from 'node:fs'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; -export function generate_report(outdir) { +const REPORT_DATA_PLACEHOLDER = '%%REPORT_DATA%%'; +const report_template = fs.readFileSync( + new URL('./results.template.html', import.meta.url), + 'utf-8' +); + +if (!report_template.includes(REPORT_DATA_PLACEHOLDER)) { + throw new Error(`Missing ${REPORT_DATA_PLACEHOLDER} in results.template.html`); +} + +export function generate_report(outdir, branches) { const result_files = fs .readdirSync(outdir) - .filter((file) => file.endsWith('.json')) + .filter((file) => file.endsWith('.json') && (!branches || branches.includes(file.slice(0, -5)))) .sort((a, b) => a.localeCompare(b)); - const branches = result_files.map((file) => file.slice(0, -5)); + // always do this so that ordering lines up (branches argument might be passed in a different order than the result files are sorted + branches = result_files.map((file) => file.slice(0, -5)); + const results = result_files.map((file) => - JSON.parse(fs.readFileSync(`${outdir}/${file}`, 'utf-8')) + JSON.parse(fs.readFileSync(path.join(outdir, file), 'utf-8')) ); if (results.length === 0) { @@ -95,6 +107,32 @@ export function generate_report(outdir) { write(''); } + + const benchmarks = names.map((name) => ({ + name, + values: by_name.map((map) => { + const entry = map.get(name); + + if (entry === undefined) return null; + + return { + time: Number(entry.time), + gc_time: Number(entry.gc_time) + }; + }) + })); + const data = JSON.stringify({ + generated_at: new Date().toISOString(), + branches, + benchmarks + }) + .replaceAll('<', '\\u003c') + .replaceAll('\u2028', '\\u2028') + .replaceAll('\u2029', '\\u2029'); + const html_file = path.resolve(outdir, '../results.html'); + + fs.writeFileSync(html_file, report_template.replace(REPORT_DATA_PLACEHOLDER, data)); + console.log(`\nHTML report written to ${html_file}`); } function char(i) { diff --git a/benchmarking/compare/index.js b/benchmarking/compare/index.js index 9064ee7da9..2e76f46e1b 100644 --- a/benchmarking/compare/index.js +++ b/benchmarking/compare/index.js @@ -85,4 +85,4 @@ if (PROFILE_DIR !== null) { console.log(`\nCPU profiles written to ${PROFILE_DIR}`); } -generate_report(outdir); +generate_report(outdir, requested_branches); diff --git a/benchmarking/compare/results.template.html b/benchmarking/compare/results.template.html new file mode 100644 index 0000000000..02f208ac4f --- /dev/null +++ b/benchmarking/compare/results.template.html @@ -0,0 +1,741 @@ + + + + + + Benchmark comparison + + + +
+

Benchmark comparison

+

+ Runtime results across branches. Green cells are fastest for an entry and red cells expose + the largest regressions. Overall runtime normalizes every benchmark to its fastest result + before averaging, so long-running entries do not outweigh short ones. +

+ +
+ +
+
+
+

Branch standings

+

+ Wins count the fastest branch for each comparable entry. Normalized runtime is the + average slowdown against each entry's fastest result; lower is better. Click a heading + to sort. +

+
+
+
+ + + + + + + + + +
+ +
+
+
+ +
+
+
+

Results by benchmark

+

+ Each cell shows runtime, difference from the winner, and GC time. Missing entries are + excluded from both standings. +

+
+
+
+ + +
+
+
+
+
+ +
+
+ + + + + From a91b0687866c8b2f78692010702bb811bf27b9a8 Mon Sep 17 00:00:00 2001 From: Fedor Nezhivoi Date: Tue, 14 Jul 2026 16:40:30 +0700 Subject: [PATCH 19/33] fix: abort deriveds own AbortSignal when it disconnects (#18400) Fixes https://github.com/sveltejs/svelte/issues/18301 --------- Co-authored-by: Fedor Nezhivoi Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon Holthausen --- .changeset/early-crabs-rest.md | 5 +++++ .../internal/client/reactivity/deriveds.js | 9 +++++++-- .../svelte/src/internal/client/runtime.js | 8 ++++++++ .../abort-signal-derived-destroy/Child.svelte | 20 +++++++++++++++++++ .../abort-signal-derived-destroy/_config.js | 14 +++++++++++++ .../abort-signal-derived-destroy/main.svelte | 14 +++++++++++++ .../abort-signal-derived-set-state/_config.js | 2 ++ .../main.svelte | 8 ++++---- 8 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 .changeset/early-crabs-rest.md create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte diff --git a/.changeset/early-crabs-rest.md b/.changeset/early-crabs-rest.md new file mode 100644 index 0000000000..d3f290a5c2 --- /dev/null +++ b/.changeset/early-crabs-rest.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: abort deriveds own AbortSignal when it disconnects diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index fd64f3b45d..6b48bc8a23 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -28,6 +28,7 @@ import { skipped_deps, new_deps } from '../runtime.js'; +import { without_reactive_context } from '../dom/elements/bindings/shared.js'; import { equals, safe_equals } from './equality.js'; import * as e from '../errors.js'; import * as w from '../warnings.js'; @@ -450,14 +451,18 @@ export function freeze_derived_effects(derived) { // if the effect has a teardown function or abort signal, call it if (e.teardown || e.ac) { e.teardown?.(); - e.ac?.abort(STALE_REACTION); + if (e.ac !== null) { + without_reactive_context(() => { + /** @type {AbortController} */ (e.ac).abort(STALE_REACTION); + e.ac = null; + }); + } // make it a noop so it doesn't get called again if the derived // is unfrozen. we don't set it to `null`, because the existence // of a teardown function is what determines whether the // effect runs again during unfreezing (but not for teardown-only effects) if (e.fn !== null) e.teardown = noop; - e.ac = null; remove_reactions(e, 0); destroy_effect_children(e); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 188d16a820..2ebce07916 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -408,6 +408,14 @@ function remove_reaction(signal, dependency) { update_derived_status(derived); } + // Call abort controller, noone's listening to this derived anymore + if (derived.ac !== null) { + without_reactive_context(() => { + /** @type {AbortController} */ (derived.ac).abort(STALE_REACTION); + derived.ac = null; + }); + } + // freeze any effects inside this derived freeze_derived_effects(derived); diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte new file mode 100644 index 0000000000..5a837e5ea4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte @@ -0,0 +1,20 @@ + + +{der} diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js new file mode 100644 index 0000000000..3c6ec98fe6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js @@ -0,0 +1,14 @@ +import { ok, test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + async test({ assert, target, errors }) { + const btn = target.querySelector('button'); + + flushSync(() => { + btn?.click(); + }); + assert.htmlEqual(target.innerHTML, '1 '); + assert.deepEqual(errors, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte new file mode 100644 index 0000000000..57c467a80c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte @@ -0,0 +1,14 @@ + + +{aborted} + + + +{#if count % 2 === 0} + +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js index 2dacf188d7..aa38dd8280 100644 --- a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js @@ -4,9 +4,11 @@ import { flushSync } from 'svelte'; export default test({ async test({ assert, target, errors }) { const btn = target.querySelector('button'); + flushSync(() => { btn?.click(); }); + assert.htmlEqual(target.innerHTML, '1:1 '); assert.deepEqual(errors, []); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte index ebefe38fb2..e76cecbac9 100644 --- a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte @@ -9,9 +9,9 @@ const signal = getAbortSignal(); signal.addEventListener("abort", () => { - try{ + try { aborted++; - }catch(e){ + } catch(e) { console.error(e); } }); @@ -19,6 +19,6 @@ }) -{der} +{der}:{aborted} - \ No newline at end of file + From edeef1f9f713ab1ffdba698ff6d92cce8da1c5b4 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:41:20 +0200 Subject: [PATCH 20/33] chore: add some failing tests (#18528) During my explorations I collected these new tests which are currently failing. Two of them work on the incremental-batches branch, all of them (adjusted for new behavior) pass on my uncommitted entangle batches branch, and all of them also pass on another uncommitted experimental branch of overlaying deriveds which will likely not land. --- .../_config.js | 42 +++++++++++++++++++ .../main.svelte | 23 ++++++++++ .../async-derived-not-overfiring/_config.js | 35 ++++++++++++++++ .../async-derived-not-overfiring/main.svelte | 29 +++++++++++++ .../async-derived-not-underfiring/_config.js | 28 +++++++++++++ .../async-derived-not-underfiring/main.svelte | 22 ++++++++++ 6 files changed, 179 insertions(+) create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/_config.js new file mode 100644 index 0000000000..1a7ed0eb92 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/_config.js @@ -0,0 +1,42 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + skip: true, // TODO fix + async test({ assert, target, logs }) { + await tick(); + + const [a, b, log, resolve] = target.querySelectorAll('button'); + const [p] = target.querySelectorAll('p'); + + a.click(); + await tick(); + assert.htmlEqual(p.innerHTML, '0 0 0'); + + b.click(); + await tick(); + assert.htmlEqual(p.innerHTML, '0 0 0'); + + log.click(); + await tick(); + assert.deepEqual(logs, [0, 2]); + + resolve.click(); + await tick(); + assert.htmlEqual(p.innerHTML, '1 0 1'); + assert.deepEqual(logs, [0, 2, 1]); + + log.click(); + await tick(); + assert.deepEqual(logs, [0, 2, 1, 2]); + + resolve.click(); + await tick(); + assert.htmlEqual(p.innerHTML, '1 1 2'); + assert.deepEqual(logs, [0, 2, 1, 2, 2]); + + log.click(); + await tick(); + assert.deepEqual(logs, [0, 2, 1, 2, 2, 2]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/main.svelte new file mode 100644 index 0000000000..bf543ba595 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-log-outside-reactivity/main.svelte @@ -0,0 +1,23 @@ + + + + + + + +

{await push(a)} {await push(b)} {d}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/_config.js new file mode 100644 index 0000000000..b959ba5407 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/_config.js @@ -0,0 +1,35 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + skip: true, // TODO fix + async test({ assert, target, logs }) { + await tick(); + + const [a, b, log, resolve] = target.querySelectorAll('button'); + const [div] = target.querySelectorAll('div'); + + assert.deepEqual(logs, ['e1 0', 'e2 0']); + logs.length = 0; + + a.click(); + await tick(); + assert.htmlEqual(div.innerHTML, '

0

0

0

'); + + b.click(); + await tick(); + assert.htmlEqual(div.innerHTML, '

0

1

1

'); + + log.click(); + await tick(); + assert.deepEqual(logs, ['e1 1', 'e2 1', 'runs 2']); // ideally it's only 2 runs, one or two more would also be acceptable but not the 8 that it's today + logs.length = 0; + + resolve.click(); + await tick(); + log.click(); + await tick(); + assert.htmlEqual(div.innerHTML, '

1

2

2

'); + assert.deepEqual(logs, ['e1 2', 'e2 2', 'runs 3']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/main.svelte new file mode 100644 index 0000000000..784932ab5d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring/main.svelte @@ -0,0 +1,29 @@ + + + + + + + +
+

{await push(a)}

+

{d}

+

{d}

+
diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/_config.js new file mode 100644 index 0000000000..7d31d30b46 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/_config.js @@ -0,0 +1,28 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + skip: true, // TODO fix + async test({ assert, target, logs }) { + await tick(); + + const [a, b, resolve] = target.querySelectorAll('button'); + const [p] = target.querySelectorAll('p'); + + assert.deepEqual(logs, ['b: 0, d: 0']); + logs.length = 0; + + a.click(); + await tick(); + b.click(); + await tick(); + assert.htmlEqual(p.innerHTML, '0 1'); + assert.deepEqual(logs, ['b: 1, d: 0']); + logs.length = 0; + + resolve.click(); + await tick(); + assert.htmlEqual(p.innerHTML, '2 1'); + assert.deepEqual(logs, ['b: 1, d: 2']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/main.svelte new file mode 100644 index 0000000000..97d91ae994 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-not-underfiring/main.svelte @@ -0,0 +1,22 @@ + + + + + + +

{await push(d)} {b}

From 199ffebca375b29b65fcac53fc1825b452175a53 Mon Sep 17 00:00:00 2001 From: prashantg-pixi Date: Tue, 14 Jul 2026 15:21:13 +0530 Subject: [PATCH 21/33] fix: clear previous_task reference after abort in Tween (#18541) avoids memory leaks --- .changeset/tween-previous-task-leak.md | 5 +++++ packages/svelte/src/motion/tweened.js | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/tween-previous-task-leak.md diff --git a/.changeset/tween-previous-task-leak.md b/.changeset/tween-previous-task-leak.md new file mode 100644 index 0000000000..6208e7c87a --- /dev/null +++ b/.changeset/tween-previous-task-leak.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: clear previous_task reference after abort in Tween to prevent memory leak on interrupted tweens diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js index a24148d075..460db0df64 100644 --- a/packages/svelte/src/motion/tweened.js +++ b/packages/svelte/src/motion/tweened.js @@ -275,6 +275,7 @@ export class Tween { } previous_task?.abort(); + previous_task = null; } const elapsed = now - start; From a4fd67e361914df612ae432ff75283c2d332ae18 Mon Sep 17 00:00:00 2001 From: adiGuba Date: Tue, 14 Jul 2026 11:54:13 +0200 Subject: [PATCH 22/33] fix: $state.eager() is sometimes incorrect in SSR (#18530) Fix #18529 A simple fix : `context.visit()` was missing on the argument of `$state.eager()`, so the generated code can be incorrect in some case : --- .changeset/long-buttons-hunt.md | 5 +++++ .../phases/3-transform/server/visitors/CallExpression.js | 2 +- .../samples/state-eager/_expected.html | 2 ++ .../server-side-rendering/samples/state-eager/main.svelte | 7 +++++++ 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .changeset/long-buttons-hunt.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/state-eager/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/state-eager/main.svelte diff --git a/.changeset/long-buttons-hunt.md b/.changeset/long-buttons-hunt.md new file mode 100644 index 0000000000..68d0b60ff4 --- /dev/null +++ b/.changeset/long-buttons-hunt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure `$state.eager()` is correctly transormed for SSR output diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js index 8525fb6366..ac78f55da9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js @@ -46,7 +46,7 @@ export function CallExpression(node, context) { } if (rune === '$state.eager') { - return node.arguments[0]; + return context.visit(node.arguments[0]); } if (rune === '$state.snapshot') { diff --git a/packages/svelte/tests/server-side-rendering/samples/state-eager/_expected.html b/packages/svelte/tests/server-side-rendering/samples/state-eager/_expected.html new file mode 100644 index 0000000000..9d3ea2f5b4 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/state-eager/_expected.html @@ -0,0 +1,2 @@ +
value=0
+
eager=0
\ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/state-eager/main.svelte b/packages/svelte/tests/server-side-rendering/samples/state-eager/main.svelte new file mode 100644 index 0000000000..d144914f72 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/state-eager/main.svelte @@ -0,0 +1,7 @@ + + +
value={value}
+
eager={$state.eager(value)}
From 602a873b6b82bba4e6edc91b039e2f6defbe3fc4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:59:06 +0200 Subject: [PATCH 23/33] Version Packages (#18497) 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.5 ### Patch Changes - chore: drop dead code that make TSGO fail ([#18496](https://github.com/sveltejs/svelte/pull/18496)) - fix: don't (re)connect deriveds when read inside branch/root effects ([#18527](https://github.com/sveltejs/svelte/pull/18527)) - fix: skip unnecessary derived effect in earlier batch ([#18525](https://github.com/sveltejs/svelte/pull/18525)) - fix: avoid declaration tag warning in event handlers ([#18500](https://github.com/sveltejs/svelte/pull/18500)) - fix: abort deriveds own AbortSignal when it disconnects ([#18400](https://github.com/sveltejs/svelte/pull/18400)) - fix: ensure `$state.eager()` is correctly transormed for SSR output ([#18530](https://github.com/sveltejs/svelte/pull/18530)) - fix: correctly transform declaration tags during SSR ([#18492](https://github.com/sveltejs/svelte/pull/18492)) - fix: transform computed keys in keyed `{#each}` destructuring patterns ([#18521](https://github.com/sveltejs/svelte/pull/18521)) - fix: chain preprocessor sourcemaps with an empty `sources[0]` instead of dropping them ([#18518](https://github.com/sveltejs/svelte/pull/18518)) - fix: clear previous_task reference after abort in Tween to prevent memory leak on interrupted tweens ([#18541](https://github.com/sveltejs/svelte/pull/18541)) - fix: don't treat declaration tags as parts inside each blocks ([#18507](https://github.com/sveltejs/svelte/pull/18507)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/big-months-shout.md | 5 ----- .changeset/common-ways-deny.md | 5 ----- .changeset/curly-wasps-hide.md | 5 ----- .changeset/dull-oranges-fry.md | 5 ----- .changeset/early-crabs-rest.md | 5 ----- .changeset/long-buttons-hunt.md | 5 ----- .changeset/rich-jokes-attack.md | 5 ----- .changeset/shiny-keys-dance.md | 5 ----- .changeset/tame-donkeys-jump.md | 5 ----- .changeset/tween-previous-task-leak.md | 5 ----- .changeset/young-doodles-beam.md | 5 ----- packages/svelte/CHANGELOG.md | 26 ++++++++++++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 14 files changed, 28 insertions(+), 57 deletions(-) delete mode 100644 .changeset/big-months-shout.md delete mode 100644 .changeset/common-ways-deny.md delete mode 100644 .changeset/curly-wasps-hide.md delete mode 100644 .changeset/dull-oranges-fry.md delete mode 100644 .changeset/early-crabs-rest.md delete mode 100644 .changeset/long-buttons-hunt.md delete mode 100644 .changeset/rich-jokes-attack.md delete mode 100644 .changeset/shiny-keys-dance.md delete mode 100644 .changeset/tame-donkeys-jump.md delete mode 100644 .changeset/tween-previous-task-leak.md delete mode 100644 .changeset/young-doodles-beam.md diff --git a/.changeset/big-months-shout.md b/.changeset/big-months-shout.md deleted file mode 100644 index 3d8ca0045e..0000000000 --- a/.changeset/big-months-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: drop dead code that make TSGO fail diff --git a/.changeset/common-ways-deny.md b/.changeset/common-ways-deny.md deleted file mode 100644 index 75e9375965..0000000000 --- a/.changeset/common-ways-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't (re)connect deriveds when read inside branch/root effects diff --git a/.changeset/curly-wasps-hide.md b/.changeset/curly-wasps-hide.md deleted file mode 100644 index 7e55d77ba0..0000000000 --- a/.changeset/curly-wasps-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: skip unnecessary derived effect in earlier batch diff --git a/.changeset/dull-oranges-fry.md b/.changeset/dull-oranges-fry.md deleted file mode 100644 index 0efcc6fa2d..0000000000 --- a/.changeset/dull-oranges-fry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: avoid declaration tag warning in event handlers diff --git a/.changeset/early-crabs-rest.md b/.changeset/early-crabs-rest.md deleted file mode 100644 index d3f290a5c2..0000000000 --- a/.changeset/early-crabs-rest.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: abort deriveds own AbortSignal when it disconnects diff --git a/.changeset/long-buttons-hunt.md b/.changeset/long-buttons-hunt.md deleted file mode 100644 index 68d0b60ff4..0000000000 --- a/.changeset/long-buttons-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure `$state.eager()` is correctly transormed for SSR output diff --git a/.changeset/rich-jokes-attack.md b/.changeset/rich-jokes-attack.md deleted file mode 100644 index 46e7d2d8c8..0000000000 --- a/.changeset/rich-jokes-attack.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly transform declaration tags during SSR diff --git a/.changeset/shiny-keys-dance.md b/.changeset/shiny-keys-dance.md deleted file mode 100644 index 20cf754212..0000000000 --- a/.changeset/shiny-keys-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: transform computed keys in keyed `{#each}` destructuring patterns diff --git a/.changeset/tame-donkeys-jump.md b/.changeset/tame-donkeys-jump.md deleted file mode 100644 index aae38910db..0000000000 --- a/.changeset/tame-donkeys-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: chain preprocessor sourcemaps with an empty `sources[0]` instead of dropping them diff --git a/.changeset/tween-previous-task-leak.md b/.changeset/tween-previous-task-leak.md deleted file mode 100644 index 6208e7c87a..0000000000 --- a/.changeset/tween-previous-task-leak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"svelte": patch ---- - -fix: clear previous_task reference after abort in Tween to prevent memory leak on interrupted tweens diff --git a/.changeset/young-doodles-beam.md b/.changeset/young-doodles-beam.md deleted file mode 100644 index b68f55d241..0000000000 --- a/.changeset/young-doodles-beam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't treat declaration tags as parts inside each blocks diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 80b61facb0..d80da025c9 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,31 @@ # svelte +## 5.56.5 + +### Patch Changes + +- chore: drop dead code that make TSGO fail ([#18496](https://github.com/sveltejs/svelte/pull/18496)) + +- fix: don't (re)connect deriveds when read inside branch/root effects ([#18527](https://github.com/sveltejs/svelte/pull/18527)) + +- fix: skip unnecessary derived effect in earlier batch ([#18525](https://github.com/sveltejs/svelte/pull/18525)) + +- fix: avoid declaration tag warning in event handlers ([#18500](https://github.com/sveltejs/svelte/pull/18500)) + +- fix: abort deriveds own AbortSignal when it disconnects ([#18400](https://github.com/sveltejs/svelte/pull/18400)) + +- fix: ensure `$state.eager()` is correctly transormed for SSR output ([#18530](https://github.com/sveltejs/svelte/pull/18530)) + +- fix: correctly transform declaration tags during SSR ([#18492](https://github.com/sveltejs/svelte/pull/18492)) + +- fix: transform computed keys in keyed `{#each}` destructuring patterns ([#18521](https://github.com/sveltejs/svelte/pull/18521)) + +- fix: chain preprocessor sourcemaps with an empty `sources[0]` instead of dropping them ([#18518](https://github.com/sveltejs/svelte/pull/18518)) + +- fix: clear previous_task reference after abort in Tween to prevent memory leak on interrupted tweens ([#18541](https://github.com/sveltejs/svelte/pull/18541)) + +- fix: don't treat declaration tags as parts inside each blocks ([#18507](https://github.com/sveltejs/svelte/pull/18507)) + ## 5.56.4 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 34fa68c8a7..a26a3e4a76 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.56.4", + "version": "5.56.5", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index ef468a2f79..80b38c225a 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.56.4'; +export const VERSION = '5.56.5'; export const PUBLIC_VERSION = '5'; From 22e0adb75a07442015feb0465cee1d3a61729e90 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:42:31 +0200 Subject: [PATCH 24/33] fix: rerun derived that had an abort controller on reconnection (#18551) Follow-up to #18400 - we need to mark a derived with an abort signal that is frozen as dirty so it is guaranteed to rerun when it reconnects/is re-requested. Else you could return a stale value, or worse, you returned a promise from the derived which you aborted, and it's now in the rejected state until you update one of its dependencies. --- .changeset/lucky-dolls-yell.md | 5 +++ .../svelte/src/internal/client/runtime.js | 2 ++ .../_config.js | 32 +++++++++++++++++++ .../main.svelte | 32 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 .changeset/lucky-dolls-yell.md create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/main.svelte diff --git a/.changeset/lucky-dolls-yell.md b/.changeset/lucky-dolls-yell.md new file mode 100644 index 0000000000..d158f376d1 --- /dev/null +++ b/.changeset/lucky-dolls-yell.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: rerun derived that had an abort controller on reconnection diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 2ebce07916..4458595d35 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -413,6 +413,8 @@ function remove_reaction(signal, dependency) { without_reactive_context(() => { /** @type {AbortController} */ (derived.ac).abort(STALE_REACTION); derived.ac = null; + // ensure it reruns right away next time instead of potentially returning a rejected promise as its value + set_signal_status(derived, DIRTY); }); } diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/_config.js new file mode 100644 index 0000000000..7caf765e3b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/_config.js @@ -0,0 +1,32 @@ +import { test } from '../../test'; +import { tick } from 'svelte'; + +export default test({ + async test({ assert, target }) { + const [increment, toggle, resolve] = target.querySelectorAll('button'); + const [div] = target.querySelectorAll('div'); + + assert.htmlEqual(div.innerHTML, 'loading'); + resolve.click(); + await tick(); + assert.htmlEqual(div.innerHTML, '0'); + + increment.click(); + await tick(); + assert.htmlEqual(div.innerHTML, 'loading'); + + toggle.click(); + await tick(); + assert.htmlEqual(div.innerHTML, ''); + + toggle.click(); + await tick(); + assert.htmlEqual(div.innerHTML, 'loading'); + + resolve.click(); // this one's for clearing the obsolete/aborted one from the queue + await tick(); + resolve.click(); + await tick(); + assert.htmlEqual(div.innerHTML, '2'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/main.svelte new file mode 100644 index 0000000000..a047afdd44 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-rerun-on-reconnect/main.svelte @@ -0,0 +1,32 @@ + + + + + + +
+ {#if show} + {#await double} + loading + {:then value} + {value} + {:catch} + error + {/await} + {/if} +
From d0dbe1a48013b9c405b518511f8330baf3a27bc9 Mon Sep 17 00:00:00 2001 From: Joe Schafer Date: Thu, 16 Jul 2026 04:35:37 -0700 Subject: [PATCH 25/33] perf: skip quadratic blocker analysis when no top-level await (#18548) Skip function reference tracing in `calculate_blockers` when a component has no top-level `await`. Blockers only represent dependencies on top-level async statements. Without a top-level `await`, no binding can have a blocker, so tracing every top-level function cannot affect the generated output. In large components with many functions and transitive assignments, that unnecessary work can become quadratic. --- .changeset/fast-cats-compile.md | 5 +++++ packages/svelte/src/compiler/phases/2-analyze/index.js | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .changeset/fast-cats-compile.md diff --git a/.changeset/fast-cats-compile.md b/.changeset/fast-cats-compile.md new file mode 100644 index 0000000000..585ce0ed1d --- /dev/null +++ b/.changeset/fast-cats-compile.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +perf: skip unnecessary blocker analysis when compiling components without top-level await diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index ef20049697..67e9030188 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -1221,6 +1221,10 @@ function calculate_blockers(instance, analysis) { } } + // With no top-level await, no binding can have a blocker and function tracing + // cannot affect the output. + if (!awaited) return; + flush_sync_group(); for (const fn of functions) { From 4a6a85b5f149cc96514ed3bf5e59083b9246d394 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:21:52 +0200 Subject: [PATCH 26/33] Version Packages (#18552) 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.6 ### Patch Changes - perf: skip unnecessary blocker analysis when compiling components without top-level await ([#18548](https://github.com/sveltejs/svelte/pull/18548)) - fix: rerun derived that had an abort controller on reconnection ([#18551](https://github.com/sveltejs/svelte/pull/18551)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/fast-cats-compile.md | 5 ----- .changeset/lucky-dolls-yell.md | 5 ----- packages/svelte/CHANGELOG.md | 8 ++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 .changeset/fast-cats-compile.md delete mode 100644 .changeset/lucky-dolls-yell.md diff --git a/.changeset/fast-cats-compile.md b/.changeset/fast-cats-compile.md deleted file mode 100644 index 585ce0ed1d..0000000000 --- a/.changeset/fast-cats-compile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -perf: skip unnecessary blocker analysis when compiling components without top-level await diff --git a/.changeset/lucky-dolls-yell.md b/.changeset/lucky-dolls-yell.md deleted file mode 100644 index d158f376d1..0000000000 --- a/.changeset/lucky-dolls-yell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: rerun derived that had an abort controller on reconnection diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index d80da025c9..0f26b571d3 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.56.6 + +### Patch Changes + +- perf: skip unnecessary blocker analysis when compiling components without top-level await ([#18548](https://github.com/sveltejs/svelte/pull/18548)) + +- fix: rerun derived that had an abort controller on reconnection ([#18551](https://github.com/sveltejs/svelte/pull/18551)) + ## 5.56.5 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a26a3e4a76..855ba92a8d 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.56.5", + "version": "5.56.6", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 80b38c225a..05d88ef904 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.56.5'; +export const VERSION = '5.56.6'; export const PUBLIC_VERSION = '5'; From b791cac54e2db43317f2bc6c8e41e694245c551e Mon Sep 17 00:00:00 2001 From: Manuel <30698007+manuel3108@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:47:30 +0200 Subject: [PATCH 27/33] chore: provide `indent` option for `print` (#18474) Relevant for https://github.com/sveltejs/cli/pull/1138. This is basically just an option from `esrap` that we pass through. That will allow tools like `sv migrate` to provide a guessed indent based on the other file contents and therefore allow us to produce way smaller diffs. Since we are just passing an option, there is no need for a test here. Technically a `feat:` but i dont think this is relevant enough. ### 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. - [ ] 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/short-otters-find.md | 5 +++++ packages/svelte/src/compiler/print/index.js | 5 ++++- packages/svelte/src/compiler/print/types.d.ts | 1 + packages/svelte/types/index.d.ts | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/short-otters-find.md diff --git a/.changeset/short-otters-find.md b/.changeset/short-otters-find.md new file mode 100644 index 0000000000..57194653b6 --- /dev/null +++ b/.changeset/short-otters-find.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: provide `indent` option for `print` diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index fbd7e86f12..a5fbc96fc8 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -30,7 +30,10 @@ export function print(ast, options = undefined) { }), ...svelte_visitors(comments), ...css_visitors - }) + }), + { + indent: options?.indent + } ); } diff --git a/packages/svelte/src/compiler/print/types.d.ts b/packages/svelte/src/compiler/print/types.d.ts index d0ff909525..9eccebb6f3 100644 --- a/packages/svelte/src/compiler/print/types.d.ts +++ b/packages/svelte/src/compiler/print/types.d.ts @@ -4,4 +4,5 @@ import type ts from 'esrap/languages/ts'; export type Options = { getLeadingComments?: NonNullable[0]>['getLeadingComments'] | undefined; getTrailingComments?: NonNullable[0]>['getTrailingComments'] | undefined; + indent?: string; // default tab }; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 5f41fabf60..d758022ae4 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1854,6 +1854,7 @@ declare module 'svelte/compiler' { type Options = { getLeadingComments?: NonNullable[0]>['getLeadingComments'] | undefined; getTrailingComments?: NonNullable[0]>['getTrailingComments'] | undefined; + indent?: string; // default tab }; export {}; From d9e40d17cf751e22dbc3ec42b9cb50920e2d1229 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 18 Jul 2026 21:00:02 -0400 Subject: [PATCH 28/33] docs: update link on hooks page (#18564) this docs page updated recently and broke everything --- documentation/docs/05-special-elements/01-svelte-boundary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/05-special-elements/01-svelte-boundary.md b/documentation/docs/05-special-elements/01-svelte-boundary.md index e1ad00a50b..41ebee1564 100644 --- a/documentation/docs/05-special-elements/01-svelte-boundary.md +++ b/documentation/docs/05-special-elements/01-svelte-boundary.md @@ -109,7 +109,7 @@ By default, error boundaries have no effect on the server — if an error occurs Since 5.51 you can control this behaviour for boundaries with a `failed` snippet, by calling [`render(...)`](imperative-component-api#render) with a `transformError` function. -> [!NOTE] If you're using Svelte via a framework such as SvelteKit, you most likely don't have direct access to the `render(...)` call — the framework must configure `transformError` on your behalf. SvelteKit will add support for this in the near future, via the [`handleError`](../kit/hooks#Shared-hooks-handleError) hook. +> [!NOTE] If you're using Svelte via a framework such as SvelteKit, you most likely don't have direct access to the `render(...)` call — the framework must configure `transformError` on your behalf. SvelteKit will add support for this in the near future, via the [`handleError`](../kit/hooks#handleError) hook. The `transformError` function must return a JSON-stringifiable object which will be used to render the `failed` snippet. This object will be serialized and used to hydrate the snippet in the browser: From b29d7002ecf9bc0036b18647c0b7677a7cb0a914 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:23:49 -0400 Subject: [PATCH 29/33] Version Packages (#18560) 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.7 ### Patch Changes - chore: provide `indent` option for `print` ([#18474](https://github.com/sveltejs/svelte/pull/18474)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/short-otters-find.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/short-otters-find.md diff --git a/.changeset/short-otters-find.md b/.changeset/short-otters-find.md deleted file mode 100644 index 57194653b6..0000000000 --- a/.changeset/short-otters-find.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: provide `indent` option for `print` diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 0f26b571d3..a9b93f5341 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.56.7 + +### Patch Changes + +- chore: provide `indent` option for `print` ([#18474](https://github.com/sveltejs/svelte/pull/18474)) + ## 5.56.6 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 855ba92a8d..67f349be71 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.56.6", + "version": "5.56.7", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 05d88ef904..e9737ec13c 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.56.6'; +export const VERSION = '5.56.7'; export const PUBLIC_VERSION = '5'; From 2bace308e37ac1def958be750bd699ed302bb715 Mon Sep 17 00:00:00 2001 From: Floze <88098863+floze-the-genius@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:17:27 +0400 Subject: [PATCH 30/33] fix: preserve select selection with spread attributes (#18561) Fixes #18557 A ` + + + {#if show_extra} + + {/if} + From 3dde011d3a9e7b9145169da0b75dcd607a378c0e Mon Sep 17 00:00:00 2001 From: Nic Polumeyv Date: Thu, 23 Jul 2026 17:45:50 -0400 Subject: [PATCH 31/33] fix: call `onerror` and provide a working `reset` when hydrating a failed boundary (#18556) Fixes #18555 A boundary that failed during SSR hydrates via `#hydrate_failed_content`, which never calls `onerror` and passes the `failed` snippet a no-op `reset`. Both came in with #17672, whose docs say `onerror` "will be called upon hydration with the deserialized error object". Once hydrated as failed, the boundary can never leave that state. Downstream this is what keeps SvelteKit's `+error.svelte` mounted after navigating away from a server-rendered error page (sveltejs/kit#16345). This extracts the reset/onerror machinery from `#handle_error` into `#create_reset` and uses it in the hydration path too. `onerror` is invoked in a microtask because it may mutate state, which is disallowed while hydrating. `#handle_error` already invokes it asynchronously, so the timing matches the error path. The tests flip a `recovered` flag before calling `reset`, since the child would otherwise throw again. That is the intended retry pattern, and the same one kit uses when it resets route boundaries on navigation. Verified against kit end to end, hydrating a server-rendered error page and navigating away now tears it down with no kit changes needed. --- .changeset/hydrated-boundary-reset.md | 5 ++ .../internal/client/dom/blocks/boundary.js | 89 ++++++++++++------- .../error-boundary-hydrate-1/_config.js | 19 ++++ .../error-boundary-hydrate-1/child.svelte | 3 + .../error-boundary-hydrate-1/main.svelte | 29 ++++++ .../error-boundary-hydrate-2/_config.js | 16 ++++ .../error-boundary-hydrate-2/child.svelte | 3 + .../error-boundary-hydrate-2/main.svelte | 22 +++++ 8 files changed, 152 insertions(+), 34 deletions(-) create mode 100644 .changeset/hydrated-boundary-reset.md create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/main.svelte diff --git a/.changeset/hydrated-boundary-reset.md b/.changeset/hydrated-boundary-reset.md new file mode 100644 index 0000000000..799a57fd9b --- /dev/null +++ b/.changeset/hydrated-boundary-reset.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: call `onerror` and provide a working `reset` when hydrating a failed boundary diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 0c0903ff52..4f655a3695 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -199,17 +199,68 @@ export class Boundary { */ #hydrate_failed_content(error) { const failed = this.#props.failed; + const { reset, invoke_onerror } = this.#create_reset(error); + + // `onerror` may mutate state, which is disallowed while hydrating + queue_micro_task(invoke_onerror); + if (!failed) return; this.#failed_effect = branch(() => { failed( this.#anchor, () => error, - () => () => {} + () => reset ); }); } + /** + * Creates the `reset` function for a failed boundary, along with a function + * that invokes `onerror` with it (if provided) + * @param {unknown} error + * @returns {{ reset: () => void, invoke_onerror: () => void }} + */ + #create_reset(error) { + var did_reset = false; + var calling_on_error = false; + + const reset = () => { + if (did_reset) { + w.svelte_boundary_reset_noop(); + return; + } + + did_reset = true; + + if (calling_on_error) { + e.svelte_boundary_reset_onerror(); + } + + if (this.#failed_effect !== null) { + pause_effect(this.#failed_effect, () => { + this.#failed_effect = null; + }); + } + + this.#run(() => { + this.#render(); + }); + }; + + const invoke_onerror = () => { + try { + calling_on_error = true; + this.#props.onerror?.(error, reset); + calling_on_error = false; + } catch (err) { + invoke_error_boundary(err, this.#effect && this.#effect.parent); + } + }; + + return { reset, invoke_onerror }; + } + #hydrate_pending_content() { const pending = this.#props.pending; if (!pending) return; @@ -429,43 +480,13 @@ export class Boundary { set_hydrate_node(skip_nodes()); } - var onerror = this.#props.onerror; let failed = this.#props.failed; - var did_reset = false; - var calling_on_error = false; - - const reset = () => { - if (did_reset) { - w.svelte_boundary_reset_noop(); - return; - } - - did_reset = true; - - if (calling_on_error) { - e.svelte_boundary_reset_onerror(); - } - - if (this.#failed_effect !== null) { - pause_effect(this.#failed_effect, () => { - this.#failed_effect = null; - }); - } - - this.#run(() => { - this.#render(); - }); - }; /** @param {unknown} transformed_error */ const handle_error_result = (transformed_error) => { - try { - calling_on_error = true; - onerror?.(transformed_error, reset); - calling_on_error = false; - } catch (error) { - invoke_error_boundary(error, this.#effect && this.#effect.parent); - } + const { reset, invoke_onerror } = this.#create_reset(transformed_error); + + invoke_onerror(); if (failed) { this.#failed_effect = this.#run(() => { diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/_config.js new file mode 100644 index 0000000000..f0aef7e92f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/_config.js @@ -0,0 +1,19 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['hydrate'], + ssrHtml: '

failed: error

', + transformError: () => 'error', + + test({ assert, target, logs }) { + // `onerror` is called upon hydration with the deserialized error + assert.deepEqual(logs, ['onerror: error']); + + const btn = target.querySelector('button'); + btn?.click(); + flushSync(); + + assert.htmlEqual(target.innerHTML, '

recovered

'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/child.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/child.svelte new file mode 100644 index 0000000000..e93c7bb0fa --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/child.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/main.svelte new file mode 100644 index 0000000000..e84198473b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-1/main.svelte @@ -0,0 +1,29 @@ + + + { + console.log(`onerror: ${error}`); + reset_fn = reset; + }} +> + {#if recovered} +

recovered

+ {:else} + + {/if} + + {#snippet failed(error)} +

failed: {error}

+ {/snippet} +
+ + diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/_config.js new file mode 100644 index 0000000000..e5c52eb19c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['hydrate'], + ssrHtml: '

failed: error

', + transformError: () => 'error', + + test({ assert, target }) { + const btn = target.querySelector('button'); + btn?.click(); + flushSync(); + + assert.htmlEqual(target.innerHTML, '

recovered

'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/child.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/child.svelte new file mode 100644 index 0000000000..e93c7bb0fa --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/child.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/main.svelte new file mode 100644 index 0000000000..1642a94e7a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-hydrate-2/main.svelte @@ -0,0 +1,22 @@ + + + + {#if recovered} +

recovered

+ {:else} + + {/if} + + {#snippet failed(error, reset)} +

failed: {error}

+ + {/snippet} +
From 23bc246a99c649c0c325509debb93e062948e240 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Fri, 24 Jul 2026 14:14:11 -0600 Subject: [PATCH 32/33] chore: Supply chain hardening (#18253) `svelte` Supply-chain hardening pass. ## Changes - Bump `packageManager` from `pnpm@10.4.0` to `pnpm@10.33.4` with `+sha512` integrity suffix. - Add to `pnpm-workspace.yaml`: - `minimumReleaseAge: 1440` - `minimumReleaseAgeExclude: ['@sveltejs/*', svelte, esrap, devalue]` - `blockExoticSubdeps: true` - Remove `pkg.pr.new.yml` workflow in favor of `pkg.svelte.dev` - Pin all third-party GitHub Actions to full SHA with `# vX.Y.Z` comments across `ci.yml`, `autofix.yml`, `release.yml`, `ecosystem-ci-trigger.yml` - Upgrade `pnpm/action-setup` references in `ci.yml`, `autofix.yml`, `release.yml` from a SHA that was incorrectly labeled `# v4` (actually `v5.0.0` / `v4.4.0`) to the current `v6.0.8` SHA so the version label matches reality. --- .github/workflows/autofix.yml | 8 +- .github/workflows/ci.yml | 30 +-- .github/workflows/ecosystem-ci-trigger.yml | 8 +- .github/workflows/pkg.pr.new.yml | 229 --------------------- .github/workflows/release.yml | 6 +- package.json | 2 +- pnpm-workspace.yaml | 12 ++ 7 files changed, 39 insertions(+), 256 deletions(-) delete mode 100644 .github/workflows/pkg.pr.new.yml diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index b6c26e0792..5cdbd34e5b 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -28,7 +28,7 @@ jobs: - name: Get PR ref if: github.event_name != 'workflow_dispatch' id: pr - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | const { data: pull } = await github.rest.pulls.get({ @@ -46,12 +46,12 @@ jobs: core.setFailed('PR is from a fork'); } core.setOutput('ref', pull.head.ref); - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 if: github.event_name == 'workflow_dispatch' || steps.pr.outcome == 'success' with: ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || steps.pr.outputs.ref }} - - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 - - uses: actions/setup-node@v6 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 24 cache: pnpm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 365717755e..3846690135 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,9 +32,9 @@ jobs: os: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: ${{ matrix.node-version }} cache: pnpm @@ -48,9 +48,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22 cache: pnpm @@ -65,9 +65,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 24 cache: pnpm @@ -82,9 +82,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 24 cache: pnpm @@ -105,9 +105,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 24 cache: pnpm diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index 8a6d1bf345..8691a64ca4 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -17,7 +17,7 @@ jobs: contents: read # to clone the repo steps: - name: Check User Permissions - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 id: check-permissions with: script: | @@ -56,7 +56,7 @@ jobs: } - name: Get PR Data - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 id: get-pr-data with: script: | @@ -106,7 +106,7 @@ jobs: - name: Generate Token id: generate-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 with: app-id: ${{ secrets.ECOSYSTEM_CI_GITHUB_APP_ID }} private-key: ${{ secrets.ECOSYSTEM_CI_GITHUB_APP_PRIVATE_KEY }} @@ -115,7 +115,7 @@ jobs: svelte-ecosystem-ci - name: Trigger Downstream Workflow - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 id: trigger env: COMMENT: ${{ github.event.comment.body }} diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml deleted file mode 100644 index 0fcda5a778..0000000000 --- a/.github/workflows/pkg.pr.new.yml +++ /dev/null @@ -1,229 +0,0 @@ -name: pkg.pr.new -on: - pull_request_target: - types: [opened, synchronize] - push: - branches: [main] - workflow_dispatch: - inputs: - sha: - description: 'Commit SHA to build' - required: true - type: string - pr: - description: 'PR number to comment on' - required: true - type: number - -permissions: {} - -jobs: - build: - # Skip pull_request_target events from forks — maintainers can use workflow_dispatch instead - if: > - github.event_name != 'pull_request_target' || - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - # No permissions — this job runs user-controlled code - permissions: {} - - steps: - - uses: actions/checkout@v6 - with: - # For pull_request_target, check out the PR head. - # For workflow_dispatch, check out the manually specified SHA. - # For push, fall back to the push SHA. - ref: ${{ github.event.pull_request.head.sha || inputs.sha || github.sha }} - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - - uses: actions/setup-node@v6 - with: - node-version: 22.x - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - - run: pnpx pkg-pr-new publish --comment=off --json output.json --compact --no-template './packages/svelte' - - - name: Upload output - uses: actions/upload-artifact@v4 - with: - name: output - path: ./output.json - - # Sanitizes the untrusted output from the build job before it's consumed by - # jobs with elevated permissions. This ensures that only known package names - # and valid SHA prefixes make it through. - sanitize: - needs: build - runs-on: ubuntu-latest - - permissions: {} - - steps: - - name: Download artifact - uses: actions/download-artifact@v7 - with: - name: output - - - name: Sanitize output - uses: actions/github-script@v8 - with: - script: | - const fs = require('fs'); - const raw = JSON.parse(fs.readFileSync('output.json', 'utf8')); - - const ALLOWED_PACKAGES = new Set(['svelte']); - const SHA_PATTERN = /^[0-9a-f]{7}$/; - - const packages = (raw.packages || []) - .filter(p => { - if (!ALLOWED_PACKAGES.has(p.name)) { - console.log(`Skipping unexpected package: ${JSON.stringify(p.name)}`); - return false; - } - const sha = p.url?.replace(/^.+@([^@]+)$/, '$1'); - if (!sha || !SHA_PATTERN.test(sha)) { - console.log(`Skipping package with invalid SHA: ${JSON.stringify(p.url)}`); - return false; - } - return true; - }) - .map(p => ({ - name: p.name, - sha: p.url.replace(/^.+@([^@]+)$/, '$1'), - })); - - fs.writeFileSync('sanitized-output.json', JSON.stringify({ packages }), 'utf8'); - - - name: Upload sanitized output - uses: actions/upload-artifact@v4 - with: - name: sanitized-output - path: ./sanitized-output.json - - comment: - needs: sanitize - if: github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - - permissions: - contents: read - pull-requests: write - - steps: - - name: Download sanitized artifact - uses: actions/download-artifact@v7 - with: - name: sanitized-output - - - name: Resolve PR number - id: pr - uses: actions/github-script@v8 - with: - script: | - if (context.eventName === 'pull_request_target') { - core.setOutput('number', context.issue.number); - return; - } - - // For workflow_dispatch, use the explicitly provided PR number. - // We can't use listPullRequestsAssociatedWithCommit because fork - // commits don't exist in the base repo, so the API returns nothing. - const pr = Number('${{ inputs.pr }}'); - if (!pr || isNaN(pr)) { - core.setFailed('workflow_dispatch requires a valid pr input'); - return; - } - - core.setOutput('number', pr); - - - name: Post or update comment - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const fs = require('fs'); - const { packages } = JSON.parse(fs.readFileSync('sanitized-output.json', 'utf8')); - - if (packages.length === 0) { - console.log('No valid packages found. Skipping comment.'); - return; - } - - const issue_number = parseInt('${{ steps.pr.outputs.number }}', 10); - - const bot_comment_identifier = ``; - - const body = `${bot_comment_identifier} - - [Playground](https://svelte.dev/playground?version=pr-${issue_number}) - - \`\`\` - ${packages.map(p => `pnpm add https://pkg.pr.new/${p.name}@${issue_number}`).join('\n')} - \`\`\` - `; - - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number, - }); - const existing = comments.data.find(c => c.body.includes(bot_comment_identifier)); - - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number, - body, - }); - } - - log: - needs: sanitize - if: github.event_name == 'push' - runs-on: ubuntu-latest - - permissions: {} - - steps: - - name: Download sanitized artifact - uses: actions/download-artifact@v7 - with: - name: sanitized-output - - - name: Log publish info - uses: actions/github-script@v8 - with: - script: | - const fs = require('fs'); - const { packages } = JSON.parse(fs.readFileSync('sanitized-output.json', 'utf8')); - - if (packages.length === 0) { - console.log('No valid packages found.'); - return; - } - - console.log('\n' + '='.repeat(50)); - console.log('Publish Information'); - console.log('='.repeat(50)); - for (const p of packages) { - console.log(`${p.name} - pnpm add https://pkg.pr.new/${p.name}@${p.sha}`); - } - const svelte = packages.find(p => p.name === 'svelte'); - if (svelte) { - console.log(`\nPlayground: https://svelte.dev/playground?version=commit-${svelte.sha}`); - } - console.log('='.repeat(50)); diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 359fcb7eea..12fe17582e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,13 +23,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 24.x cache: pnpm diff --git a/package.json b/package.json index 0d2fbc7fd6..a178d31698 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "private": true, "type": "module", "license": "MIT", - "packageManager": "pnpm@10.4.0", + "packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800", "engines": { "pnpm": ">=9.0.0" }, diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f94dac7cc7..8c81497078 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,15 @@ +minimumReleaseAge: 2880 +minimumReleaseAgeExclude: + - '@sveltejs/*' + - svelte + - esrap + - devalue + - zimmerframe + - prettier-plugin-svelte + - svelte-check + - esm-env +blockExoticSubdeps: true + packages: - 'packages/*' - 'playgrounds/*' From 44a7813730579b94004e182e5a67aab27aa9d2a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:04:41 +0200 Subject: [PATCH 33/33] Version Packages (#18572) 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.8 ### Patch Changes - fix: call `onerror` and provide a working `reset` when hydrating a failed boundary ([#18556](https://github.com/sveltejs/svelte/pull/18556)) - fix: preserve select selection when spread attributes omit value ([#18561](https://github.com/sveltejs/svelte/pull/18561)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/hydrated-boundary-reset.md | 5 ----- .changeset/tidy-select-spreads.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/hydrated-boundary-reset.md delete mode 100644 .changeset/tidy-select-spreads.md diff --git a/.changeset/hydrated-boundary-reset.md b/.changeset/hydrated-boundary-reset.md deleted file mode 100644 index 799a57fd9b..0000000000 --- a/.changeset/hydrated-boundary-reset.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: call `onerror` and provide a working `reset` when hydrating a failed boundary diff --git a/.changeset/tidy-select-spreads.md b/.changeset/tidy-select-spreads.md deleted file mode 100644 index 3b14dc5d34..0000000000 --- a/.changeset/tidy-select-spreads.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: preserve select selection when spread attributes omit value diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index a9b93f5341..722fc21052 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.56.8 + +### Patch Changes + +- fix: call `onerror` and provide a working `reset` when hydrating a failed boundary ([#18556](https://github.com/sveltejs/svelte/pull/18556)) + +- fix: preserve select selection when spread attributes omit value ([#18561](https://github.com/sveltejs/svelte/pull/18561)) + ## 5.56.7 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 67f349be71..82dd4faf61 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.56.7", + "version": "5.56.8", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index e9737ec13c..8fb86f3398 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.56.7'; +export const VERSION = '5.56.8'; export const PUBLIC_VERSION = '5';