From bb33c555b49b27ecd01afc742a7cf257b494b0b5 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 26 Sep 2025 20:46:26 +0200 Subject: [PATCH 01/30] chore: bump some dev deps, harden github actions (#16851) --- .github/workflows/ecosystem-ci-trigger.yml | 2 + .github/workflows/pkg.pr.new.yml | 2 + package.json | 2 +- packages/svelte/package.json | 2 +- pnpm-lock.yaml | 442 +++++++++------------ 5 files changed, 194 insertions(+), 256 deletions(-) diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index 7753b606e1..aa08df2f84 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -4,6 +4,8 @@ on: issue_comment: types: [created] +permissions: {} + jobs: trigger: runs-on: ubuntu-latest diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml index b1ba217e5a..49303f1684 100644 --- a/.github/workflows/pkg.pr.new.yml +++ b/.github/workflows/pkg.pr.new.yml @@ -1,6 +1,8 @@ name: Publish Any Commit on: [push, pull_request] +permissions: {} + jobs: build: permissions: {} diff --git a/package.json b/package.json index df882141ac..ad60494bf2 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "bench:debug": "node --allow-natives-syntax --inspect-brk ./benchmarking/run.js" }, "devDependencies": { - "@changesets/cli": "^2.27.8", + "@changesets/cli": "^2.29.7", "@sveltejs/eslint-config": "^8.3.3", "@svitejs/changesets-changelog-github-compact": "^1.1.0", "@types/node": "^20.11.5", diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 51c6a4f12e..41a32f4a79 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -158,7 +158,7 @@ "@types/aria-query": "^5.0.4", "@types/node": "^20.11.5", "dts-buddy": "^0.5.5", - "esbuild": "^0.21.5", + "esbuild": "^0.25.10", "rollup": "^4.22.4", "source-map": "^0.7.4", "tinyglobby": "^0.2.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39a504e504..f585619252 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@changesets/cli': - specifier: ^2.27.8 - version: 2.27.8 + specifier: ^2.29.7 + version: 2.29.7(@types/node@20.19.17) '@sveltejs/eslint-config': specifier: ^8.3.3 version: 8.3.3(@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1))(eslint-config-prettier@9.1.0(eslint@9.9.1))(eslint-plugin-n@17.16.1(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-svelte@3.11.0(eslint@9.9.1)(svelte@packages+svelte))(eslint@9.9.1)(typescript-eslint@8.26.0(eslint@9.9.1)(typescript@5.5.4))(typescript@5.5.4) @@ -19,13 +19,13 @@ importers: version: 1.1.0 '@types/node': specifier: ^20.11.5 - version: 20.12.7 + version: 20.19.17 '@types/picomatch': specifier: ^4.0.2 version: 4.0.2 '@vitest/coverage-v8': specifier: ^2.1.9 - version: 2.1.9(vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + version: 2.1.9(vitest@2.1.9(@types/node@20.19.17)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) eslint: specifier: ^9.9.1 version: 9.9.1 @@ -61,7 +61,7 @@ importers: version: 1.2.5 vitest: specifier: ^2.1.9 - version: 2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + version: 2.1.9(@types/node@20.19.17)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) packages/svelte: dependencies: @@ -110,7 +110,7 @@ importers: devDependencies: '@jridgewell/trace-mapping': specifier: ^0.3.25 - version: 0.3.25 + version: 0.3.31 '@playwright/test': specifier: ^1.46.1 version: 1.46.1 @@ -131,13 +131,13 @@ importers: version: 5.0.4 '@types/node': specifier: ^20.11.5 - version: 20.12.7 + version: 20.19.17 dts-buddy: specifier: ^0.5.5 version: 0.5.5(typescript@5.5.4) esbuild: - specifier: ^0.21.5 - version: 0.21.5 + specifier: ^0.25.10 + version: 0.25.10 rollup: specifier: ^4.22.4 version: 4.50.1 @@ -152,7 +152,7 @@ importers: version: 5.5.4 vitest: specifier: ^2.1.9 - version: 2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + version: 2.1.9(@types/node@20.19.17)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) playgrounds/sandbox: devDependencies: @@ -215,63 +215,63 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@changesets/apply-release-plan@7.0.5': - resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} + '@changesets/apply-release-plan@7.0.13': + resolution: {integrity: sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg==} - '@changesets/assemble-release-plan@6.0.4': - resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==} + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} - '@changesets/changelog-git@0.2.0': - resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} - '@changesets/cli@2.27.8': - resolution: {integrity: sha512-gZNyh+LdSsI82wBSHLQ3QN5J30P4uHKJ4fXgoGwQxfXwYFTJzDdvIJasZn8rYQtmKhyQuiBj4SSnLuKlxKWq4w==} + '@changesets/cli@2.29.7': + resolution: {integrity: sha512-R7RqWoaksyyKXbKXBTbT4REdy22yH81mcFK6sWtqSanxUCbUi9Uf+6aqxZtDQouIqPdem2W56CdxXgsxdq7FLQ==} hasBin: true - '@changesets/config@3.0.3': - resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==} + '@changesets/config@3.1.1': + resolution: {integrity: sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - '@changesets/get-dependents-graph@2.1.2': - resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} + '@changesets/get-dependents-graph@2.1.3': + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} '@changesets/get-github-info@0.5.2': resolution: {integrity: sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==} - '@changesets/get-release-plan@4.0.4': - resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==} + '@changesets/get-release-plan@4.0.13': + resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - '@changesets/git@3.0.1': - resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==} + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} '@changesets/logger@0.1.1': resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} - '@changesets/parse@0.4.0': - resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} + '@changesets/parse@0.4.1': + resolution: {integrity: sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==} - '@changesets/pre@2.0.1': - resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} - '@changesets/read@0.6.1': - resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==} + '@changesets/read@0.6.5': + resolution: {integrity: sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==} - '@changesets/should-skip-package@0.1.1': - resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} '@changesets/types@4.1.0': resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} - '@changesets/types@6.0.0': - resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} - '@changesets/write@0.3.2': - resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} @@ -601,6 +601,15 @@ packages: resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} engines: {node: '>=18.18'} + '@inquirer/external-editor@1.0.2': + resolution: {integrity: sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -630,8 +639,8 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -869,8 +878,8 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@20.12.7': - resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} + '@types/node@20.19.17': + resolution: {integrity: sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==} '@types/node@24.5.2': resolution: {integrity: sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==} @@ -881,9 +890,6 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - '@types/semver@7.5.6': - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} - '@typescript-eslint/eslint-plugin@8.26.0': resolution: {integrity: sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1115,16 +1121,16 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - chai@5.1.2: - resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} - engines: {node: '>=12'} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + chardet@2.1.0: + resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} @@ -1162,11 +1168,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} - - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} cssesc@3.0.0: @@ -1271,8 +1274,8 @@ packages: error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - es-module-lexer@1.6.0: - resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} @@ -1388,17 +1391,13 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - expect-type@1.1.0: - resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1536,17 +1535,18 @@ packages: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} - human-id@1.0.2: - resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + human-id@4.1.1: + resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==} + hasBin: true iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1767,15 +1767,12 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -1859,10 +1856,6 @@ packages: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -2020,9 +2013,6 @@ packages: engines: {node: '>=14'} hasBin: true - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2109,18 +2099,10 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -2128,9 +2110,6 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2161,8 +2140,8 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} - spawndamnit@2.0.0: - resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -2170,8 +2149,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.8.0: - resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -2247,8 +2226,8 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@1.0.2: - resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} tinyrainbow@1.2.0: @@ -2266,10 +2245,6 @@ packages: resolution: {integrity: sha512-ph4AE5BXWIOsSy9stpoeo7bYe/Cy7VfpciIH4RhVZUPItCJmhqWCN0EVzxd8BOHiyNb42vuJc6NWTjJkg91Tuw==} hasBin: true - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -2330,8 +2305,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} undici-types@7.12.0: resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==} @@ -2518,10 +2493,6 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2563,9 +2534,6 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -2584,7 +2552,7 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 '@babel/helper-string-parser@7.24.8': {} @@ -2606,13 +2574,13 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@changesets/apply-release-plan@7.0.5': + '@changesets/apply-release-plan@7.0.13': dependencies: - '@changesets/config': 3.0.3 + '@changesets/config': 3.1.1 '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.1 - '@changesets/should-skip-package': 0.1.1 - '@changesets/types': 6.0.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 detect-indent: 6.1.0 fs-extra: 7.0.1 @@ -2622,58 +2590,58 @@ snapshots: resolve-from: 5.0.0 semver: 7.7.2 - '@changesets/assemble-release-plan@6.0.4': + '@changesets/assemble-release-plan@6.0.9': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 - '@changesets/should-skip-package': 0.1.1 - '@changesets/types': 6.0.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 semver: 7.7.2 - '@changesets/changelog-git@0.2.0': + '@changesets/changelog-git@0.2.1': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 - '@changesets/cli@2.27.8': + '@changesets/cli@2.29.7(@types/node@20.19.17)': dependencies: - '@changesets/apply-release-plan': 7.0.5 - '@changesets/assemble-release-plan': 6.0.4 - '@changesets/changelog-git': 0.2.0 - '@changesets/config': 3.0.3 + '@changesets/apply-release-plan': 7.0.13 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.1 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 - '@changesets/get-release-plan': 4.0.4 - '@changesets/git': 3.0.1 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.13 + '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 - '@changesets/pre': 2.0.1 - '@changesets/read': 0.6.1 - '@changesets/should-skip-package': 0.1.1 - '@changesets/types': 6.0.0 - '@changesets/write': 0.3.2 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.5 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.2(@types/node@20.19.17) '@manypkg/get-packages': 1.1.3 - '@types/semver': 7.5.6 ansi-colors: 4.1.3 ci-info: 3.9.0 enquirer: 2.4.1 - external-editor: 3.1.0 fs-extra: 7.0.1 mri: 1.2.0 - outdent: 0.5.0 p-limit: 2.3.0 package-manager-detector: 0.2.0 picocolors: 1.1.1 resolve-from: 5.0.0 semver: 7.7.2 - spawndamnit: 2.0.0 + spawndamnit: 3.0.1 term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' - '@changesets/config@3.0.3': + '@changesets/config@3.1.1': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 + '@changesets/get-dependents-graph': 2.1.3 '@changesets/logger': 0.1.1 - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 micromatch: 4.0.8 @@ -2682,9 +2650,9 @@ snapshots: dependencies: extendable-error: 0.1.7 - '@changesets/get-dependents-graph@2.1.2': + '@changesets/get-dependents-graph@2.1.3': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 picocolors: 1.1.1 semver: 7.7.2 @@ -2696,65 +2664,65 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/get-release-plan@4.0.4': + '@changesets/get-release-plan@4.0.13': dependencies: - '@changesets/assemble-release-plan': 6.0.4 - '@changesets/config': 3.0.3 - '@changesets/pre': 2.0.1 - '@changesets/read': 0.6.1 - '@changesets/types': 6.0.0 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.5 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 '@changesets/get-version-range-type@0.4.0': {} - '@changesets/git@3.0.1': + '@changesets/git@3.0.4': dependencies: '@changesets/errors': 0.2.0 '@manypkg/get-packages': 1.1.3 is-subdir: 1.2.0 micromatch: 4.0.8 - spawndamnit: 2.0.0 + spawndamnit: 3.0.1 '@changesets/logger@0.1.1': dependencies: picocolors: 1.1.1 - '@changesets/parse@0.4.0': + '@changesets/parse@0.4.1': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 js-yaml: 3.14.1 - '@changesets/pre@2.0.1': + '@changesets/pre@2.0.2': dependencies: '@changesets/errors': 0.2.0 - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.6.1': + '@changesets/read@0.6.5': dependencies: - '@changesets/git': 3.0.1 + '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 - '@changesets/parse': 0.4.0 - '@changesets/types': 6.0.0 + '@changesets/parse': 0.4.1 + '@changesets/types': 6.1.0 fs-extra: 7.0.1 p-filter: 2.1.0 picocolors: 1.1.1 - '@changesets/should-skip-package@0.1.1': + '@changesets/should-skip-package@0.1.2': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 '@changesets/types@4.1.0': {} - '@changesets/types@6.0.0': {} + '@changesets/types@6.1.0': {} - '@changesets/write@0.3.2': + '@changesets/write@0.4.0': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 fs-extra: 7.0.1 - human-id: 1.0.2 + human-id: 4.1.1 prettier: 2.8.8 '@esbuild/aix-ppc64@0.21.5': @@ -2941,6 +2909,13 @@ snapshots: '@humanwhocodes/retry@0.3.0': {} + '@inquirer/external-editor@1.0.2(@types/node@20.19.17)': + dependencies: + chardet: 2.1.0 + iconv-lite: 0.7.0 + optionalDependencies: + '@types/node': 20.19.17 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2956,12 +2931,12 @@ snapshots: dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.4': dependencies: '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.1': {} @@ -2970,11 +2945,11 @@ snapshots: '@jridgewell/source-map@0.3.6': dependencies: '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.5.0 @@ -3186,9 +3161,9 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@20.12.7': + '@types/node@20.19.17': dependencies: - undici-types: 5.26.5 + undici-types: 6.21.0 '@types/node@24.5.2': dependencies: @@ -3198,8 +3173,6 @@ snapshots: '@types/resolve@1.20.2': {} - '@types/semver@7.5.6': {} - '@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -3329,7 +3302,7 @@ snapshots: '@typescript-eslint/types': 8.43.0 eslint-visitor-keys: 4.2.1 - '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': + '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.19.17)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -3340,10 +3313,10 @@ snapshots: istanbul-reports: 3.1.7 magic-string: 0.30.17 magicast: 0.3.5 - std-env: 3.8.0 + std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vitest: 2.1.9(@types/node@20.19.17)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - supports-color @@ -3351,16 +3324,16 @@ snapshots: dependencies: '@vitest/spy': 2.1.9 '@vitest/utils': 2.1.9 - chai: 5.1.2 + chai: 5.3.3 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': + '@vitest/mocker@2.1.9(vite@5.4.20(@types/node@20.19.17)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': dependencies: '@vitest/spy': 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.20(@types/node@20.19.17)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) '@vitest/pretty-format@2.1.9': dependencies: @@ -3384,7 +3357,7 @@ snapshots: '@vitest/utils@2.1.9': dependencies: '@vitest/pretty-format': 2.1.9 - loupe: 3.1.3 + loupe: 3.2.1 tinyrainbow: 1.2.0 acorn-jsx@5.3.2(acorn@8.15.0): @@ -3476,12 +3449,12 @@ snapshots: callsites@3.1.0: {} - chai@5.1.2: + chai@5.3.3: dependencies: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.3 + loupe: 3.2.1 pathval: 2.0.0 chalk@4.1.2: @@ -3489,7 +3462,7 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chardet@0.7.0: {} + chardet@2.1.0: {} check-error@2.1.1: {} @@ -3526,13 +3499,7 @@ snapshots: concat-map@0.0.1: {} - cross-spawn@5.1.0: - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 - - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -3617,7 +3584,7 @@ snapshots: error-stack-parser-es@1.0.5: {} - es-module-lexer@1.6.0: {} + es-module-lexer@1.7.0: {} esbuild@0.21.5: optionalDependencies: @@ -3750,7 +3717,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.4.1 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 @@ -3815,16 +3782,10 @@ snapshots: esutils@2.0.3: {} - expect-type@1.1.0: {} + expect-type@1.2.2: {} extendable-error@0.1.7: {} - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -3874,7 +3835,7 @@ snapshots: foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 form-data@4.0.0: @@ -3969,13 +3930,13 @@ snapshots: transitivePeerDependencies: - supports-color - human-id@1.0.2: {} + human-id@4.1.1: {} - iconv-lite@0.4.24: + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.6.3: + iconv-lite@0.7.0: dependencies: safer-buffer: 2.1.2 @@ -4052,7 +4013,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 debug: 4.4.1 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: @@ -4187,15 +4148,10 @@ snapshots: lodash.startcase@4.4.0: {} - loupe@3.1.3: {} + loupe@3.2.1: {} lru-cache@10.4.3: {} - lru-cache@4.1.5: - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -4270,8 +4226,6 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - os-tmpdir@1.0.2: {} - outdent@0.5.0: {} p-filter@2.1.0: @@ -4389,8 +4343,6 @@ snapshots: prettier@3.2.4: {} - pseudomap@1.0.2: {} - punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -4489,22 +4441,14 @@ snapshots: dependencies: randombytes: 2.1.0 - shebang-command@1.2.0: - dependencies: - shebang-regex: 1.0.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} siginfo@2.0.0: {} - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} sirv@3.0.2: @@ -4528,16 +4472,16 @@ snapshots: source-map@0.7.4: {} - spawndamnit@2.0.0: + spawndamnit@3.0.1: dependencies: - cross-spawn: 5.1.0 - signal-exit: 3.0.7 + cross-spawn: 7.0.6 + signal-exit: 4.1.0 sprintf-js@1.0.3: {} stackback@0.0.2: {} - std-env@3.8.0: {} + std-env@3.9.0: {} string-width@4.2.3: dependencies: @@ -4610,7 +4554,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@1.0.2: {} + tinypool@1.1.1: {} tinyrainbow@1.2.0: {} @@ -4622,10 +4566,6 @@ snapshots: dependencies: tldts-core: 6.1.64 - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 - to-fast-properties@2.0.0: {} to-regex-range@5.0.1: @@ -4677,7 +4617,7 @@ snapshots: typescript@5.5.4: {} - undici-types@5.26.5: {} + undici-types@6.21.0: {} undici-types@7.12.0: {} @@ -4708,13 +4648,13 @@ snapshots: dependencies: vite: 7.1.5(@types/node@24.5.2)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) - vite-node@2.1.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): + vite-node@2.1.9(@types/node@20.19.17)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: cac: 6.7.14 debug: 4.4.1 - es-module-lexer: 1.6.0 + es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.20(@types/node@20.19.17)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - '@types/node' - less @@ -4746,13 +4686,13 @@ snapshots: transitivePeerDependencies: - supports-color - vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): + vite@5.4.20(@types/node@20.19.17)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: esbuild: 0.21.5 postcss: 8.5.6 rollup: 4.50.1 optionalDependencies: - '@types/node': 20.12.7 + '@types/node': 20.19.17 fsevents: 2.3.3 lightningcss: 1.23.0 sass: 1.70.0 @@ -4777,30 +4717,30 @@ snapshots: optionalDependencies: vite: 7.1.5(@types/node@24.5.2)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) - vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): + vitest@2.1.9(@types/node@20.19.17)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@20.19.17)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 '@vitest/spy': 2.1.9 '@vitest/utils': 2.1.9 - chai: 5.1.2 + chai: 5.3.3 debug: 4.4.1 - expect-type: 1.1.0 + expect-type: 1.2.2 magic-string: 0.30.17 pathe: 1.1.2 - std-env: 3.8.0 + std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinypool: 1.0.2 + tinypool: 1.1.1 tinyrainbow: 1.2.0 - vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) - vite-node: 2.1.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.20(@types/node@20.19.17)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite-node: 2.1.9(@types/node@20.19.17)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.12.7 + '@types/node': 20.19.17 jsdom: 25.0.1 transitivePeerDependencies: - less @@ -4837,10 +4777,6 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -4872,8 +4808,6 @@ snapshots: xmlchars@2.2.0: {} - yallist@2.1.2: {} - yaml@1.10.2: {} yocto-queue@0.1.0: {} From e0dba165b49d4489a1e622dd5bdededd32fa3d1a Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 28 Sep 2025 17:38:18 -0400 Subject: [PATCH 02/30] docs: remove reference to SSR being synchronous (#16861) --- documentation/docs/03-template-syntax/19-await-expressions.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/documentation/docs/03-template-syntax/19-await-expressions.md b/documentation/docs/03-template-syntax/19-await-expressions.md index d3896a5c4e..1c613af870 100644 --- a/documentation/docs/03-template-syntax/19-await-expressions.md +++ b/documentation/docs/03-template-syntax/19-await-expressions.md @@ -139,8 +139,6 @@ If a `` with a `pending` snippet is encountered during SSR, tha As an experimental feature, the details of how `await` is handled (and related APIs like `$effect.pending()`) are subject to breaking changes outside of a semver major release, though we intend to keep such changes to a bare minimum. -Currently, server-side rendering is synchronous. If a `` with a `pending` snippet is encountered during SSR, only the `pending` snippet will be rendered. - ## Breaking changes Effects run in a slightly different order when the `experimental.async` option is `true`. Specifically, _block_ effects like `{#if ...}` and `{#each ...}` now run before an `$effect.pre` or `beforeUpdate` in the same component, which means that in [very rare situations](/playground/untitled?#H4sIAAAAAAAAE22R3VLDIBCFX2WLvUhnTHsf0zre-Q7WmfwtFV2BgU1rJ5N3F0jaOuoVcPbw7VkYhK4_URTiGYkMnIyjDjLsFGO3EvdCKkIvipdB8NlGXxSCPt96snbtj0gctab2-J_eGs2oOWBE6VunLO_2es-EDKZ5x5ZhC0vPNWM2gHXGouNzAex6hHH1cPHil_Lsb95YT9VQX6KUAbS2DrNsBdsdDFHe8_XSYjH1SrhELTe3MLpsemajweiWVPuxHSbKNd-8eQTdE0EBf4OOaSg2hwNhhE_ABB_ulJzjj9FULvIcqgm5vnAqUB7wWFMfhuugQWkcAr8hVD-mq8D12kOep24J_IszToOXdveGDsuNnZwbJUNlXsKnhJdhUcTo42s41YpOSneikDV5HL8BktM6yRcCAAA=) it is possible to update a block that should no longer exist, but only if you update state inside an effect, [which you should avoid]($effect#When-not-to-use-$effect). From 8895bd44f5297c0c69b8bc081155fbf9bac9c00e Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 29 Sep 2025 08:04:57 -0700 Subject: [PATCH 03/30] fix: coerce nullish `` to empty string (#16863) --- .changeset/silly-walls-fail.md | 5 +++++ .../3-transform/client/visitors/TitleElement.js | 13 ++++++++++++- .../runtime-runes/samples/title-nullish/_config.js | 7 +++++++ .../runtime-runes/samples/title-nullish/main.svelte | 6 ++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .changeset/silly-walls-fail.md create mode 100644 packages/svelte/tests/runtime-runes/samples/title-nullish/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/title-nullish/main.svelte diff --git a/.changeset/silly-walls-fail.md b/.changeset/silly-walls-fail.md new file mode 100644 index 0000000000..6f364e7f8a --- /dev/null +++ b/.changeset/silly-walls-fail.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: coerce nullish `<title>` to empty string diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js index e6f4202a01..98d7880b25 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js @@ -12,8 +12,19 @@ export function TitleElement(node, context) { /** @type {any} */ (node.fragment.nodes), context ); + const evaluated = context.state.scope.evaluate(value); - const statement = b.stmt(b.assignment('=', b.id('$.document.title'), value)); + const statement = b.stmt( + b.assignment( + '=', + b.id('$.document.title'), + evaluated.is_known + ? b.literal(evaluated.value) + : evaluated.is_defined + ? value + : b.logical('??', value, b.literal('')) + ) + ); if (has_state) { context.state.update.push(statement); diff --git a/packages/svelte/tests/runtime-runes/samples/title-nullish/_config.js b/packages/svelte/tests/runtime-runes/samples/title-nullish/_config.js new file mode 100644 index 0000000000..819d6b620e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/title-nullish/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + assert.equal(target.ownerDocument.title, ''); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/title-nullish/main.svelte b/packages/svelte/tests/runtime-runes/samples/title-nullish/main.svelte new file mode 100644 index 0000000000..f1fa7e7b84 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/title-nullish/main.svelte @@ -0,0 +1,6 @@ +<script> + const thing = {}; +</script> +<svelte:head> + <title>{thing.thing} + \ No newline at end of file From 87f7e979635baa46acb594ecc7f2822920cb7aaf Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 29 Sep 2025 08:05:13 -0700 Subject: [PATCH 04/30] fix: wrap async `children` in `$$renderer.async` (#16862) --- .changeset/rude-terms-confess.md | 5 +++++ .../phases/3-transform/server/visitors/shared/component.js | 7 ++++++- .../samples/async-children/_expected.html | 1 + .../samples/async-children/component.svelte | 4 ++++ .../samples/async-children/main.svelte | 7 +++++++ 5 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 .changeset/rude-terms-confess.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-children/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-children/component.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-children/main.svelte diff --git a/.changeset/rude-terms-confess.md b/.changeset/rude-terms-confess.md new file mode 100644 index 0000000000..a24774952e --- /dev/null +++ b/.changeset/rude-terms-confess.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: wrap async `children` in `$$renderer.async` diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js index d0de750c0e..e463ea785a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js @@ -242,7 +242,12 @@ export function build_inline_component(node, expression, context) { params.push(pattern); } - const slot_fn = b.arrow(params, b.block(block.body)); + const slot_fn = b.arrow( + params, + node.fragment.metadata.has_await + ? b.block([create_async_block(b.block(block.body))]) + : b.block(block.body) + ); if (slot_name === 'default' && !has_children_prop) { if ( diff --git a/packages/svelte/tests/server-side-rendering/samples/async-children/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-children/_expected.html new file mode 100644 index 0000000000..04d9709792 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-children/_expected.html @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-children/component.svelte b/packages/svelte/tests/server-side-rendering/samples/async-children/component.svelte new file mode 100644 index 0000000000..73b5a2f823 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-children/component.svelte @@ -0,0 +1,4 @@ + +{@render children()} \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-children/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-children/main.svelte new file mode 100644 index 0000000000..ae0fc571b3 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-children/main.svelte @@ -0,0 +1,7 @@ + + + {@const one = await 1} + {one} + \ No newline at end of file From 25cbdc8cb1c672cfa0ce506e9d432cf5d86863e6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Sep 2025 09:57:22 -0400 Subject: [PATCH 05/30] fix: merge batches (#16866) whenever a batch is committed, we essentially 'rebase' other pending batches on top of the newly applied state --- .changeset/lemon-cars-count.md | 5 + .../src/internal/client/reactivity/batch.js | 206 +++++++++++------- .../internal/client/reactivity/deriveds.js | 46 ++-- .../samples/async-derived-module/_config.js | 11 - .../runtime-runes/samples/async-if/_config.js | 30 ++- .../samples/async-if/main.svelte | 21 +- .../_config.js | 12 +- .../main.svelte | 21 +- 8 files changed, 208 insertions(+), 144 deletions(-) create mode 100644 .changeset/lemon-cars-count.md diff --git a/.changeset/lemon-cars-count.md b/.changeset/lemon-cars-count.md new file mode 100644 index 0000000000..5724e48460 --- /dev/null +++ b/.changeset/lemon-cars-count.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: rebase pending batches when other batches are committed diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 8cf0e4bd9d..fb704edb13 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -1,4 +1,4 @@ -/** @import { Derived, Effect, Source } from '#client' */ +/** @import { Derived, Effect, Source, Value } from '#client' */ import { BLOCK_EFFECT, BRANCH_EFFECT, @@ -10,10 +10,11 @@ import { INERT, RENDER_EFFECT, ROOT_EFFECT, - MAYBE_DIRTY + MAYBE_DIRTY, + DERIVED } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; -import { deferred, define_property } from '../../shared/utils.js'; +import { deferred, define_property, noop } from '../../shared/utils.js'; import { active_effect, is_dirty, @@ -97,22 +98,8 @@ export class Batch { #deferred = null; /** - * True if an async effect inside this batch resolved and - * its parent branch was already deleted - */ - #neutered = false; - - /** - * Async effects (created inside `async_derived`) encountered during processing. - * These run after the rest of the batch has updated, since they should - * always have the latest values - * @type {Effect[]} - */ - #async_effects = []; - - /** - * The same as `#async_effects`, but for effects inside a newly-created - * `` — these do not prevent the batch from committing + * Async effects inside a newly-created `` + * — these do not prevent the batch from committing * @type {Effect[]} */ #boundary_async_effects = []; @@ -165,32 +152,7 @@ export class Batch { previous_batch = null; - /** @type {Map | null} */ - var current_values = null; - - // if there are multiple batches, we are 'time travelling' — - // we need to undo the changes belonging to any batch - // other than the current one - if (async_mode_flag && batches.size > 1) { - current_values = new Map(); - batch_deriveds = new Map(); - - for (const [source, current] of this.current) { - current_values.set(source, { v: source.v, wv: source.wv }); - source.v = current; - } - - for (const batch of batches) { - if (batch === this) continue; - - for (const [source, previous] of batch.#previous) { - if (!current_values.has(source)) { - current_values.set(source, { v: source.v, wv: source.wv }); - source.v = previous; - } - } - } - } + var revert = Batch.apply(this); for (const root of root_effects) { this.#traverse_effect_tree(root); @@ -198,7 +160,7 @@ export class Batch { // if we didn't start any new async work, and no async work // is outstanding from a previous flush, commit - if (this.#async_effects.length === 0 && this.#pending === 0) { + if (this.#pending === 0) { this.#commit(); var render_effects = this.#render_effects; @@ -210,7 +172,7 @@ export class Batch { // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with // newly updated sources, which could lead to infinite loops when effects run over and over again. - previous_batch = current_batch; + previous_batch = this; current_batch = null; flush_queued_effects(render_effects); @@ -223,27 +185,12 @@ export class Batch { this.#defer_effects(this.#block_effects); } - if (current_values) { - for (const [source, { v, wv }] of current_values) { - // reset the source to the current value (unless - // it got a newer value as a result of effects running) - if (source.wv <= wv) { - source.v = v; - } - } - - batch_deriveds = null; - } - - for (const effect of this.#async_effects) { - update_effect(effect); - } + revert(); for (const effect of this.#boundary_async_effects) { update_effect(effect); } - this.#async_effects = []; this.#boundary_async_effects = []; } @@ -272,12 +219,8 @@ export class Batch { } else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) { this.#render_effects.push(effect); } else if ((flags & CLEAN) === 0) { - if ((flags & ASYNC) !== 0) { - var effects = effect.b?.is_pending() - ? this.#boundary_async_effects - : this.#async_effects; - - effects.push(effect); + if ((flags & ASYNC) !== 0 && effect.b?.is_pending()) { + this.#boundary_async_effects.push(effect); } else if (is_dirty(effect)) { if ((effect.f & BLOCK_EFFECT) !== 0) this.#block_effects.push(effect); update_effect(effect); @@ -350,10 +293,6 @@ export class Batch { } } - neuter() { - this.#neutered = true; - } - flush() { if (queued_root_effects.length > 0) { this.activate(); @@ -374,13 +313,58 @@ export class Batch { * Append and remove branches to/from the DOM */ #commit() { - if (!this.#neutered) { - for (const fn of this.#callbacks) { - fn(); - } + for (const fn of this.#callbacks) { + fn(); } this.#callbacks.clear(); + + // If there are other pending batches, they now need to be 'rebased' — + // in other words, we re-run block/async effects with the newly + // committed state, unless the batch in question has a more + // recent value for a given source + if (batches.size > 1) { + this.#previous.clear(); + + let is_earlier = true; + + for (const batch of batches) { + if (batch === this) { + is_earlier = false; + continue; + } + + for (const [source, value] of this.current) { + if (batch.current.has(source)) { + if (is_earlier) { + // bring the value up to date + batch.current.set(source, value); + } else { + // later batch has more recent value, + // no need to re-run these effects + continue; + } + } + + mark_effects(source); + } + + if (queued_root_effects.length > 0) { + current_batch = batch; + const revert = Batch.apply(batch); + + for (const root of queued_root_effects) { + batch.#traverse_effect_tree(root); + } + + queued_root_effects = []; + revert(); + } + } + + current_batch = null; + } + batches.delete(this); } @@ -402,9 +386,6 @@ export class Batch { schedule_effect(e); } - this.#render_effects = []; - this.#effects = []; - this.flush(); } else { this.deactivate(); @@ -444,6 +425,51 @@ export class Batch { static enqueue(task) { queue_micro_task(task); } + + /** + * @param {Batch} current_batch + */ + static apply(current_batch) { + if (!async_mode_flag || batches.size === 1) { + return noop; + } + + // if there are multiple batches, we are 'time travelling' — + // we need to undo the changes belonging to any batch + // other than the current one + + /** @type {Map} */ + var current_values = new Map(); + batch_deriveds = new Map(); + + for (const [source, current] of current_batch.current) { + current_values.set(source, { v: source.v, wv: source.wv }); + source.v = current; + } + + for (const batch of batches) { + if (batch === current_batch) continue; + + for (const [source, previous] of batch.#previous) { + if (!current_values.has(source)) { + current_values.set(source, { v: source.v, wv: source.wv }); + source.v = previous; + } + } + } + + return () => { + for (const [source, { v, wv }] of current_values) { + // reset the source to the current value (unless + // it got a newer value as a result of effects running) + if (source.wv <= wv) { + source.v = v; + } + } + + batch_deriveds = null; + }; + } } /** @@ -615,6 +641,26 @@ function flush_queued_effects(effects) { eager_block_effects = null; } +/** + * This is similar to `mark_reactions`, but it only marks async/block effects + * so that these can re-run after another batch has been committed + * @param {Value} value + */ +function mark_effects(value) { + if (value.reactions !== null) { + for (const reaction of value.reactions) { + const flags = reaction.f; + + if ((flags & DERIVED) !== 0) { + mark_effects(/** @type {Derived} */ (reaction)); + } else if ((flags & (ASYNC | BLOCK_EFFECT)) !== 0) { + set_signal_status(reaction, DIRTY); + schedule_effect(/** @type {Effect} */ (reaction)); + } + } + } +} + /** * @param {Effect} signal * @returns {void} diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 11405a8e66..5d5976a6c1 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -26,7 +26,7 @@ import { import { equals, safe_equals } from './equality.js'; import * as e from '../errors.js'; import * as w from '../warnings.js'; -import { async_effect, destroy_effect } from './effects.js'; +import { async_effect, destroy_effect, teardown } from './effects.js'; import { inspect_effects, internal_set, set_inspect_effects, source } from './sources.js'; import { get_stack } from '../dev/tracing.js'; import { async_mode_flag, tracing_mode_flag } from '../../flags/index.js'; @@ -35,6 +35,7 @@ import { component_context } from '../context.js'; import { UNINITIALIZED } from '../../../constants.js'; import { batch_deriveds, current_batch } from './batch.js'; import { unset_context } from './async.js'; +import { deferred } from '../../shared/utils.js'; /** @type {Effect | null} */ export let current_async_effect = null; @@ -109,37 +110,40 @@ export function async_derived(fn, location) { var promise = /** @type {Promise} */ (/** @type {unknown} */ (undefined)); var signal = source(/** @type {V} */ (UNINITIALIZED)); - /** @type {Promise | null} */ - var prev = null; - // only suspend in async deriveds created on initialisation var should_suspend = !active_reaction; + /** @type {Map>>} */ + var deferreds = new Map(); + async_effect(() => { if (DEV) current_async_effect = active_effect; + /** @type {ReturnType>} */ + var d = deferred(); + promise = d.promise; + try { - var p = fn(); - // Make sure to always access the then property to read any signals - // it might access, so that we track them as dependencies. - if (prev) Promise.resolve(p).catch(() => {}); // avoid unhandled rejection + // If this code is changed at some point, make sure to still access the then property + // of fn() to read any signals it might access, so that we track them as dependencies. + Promise.resolve(fn()).then(d.resolve, d.reject); } catch (error) { - p = Promise.reject(error); + d.reject(error); } if (DEV) current_async_effect = null; - var r = () => p; - promise = prev?.then(r, r) ?? Promise.resolve(p); - - prev = promise; - var batch = /** @type {Batch} */ (current_batch); var pending = boundary.is_pending(); if (should_suspend) { boundary.update_pending_count(1); - if (!pending) batch.increment(); + if (!pending) { + batch.increment(); + + deferreds.get(batch)?.reject(STALE_REACTION); + deferreds.set(batch, d); + } } /** @@ -147,8 +151,6 @@ export function async_derived(fn, location) { * @param {unknown} error */ const handler = (value, error = undefined) => { - prev = null; - current_async_effect = null; if (!pending) batch.activate(); @@ -187,12 +189,12 @@ export function async_derived(fn, location) { unset_context(); }; - promise.then(handler, (e) => handler(null, e || 'unknown')); + d.promise.then(handler, (e) => handler(null, e || 'unknown')); + }); - if (batch) { - return () => { - queueMicrotask(() => batch.neuter()); - }; + teardown(() => { + for (const d of deferreds.values()) { + d.reject(STALE_REACTION); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-module/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-module/_config.js index f7d1d28fde..318f88bcc9 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-derived-module/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-module/_config.js @@ -14,17 +14,6 @@ export default test({ const [reset, a, b, increment] = target.querySelectorAll('button'); a.click(); - - // TODO why is this necessary? why isn't `await tick()` enough? - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - flushSync(); await tick(); assert.htmlEqual( target.innerHTML, diff --git a/packages/svelte/tests/runtime-runes/samples/async-if/_config.js b/packages/svelte/tests/runtime-runes/samples/async-if/_config.js index 3cd67952c3..ef119d601d 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-if/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-if/_config.js @@ -2,30 +2,46 @@ import { tick } from 'svelte'; import { test } from '../../test'; export default test({ - html: `

pending

`, + html: `

pending

`, async test({ assert, target }) { - const [reset, t, f] = target.querySelectorAll('button'); + const [shift, t, f] = target.querySelectorAll('button'); + + shift.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + '

yes

' + ); + + f.click(); + await tick(); t.click(); await tick(); + + f.click(); + await tick(); + + shift.click(); + await tick(); assert.htmlEqual( target.innerHTML, - '

yes

' + '

no

' ); - reset.click(); + shift.click(); await tick(); assert.htmlEqual( target.innerHTML, - '

yes

' + '

yes

' ); - f.click(); + shift.click(); await tick(); assert.htmlEqual( target.innerHTML, - '

no

' + '

no

' ); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-if/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-if/main.svelte index 21a4cbef97..ed708354a4 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-if/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-if/main.svelte @@ -1,13 +1,24 @@ - - - + + + - {#if await deferred.promise} + {#if await push(condition)}

yes

{:else}

no

diff --git a/packages/svelte/tests/runtime-runes/samples/async-linear-order-same-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/async-linear-order-same-derived/_config.js index 5e522ebdb5..cc7b2756fa 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-linear-order-same-derived/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-linear-order-same-derived/_config.js @@ -3,26 +3,24 @@ import { test } from '../../test'; export default test({ async test({ assert, target }) { - const [a, b, reset1, reset2, resolve1, resolve2] = target.querySelectorAll('button'); + const [a, b, shift, pop] = target.querySelectorAll('button'); - resolve1.click(); + shift.click(); await tick(); const p = /** @type {HTMLElement} */ (target.querySelector('#test')); assert.htmlEqual(p.innerHTML, '1 + 2 = 3'); - flushSync(() => reset1.click()); flushSync(() => a.click()); - flushSync(() => reset2.click()); flushSync(() => b.click()); - resolve2.click(); + pop.click(); await tick(); - assert.htmlEqual(p.innerHTML, '1 + 2 = 3'); + assert.htmlEqual(p.innerHTML, '1 + 3 = 4'); - resolve1.click(); + pop.click(); await tick(); assert.htmlEqual(p.innerHTML, '2 + 3 = 5'); diff --git a/packages/svelte/tests/runtime-runes/samples/async-linear-order-same-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-linear-order-same-derived/main.svelte index cc82db0d75..48ff06fece 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-linear-order-same-derived/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-linear-order-same-derived/main.svelte @@ -1,14 +1,14 @@ @@ -16,14 +16,11 @@ - - - - - + + -

{a} + {b} = {await add(a, b)}

+

{a} + {b} = {await push(a, b)}

{#snippet pending()}

loading...

From 46c10d0476c38fae9dff28b812fa476d3202a21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koles=C3=A1r?= <34346635+jakubkolesar@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:46:58 +0200 Subject: [PATCH 06/30] fix: missing title warning for buttons and anchor tags (#16872) * fix(a11y): title warning for buttons and anchor tags * chore(changeset): added changeset * Update playgrounds/sandbox/index.html * Update .changeset/shiny-chicken-occur.md --------- Co-authored-by: 7nik Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/shiny-chicken-occur.md | 5 +++++ .../docs/98-reference/.generated/compile-warnings.md | 2 +- packages/svelte/messages/compile-warnings/a11y.md | 2 +- .../compiler/phases/2-analyze/visitors/shared/a11y/index.js | 5 ++++- packages/svelte/src/compiler/warnings.js | 4 ++-- .../validator/samples/a11y-anchor-has-content/warnings.json | 2 +- .../samples/a11y-consider-explicit-label/input.svelte | 3 +++ .../samples/a11y-consider-explicit-label/warnings.json | 4 ++-- 8 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 .changeset/shiny-chicken-occur.md diff --git a/.changeset/shiny-chicken-occur.md b/.changeset/shiny-chicken-occur.md new file mode 100644 index 0000000000..45a560480b --- /dev/null +++ b/.changeset/shiny-chicken-occur.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: silence label warning for buttons and anchor tags with title attributes diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index 17841b863c..7dfbe75888 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -81,7 +81,7 @@ Coding for the keyboard is important for users with physical disabilities who ca ### a11y_consider_explicit_label ``` -Buttons and links should either contain text or have an `aria-label` or `aria-labelledby` attribute +Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute ``` ### a11y_distracting_elements diff --git a/packages/svelte/messages/compile-warnings/a11y.md b/packages/svelte/messages/compile-warnings/a11y.md index a299fa53bc..bc34829ce9 100644 --- a/packages/svelte/messages/compile-warnings/a11y.md +++ b/packages/svelte/messages/compile-warnings/a11y.md @@ -66,7 +66,7 @@ Coding for the keyboard is important for users with physical disabilities who ca ## a11y_consider_explicit_label -> Buttons and links should either contain text or have an `aria-label` or `aria-labelledby` attribute +> Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute ## a11y_distracting_elements diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js index e2f84290e5..8c5bf55e5f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js @@ -382,7 +382,10 @@ export function check_element(node, context) { } // element-specific checks - const is_labelled = attribute_map.has('aria-label') || attribute_map.has('aria-labelledby'); + const is_labelled = + attribute_map.has('aria-label') || + attribute_map.has('aria-labelledby') || + attribute_map.has('title'); switch (node.name) { case 'a': diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index 8c03e12e71..089cb1e118 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -174,11 +174,11 @@ export function a11y_click_events_have_key_events(node) { } /** - * Buttons and links should either contain text or have an `aria-label` or `aria-labelledby` attribute + * Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute * @param {null | NodeLike} node */ export function a11y_consider_explicit_label(node) { - w(node, 'a11y_consider_explicit_label', `Buttons and links should either contain text or have an \`aria-label\` or \`aria-labelledby\` attribute\nhttps://svelte.dev/e/a11y_consider_explicit_label`); + w(node, 'a11y_consider_explicit_label', `Buttons and links should either contain text or have an \`aria-label\`, \`aria-labelledby\` or \`title\` attribute\nhttps://svelte.dev/e/a11y_consider_explicit_label`); } /** diff --git a/packages/svelte/tests/validator/samples/a11y-anchor-has-content/warnings.json b/packages/svelte/tests/validator/samples/a11y-anchor-has-content/warnings.json index cd3778a443..3d9adc1cde 100644 --- a/packages/svelte/tests/validator/samples/a11y-anchor-has-content/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-anchor-has-content/warnings.json @@ -1,7 +1,7 @@ [ { "code": "a11y_consider_explicit_label", - "message": "Buttons and links should either contain text or have an `aria-label` or `aria-labelledby` attribute", + "message": "Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute", "start": { "line": 1, "column": 0 diff --git a/packages/svelte/tests/validator/samples/a11y-consider-explicit-label/input.svelte b/packages/svelte/tests/validator/samples/a11y-consider-explicit-label/input.svelte index e97951065d..463889dc4f 100644 --- a/packages/svelte/tests/validator/samples/a11y-consider-explicit-label/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-consider-explicit-label/input.svelte @@ -4,6 +4,9 @@ + + + diff --git a/packages/svelte/tests/validator/samples/a11y-consider-explicit-label/warnings.json b/packages/svelte/tests/validator/samples/a11y-consider-explicit-label/warnings.json index 2dcecf08b3..bc29076184 100644 --- a/packages/svelte/tests/validator/samples/a11y-consider-explicit-label/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-consider-explicit-label/warnings.json @@ -1,7 +1,7 @@ [ { "code": "a11y_consider_explicit_label", - "message": "Buttons and links should either contain text or have an `aria-label` or `aria-labelledby` attribute", + "message": "Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute", "start": { "line": 1, "column": 0 @@ -13,7 +13,7 @@ }, { "code": "a11y_consider_explicit_label", - "message": "Buttons and links should either contain text or have an `aria-label` or `aria-labelledby` attribute", + "message": "Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute", "start": { "line": 2, "column": 0 From e9cd45a2daf2de2a62077b1e68019e3cc6f658c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:55:29 +0200 Subject: [PATCH 07/30] Version Packages (#16848) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/clever-turtles-wink.md | 5 ----- .changeset/lemon-cars-count.md | 5 ----- .changeset/rude-terms-confess.md | 5 ----- .changeset/shiny-chicken-occur.md | 5 ----- .changeset/silly-walls-fail.md | 5 ----- packages/svelte/CHANGELOG.md | 14 ++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 8 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 .changeset/clever-turtles-wink.md delete mode 100644 .changeset/lemon-cars-count.md delete mode 100644 .changeset/rude-terms-confess.md delete mode 100644 .changeset/shiny-chicken-occur.md delete mode 100644 .changeset/silly-walls-fail.md diff --git a/.changeset/clever-turtles-wink.md b/.changeset/clever-turtles-wink.md deleted file mode 100644 index 6ff21f4d91..0000000000 --- a/.changeset/clever-turtles-wink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: simplify batch logic diff --git a/.changeset/lemon-cars-count.md b/.changeset/lemon-cars-count.md deleted file mode 100644 index 5724e48460..0000000000 --- a/.changeset/lemon-cars-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: rebase pending batches when other batches are committed diff --git a/.changeset/rude-terms-confess.md b/.changeset/rude-terms-confess.md deleted file mode 100644 index a24774952e..0000000000 --- a/.changeset/rude-terms-confess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: wrap async `children` in `$$renderer.async` diff --git a/.changeset/shiny-chicken-occur.md b/.changeset/shiny-chicken-occur.md deleted file mode 100644 index 45a560480b..0000000000 --- a/.changeset/shiny-chicken-occur.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: silence label warning for buttons and anchor tags with title attributes diff --git a/.changeset/silly-walls-fail.md b/.changeset/silly-walls-fail.md deleted file mode 100644 index 6f364e7f8a..0000000000 --- a/.changeset/silly-walls-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: coerce nullish `` to empty string diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 83bd1d043f..58cc215e9d 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,19 @@ # svelte +## 5.39.7 + +### Patch Changes + +- chore: simplify batch logic ([#16847](https://github.com/sveltejs/svelte/pull/16847)) + +- fix: rebase pending batches when other batches are committed ([#16866](https://github.com/sveltejs/svelte/pull/16866)) + +- fix: wrap async `children` in `$$renderer.async` ([#16862](https://github.com/sveltejs/svelte/pull/16862)) + +- fix: silence label warning for buttons and anchor tags with title attributes ([#16872](https://github.com/sveltejs/svelte/pull/16872)) + +- fix: coerce nullish `<title>` to empty string ([#16863](https://github.com/sveltejs/svelte/pull/16863)) + ## 5.39.6 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 41a32f4a79..e5ed8c5aaf 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.39.6", + "version": "5.39.7", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index a0271de08b..aa515b600d 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.39.6'; +export const VERSION = '5.39.7'; export const PUBLIC_VERSION = '5'; From 31541c88496d58850656005eb58570ff85b1d9ef Mon Sep 17 00:00:00 2001 From: Yuki Ishii <keryuuki0418@gmail.com> Date: Wed, 1 Oct 2025 19:26:14 +0900 Subject: [PATCH 08/30] fix: allow await in svelte boundary without pending (#16857) Fix: #16856 --- .changeset/poor-flies-serve.md | 5 +++++ .../3-transform/server/visitors/SvelteBoundary.js | 13 +++++++++++-- .../samples/async-svelte-boundary/_expected.html | 1 + .../samples/async-svelte-boundary/main.svelte | 4 ++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .changeset/poor-flies-serve.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-svelte-boundary/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-svelte-boundary/main.svelte diff --git a/.changeset/poor-flies-serve.md b/.changeset/poor-flies-serve.md new file mode 100644 index 0000000000..1ac13a88ab --- /dev/null +++ b/.changeset/poor-flies-serve.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow await in svelte:boundary without pending diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js index b914c96f4e..39e6912be0 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js @@ -2,7 +2,13 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '#compiler/builders'; -import { block_close, block_open, block_open_else, build_attribute_value } from './shared/utils.js'; +import { + block_close, + block_open, + block_open_else, + build_attribute_value, + create_async_block +} from './shared/utils.js'; /** * @param {AST.SvelteBoundary} node @@ -37,6 +43,9 @@ export function SvelteBoundary(node, context) { context.state.template.push(block_open_else, pending, block_close); } else { const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); - context.state.template.push(block_open, block, block_close); + const statement = node.fragment.metadata.has_await + ? create_async_block(b.block([block])) + : block; + context.state.template.push(block_open, statement, block_close); } } diff --git a/packages/svelte/tests/server-side-rendering/samples/async-svelte-boundary/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-svelte-boundary/_expected.html new file mode 100644 index 0000000000..5be0be37f2 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-svelte-boundary/_expected.html @@ -0,0 +1 @@ +<div>this should work</div> \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-svelte-boundary/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-svelte-boundary/main.svelte new file mode 100644 index 0000000000..c4cde60119 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-svelte-boundary/main.svelte @@ -0,0 +1,4 @@ +<svelte:boundary> + {@const x = await 'this should work'} + <div>{x}</div> +</svelte:boundary> \ No newline at end of file From 4f653ddceac466302b587830089c4cd5ffd7eff6 Mon Sep 17 00:00:00 2001 From: Yuki Ishii <keryuuki0418@gmail.com> Date: Thu, 2 Oct 2025 00:56:32 +0900 Subject: [PATCH 09/30] fix: update error message to clarify usage with radio inputs (#16874) * fix: update error message to clarify usage with radio inputs * show an extra bind checked error when a radio type * typo * replace 'group' binding with , drive-by fix some other messages --------- Co-authored-by: Rich Harris <rich.harris@vercel.com> --- .changeset/slow-nails-push.md | 5 +++++ documentation/docs/03-template-syntax/12-bind.md | 4 +++- .../phases/2-analyze/visitors/BindDirective.js | 12 ++++++++---- .../dynamic-element-binding-invalid/_config.js | 2 +- .../samples/binding-dimensions-svg/errors.json | 2 +- .../samples/binding-input-checked/errors.json | 2 +- .../binding-invalid-on-element-2/errors.json | 2 +- .../samples/binding-invalid-on-element/errors.json | 2 +- .../binding-radio-input-checked/errors.json | 14 ++++++++++++++ .../binding-radio-input-checked/input.svelte | 5 +++++ 10 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 .changeset/slow-nails-push.md create mode 100644 packages/svelte/tests/validator/samples/binding-radio-input-checked/errors.json create mode 100644 packages/svelte/tests/validator/samples/binding-radio-input-checked/input.svelte diff --git a/.changeset/slow-nails-push.md b/.changeset/slow-nails-push.md new file mode 100644 index 0000000000..8dedf35593 --- /dev/null +++ b/.changeset/slow-nails-push.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: update `bind:checked` error message to clarify usage with radio inputs diff --git a/documentation/docs/03-template-syntax/12-bind.md b/documentation/docs/03-template-syntax/12-bind.md index de57815687..c21ed35919 100644 --- a/documentation/docs/03-template-syntax/12-bind.md +++ b/documentation/docs/03-template-syntax/12-bind.md @@ -95,7 +95,7 @@ Since 5.6.0, if an `<input>` has a `defaultValue` and is part of a form, it will ## `<input bind:checked>` -Checkbox and radio inputs can be bound with `bind:checked`: +Checkbox inputs can be bound with `bind:checked`: ```svelte <label> @@ -117,6 +117,8 @@ Since 5.6.0, if an `<input>` has a `defaultChecked` attribute and is part of a f </form> ``` +> [!NOTE] Use `bind:group` for radio inputs instead of `bind:checked`. + ## `<input bind:indeterminate>` Checkboxes can be in an [indeterminate](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/indeterminate) state, independently of whether they are checked or unchecked: diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js index 9f02e7fa5a..995d251a2e 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js @@ -33,7 +33,7 @@ export function BindDirective(node, context) { e.bind_invalid_target( node, node.name, - property.valid_elements.map((valid_element) => `<${valid_element}>`).join(', ') + property.valid_elements.map((valid_element) => `\`<${valid_element}>\``).join(', ') ); } @@ -67,11 +67,15 @@ export function BindDirective(node, context) { } } else { if (node.name === 'checked' && type?.value[0].data !== 'checkbox') { - e.bind_invalid_target(node, node.name, '<input type="checkbox">'); + e.bind_invalid_target( + node, + node.name, + `\`<input type="checkbox">\`${type?.value[0].data === 'radio' ? ` — for \`<input type="radio">\`, use \`bind:group\`` : ''}` + ); } if (node.name === 'files' && type?.value[0].data !== 'file') { - e.bind_invalid_target(node, node.name, '<input type="file">'); + e.bind_invalid_target(node, node.name, '`<input type="file">`'); } } } @@ -94,7 +98,7 @@ export function BindDirective(node, context) { e.bind_invalid_target( node, node.name, - `non-<svg> elements. Use 'clientWidth' for <svg> instead` + `non-\`<svg>\` elements. Use \`bind:clientWidth\` for \`<svg>\` instead` ); } diff --git a/packages/svelte/tests/compiler-errors/samples/dynamic-element-binding-invalid/_config.js b/packages/svelte/tests/compiler-errors/samples/dynamic-element-binding-invalid/_config.js index 4bb3edc27b..25b0827765 100644 --- a/packages/svelte/tests/compiler-errors/samples/dynamic-element-binding-invalid/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/dynamic-element-binding-invalid/_config.js @@ -3,6 +3,6 @@ import { test } from '../../test'; export default test({ error: { code: 'bind_invalid_target', - message: '`bind:value` can only be used with <input>, <textarea>, <select>' + message: '`bind:value` can only be used with `<input>`, `<textarea>`, `<select>`' } }); diff --git a/packages/svelte/tests/validator/samples/binding-dimensions-svg/errors.json b/packages/svelte/tests/validator/samples/binding-dimensions-svg/errors.json index 4d3819453c..61e6ca6355 100644 --- a/packages/svelte/tests/validator/samples/binding-dimensions-svg/errors.json +++ b/packages/svelte/tests/validator/samples/binding-dimensions-svg/errors.json @@ -1,7 +1,7 @@ [ { "code": "bind_invalid_target", - "message": "`bind:offsetWidth` can only be used with non-<svg> elements. Use 'clientWidth' for <svg> instead", + "message": "`bind:offsetWidth` can only be used with non-`<svg>` elements. Use `bind:clientWidth` for `<svg>` instead", "start": { "line": 5, "column": 5 diff --git a/packages/svelte/tests/validator/samples/binding-input-checked/errors.json b/packages/svelte/tests/validator/samples/binding-input-checked/errors.json index 574b25e06f..d7e8e48c95 100644 --- a/packages/svelte/tests/validator/samples/binding-input-checked/errors.json +++ b/packages/svelte/tests/validator/samples/binding-input-checked/errors.json @@ -1,7 +1,7 @@ [ { "code": "bind_invalid_target", - "message": "`bind:checked` can only be used with <input type=\"checkbox\">", + "message": "`bind:checked` can only be used with `<input type=\"checkbox\">`", "start": { "line": 5, "column": 7 diff --git a/packages/svelte/tests/validator/samples/binding-invalid-on-element-2/errors.json b/packages/svelte/tests/validator/samples/binding-invalid-on-element-2/errors.json index 92111a4ec2..19d0cd0ce1 100644 --- a/packages/svelte/tests/validator/samples/binding-invalid-on-element-2/errors.json +++ b/packages/svelte/tests/validator/samples/binding-invalid-on-element-2/errors.json @@ -1,7 +1,7 @@ [ { "code": "bind_invalid_target", - "message": "`bind:open` can only be used with <details>", + "message": "`bind:open` can only be used with `<details>`", "start": { "line": 5, "column": 5 diff --git a/packages/svelte/tests/validator/samples/binding-invalid-on-element/errors.json b/packages/svelte/tests/validator/samples/binding-invalid-on-element/errors.json index 6626afc1d0..781a8301ea 100644 --- a/packages/svelte/tests/validator/samples/binding-invalid-on-element/errors.json +++ b/packages/svelte/tests/validator/samples/binding-invalid-on-element/errors.json @@ -1,7 +1,7 @@ [ { "code": "bind_invalid_target", - "message": "`bind:value` can only be used with <input>, <textarea>, <select>", + "message": "`bind:value` can only be used with `<input>`, `<textarea>`, `<select>`", "start": { "line": 5, "column": 5 diff --git a/packages/svelte/tests/validator/samples/binding-radio-input-checked/errors.json b/packages/svelte/tests/validator/samples/binding-radio-input-checked/errors.json new file mode 100644 index 0000000000..beb46affc5 --- /dev/null +++ b/packages/svelte/tests/validator/samples/binding-radio-input-checked/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "bind_invalid_target", + "message": "`bind:checked` can only be used with `<input type=\"checkbox\">` — for `<input type=\"radio\">`, use `bind:group`", + "start": { + "line": 5, + "column": 20 + }, + "end": { + "line": 5, + "column": 38 + } + } +] diff --git a/packages/svelte/tests/validator/samples/binding-radio-input-checked/input.svelte b/packages/svelte/tests/validator/samples/binding-radio-input-checked/input.svelte new file mode 100644 index 0000000000..67e7b77a3a --- /dev/null +++ b/packages/svelte/tests/validator/samples/binding-radio-input-checked/input.svelte @@ -0,0 +1,5 @@ +<script> + let foo; +</script> + +<input type="radio" bind:checked={foo}> \ No newline at end of file From 499973454af1081e19d7eaae87658f3b76d3d091 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:03:50 -0700 Subject: [PATCH 10/30] fix: check boundary `pending` attribute at runtime on server (#16855) * fix: check boundary `pending` attribute at runtime on server * fix --------- Co-authored-by: Rich Harris <rich.harris@vercel.com> --- .changeset/lazy-cooks-return.md | 5 ++ .../server/visitors/SvelteBoundary.js | 50 ++++++++++++++----- packages/svelte/src/compiler/phases/scope.js | 6 +++ .../_expected.html | 1 + .../async-nullish-pending-snippet/main.svelte | 6 +++ 5 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 .changeset/lazy-cooks-return.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/main.svelte diff --git a/.changeset/lazy-cooks-return.md b/.changeset/lazy-cooks-return.md new file mode 100644 index 0000000000..d2f346f76d --- /dev/null +++ b/.changeset/lazy-cooks-return.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: check boundary `pending` attribute at runtime on server diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js index 39e6912be0..b272870ac3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js @@ -7,6 +7,7 @@ import { block_open, block_open_else, build_attribute_value, + build_template, create_async_block } from './shared/utils.js'; @@ -19,6 +20,11 @@ export function SvelteBoundary(node, context) { const pending_attribute = /** @type {AST.Attribute} */ ( node.attributes.find((node) => node.type === 'Attribute' && node.name === 'pending') ); + const is_pending_attr_nullish = + pending_attribute && + typeof pending_attribute.value === 'object' && + !Array.isArray(pending_attribute.value) && + !context.state.scope.evaluate(pending_attribute.value.expression).is_defined; const pending_snippet = /** @type {AST.SnippetBlock} */ ( node.fragment.nodes.find( @@ -27,20 +33,38 @@ export function SvelteBoundary(node, context) { ); if (pending_attribute || pending_snippet) { - const pending = pending_attribute - ? b.call( - build_attribute_value( - pending_attribute.value, - context, - (expression) => expression, - false, - true - ), - b.id('$$renderer') + if (pending_attribute && is_pending_attr_nullish && !pending_snippet) { + const callee = build_attribute_value( + pending_attribute.value, + context, + (expression) => expression, + false, + true + ); + const pending = b.call(callee, b.id('$$renderer')); + const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); + context.state.template.push( + b.if( + callee, + b.block(build_template([block_open_else, pending, block_close])), + b.block(build_template([block_open, block, block_close])) ) - : /** @type {BlockStatement} */ (context.visit(pending_snippet.body)); - - context.state.template.push(block_open_else, pending, block_close); + ); + } else { + const pending = pending_attribute + ? b.call( + build_attribute_value( + pending_attribute.value, + context, + (expression) => expression, + false, + true + ), + b.id('$$renderer') + ) + : /** @type {BlockStatement} */ (context.visit(pending_snippet.body)); + context.state.template.push(block_open_else, pending, block_close); + } } else { const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); const statement = node.fragment.metadata.has_await diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 887bc47c56..0c6b64dd04 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -260,6 +260,12 @@ class Evaluation { break; } + if (binding.initial?.type === 'SnippetBlock') { + this.is_defined = true; + this.is_known = false; + break; + } + if (!binding.updated && binding.initial !== null && !is_prop) { binding.scope.evaluate(/** @type {Expression} */ (binding.initial), this.values); break; diff --git a/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/_expected.html new file mode 100644 index 0000000000..1cc0b4b00f --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/_expected.html @@ -0,0 +1 @@ +<!--[--><!--[--><!---->awaited<!--]--><!--]--> \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/main.svelte new file mode 100644 index 0000000000..b4e8dbcb75 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/main.svelte @@ -0,0 +1,6 @@ +<script> + let pending = null; +</script> +<svelte:boundary {pending}> + {await 'awaited'} +</svelte:boundary> \ No newline at end of file From 949c026259eb7d10cb50c92eec01f542b3916e05 Mon Sep 17 00:00:00 2001 From: Jungzl <13jungzl@gmail.com> Date: Thu, 2 Oct 2025 00:04:03 +0800 Subject: [PATCH 11/30] fix: preserve tuple type in `$state.snapshot` (#16864) * fix: preserve tuple type in `$state.snapshot` * Update .changeset/lucky-moles-enjoy.md --------- Co-authored-by: Rich Harris <hello@rich-harris.dev> --- .changeset/lucky-moles-enjoy.md | 5 +++++ packages/svelte/src/ambient.d.ts | 16 +++++++++------- packages/svelte/types/index.d.ts | 16 +++++++++------- 3 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 .changeset/lucky-moles-enjoy.md diff --git a/.changeset/lucky-moles-enjoy.md b/.changeset/lucky-moles-enjoy.md new file mode 100644 index 0000000000..ef1dfe4bdd --- /dev/null +++ b/.changeset/lucky-moles-enjoy.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: preserve tuple type in `$state.snapshot` diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index 59128e45f0..d655fb648a 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -85,13 +85,15 @@ declare namespace $state { ? NonReactive<T> : T extends { toJSON(): infer R } ? R - : T extends Array<infer U> - ? Array<Snapshot<U>> - : T extends object - ? T extends { [key: string]: any } - ? { [K in keyof T]: Snapshot<T[K]> } - : never - : never; + : T extends readonly unknown[] + ? { [K in keyof T]: Snapshot<T[K]> } + : T extends Array<infer U> + ? Array<Snapshot<U>> + : T extends object + ? T extends { [key: string]: any } + ? { [K in keyof T]: Snapshot<T[K]> } + : never + : never; /** * Declares state that is _not_ made deeply reactive — instead of mutating it, diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 2c015b5a10..6dc6629faa 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -3171,13 +3171,15 @@ declare namespace $state { ? NonReactive<T> : T extends { toJSON(): infer R } ? R - : T extends Array<infer U> - ? Array<Snapshot<U>> - : T extends object - ? T extends { [key: string]: any } - ? { [K in keyof T]: Snapshot<T[K]> } - : never - : never; + : T extends readonly unknown[] + ? { [K in keyof T]: Snapshot<T[K]> } + : T extends Array<infer U> + ? Array<Snapshot<U>> + : T extends object + ? T extends { [key: string]: any } + ? { [K in keyof T]: Snapshot<T[K]> } + : never + : never; /** * Declares state that is _not_ made deeply reactive — instead of mutating it, From fdb01fd9663937411f9d9a7a9306bf823d4acee4 Mon Sep 17 00:00:00 2001 From: Michael Shilman <shilman@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:05:53 +0800 Subject: [PATCH 12/30] docs: Promote Storybook testing to h2 (#16792) * Promote Storybook testing Storybook testing docs are currently buried in the page contents. This change makes it visible in the right-hand table of contents. * Incorporate review feedback --- documentation/docs/07-misc/02-testing.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/docs/07-misc/02-testing.md b/documentation/docs/07-misc/02-testing.md index 23e2e023d3..4807fc8f0c 100644 --- a/documentation/docs/07-misc/02-testing.md +++ b/documentation/docs/07-misc/02-testing.md @@ -4,7 +4,7 @@ title: Testing Testing helps you write and maintain your code and guard against regressions. Testing frameworks help you with that, allowing you to describe assertions or expectations about how your code should behave. Svelte is unopinionated about which testing framework you use — you can write unit tests, integration tests, and end-to-end tests using solutions like [Vitest](https://vitest.dev/), [Jasmine](https://jasmine.github.io/), [Cypress](https://www.cypress.io/) and [Playwright](https://playwright.dev/). -## Unit and integration testing using Vitest +## Unit and component tests with Vitest Unit tests allow you to test small isolated parts of your code. Integration tests allow you to test parts of your application to see if they work together. If you're using Vite (including via SvelteKit), we recommend using [Vitest](https://vitest.dev/). You can use the Svelte CLI to [setup Vitest](/docs/cli/vitest) either during project creation or later on. @@ -246,7 +246,7 @@ test('Component', async () => { When writing component tests that involve two-way bindings, context or snippet props, it's best to create a wrapper component for your specific test and interact with that. `@testing-library/svelte` contains some [examples](https://testing-library.com/docs/svelte-testing-library/example). -### Component testing with Storybook +## Component tests with Storybook [Storybook](https://storybook.js.org) is a tool for developing and documenting UI components, and it can also be used to test your components. They're run with Vitest's browser mode, which renders your components in a real browser for the most realistic testing environment. @@ -288,7 +288,7 @@ You can create stories for component variations and test interactions with the [ /> ``` -## E2E tests using Playwright +## End-to-end tests with Playwright E2E (short for 'end to end') tests allow you to test your full application through the eyes of the user. This section uses [Playwright](https://playwright.dev/) as an example, but you can also use other solutions like [Cypress](https://www.cypress.io/) or [NightwatchJS](https://nightwatchjs.org/). From 5c847ff79e13cf3a901794ba1fd053c12b2a744d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:07:12 -0400 Subject: [PATCH 13/30] Version Packages (#16877) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/lazy-cooks-return.md | 5 ----- .changeset/lucky-moles-enjoy.md | 5 ----- .changeset/poor-flies-serve.md | 5 ----- .changeset/slow-nails-push.md | 5 ----- packages/svelte/CHANGELOG.md | 12 ++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 7 files changed, 14 insertions(+), 22 deletions(-) delete mode 100644 .changeset/lazy-cooks-return.md delete mode 100644 .changeset/lucky-moles-enjoy.md delete mode 100644 .changeset/poor-flies-serve.md delete mode 100644 .changeset/slow-nails-push.md diff --git a/.changeset/lazy-cooks-return.md b/.changeset/lazy-cooks-return.md deleted file mode 100644 index d2f346f76d..0000000000 --- a/.changeset/lazy-cooks-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: check boundary `pending` attribute at runtime on server diff --git a/.changeset/lucky-moles-enjoy.md b/.changeset/lucky-moles-enjoy.md deleted file mode 100644 index ef1dfe4bdd..0000000000 --- a/.changeset/lucky-moles-enjoy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: preserve tuple type in `$state.snapshot` diff --git a/.changeset/poor-flies-serve.md b/.changeset/poor-flies-serve.md deleted file mode 100644 index 1ac13a88ab..0000000000 --- a/.changeset/poor-flies-serve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: allow await in svelte:boundary without pending diff --git a/.changeset/slow-nails-push.md b/.changeset/slow-nails-push.md deleted file mode 100644 index 8dedf35593..0000000000 --- a/.changeset/slow-nails-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: update `bind:checked` error message to clarify usage with radio inputs diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 58cc215e9d..18a726277f 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.39.8 + +### Patch Changes + +- fix: check boundary `pending` attribute at runtime on server ([#16855](https://github.com/sveltejs/svelte/pull/16855)) + +- fix: preserve tuple type in `$state.snapshot` ([#16864](https://github.com/sveltejs/svelte/pull/16864)) + +- fix: allow await in svelte:boundary without pending ([#16857](https://github.com/sveltejs/svelte/pull/16857)) + +- fix: update `bind:checked` error message to clarify usage with radio inputs ([#16874](https://github.com/sveltejs/svelte/pull/16874)) + ## 5.39.7 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index e5ed8c5aaf..21752e2d4b 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.39.7", + "version": "5.39.8", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index aa515b600d..6d69b57447 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.39.7'; +export const VERSION = '5.39.8'; export const PUBLIC_VERSION = '5'; From 01fc47d155ce7d4b94b32adf1db85179df6b9977 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:48:15 -0700 Subject: [PATCH 14/30] docs: link to new packages page (#16882) --- .../docs/01-introduction/02-getting-started.md | 4 ++-- documentation/docs/07-misc/99-faq.md | 12 ++---------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/documentation/docs/01-introduction/02-getting-started.md b/documentation/docs/01-introduction/02-getting-started.md index e97a46ad34..2ad22c8469 100644 --- a/documentation/docs/01-introduction/02-getting-started.md +++ b/documentation/docs/01-introduction/02-getting-started.md @@ -15,11 +15,11 @@ Don't worry if you don't know Svelte yet! You can ignore all the nice features S ## Alternatives to SvelteKit -You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS, and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](faq#Is-there-a-router) as well. +You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS, and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](/packages#routing) as well. >[!NOTE] Vite is often used in standalone mode to build [single page apps (SPAs)](../kit/glossary#SPA), which you can also [build with SvelteKit](../kit/single-page-apps). -There are also plugins for [Rollup](https://github.com/sveltejs/rollup-plugin-svelte), [Webpack](https://github.com/sveltejs/svelte-loader) [and a few others](https://sveltesociety.dev/packages?category=build-plugins), but we recommend Vite. +There are also [plugins for other bundlers](/packages#bundler-plugins), but we recommend Vite. ## Editor tooling diff --git a/documentation/docs/07-misc/99-faq.md b/documentation/docs/07-misc/99-faq.md index cf98cdd3c3..cf84180dc2 100644 --- a/documentation/docs/07-misc/99-faq.md +++ b/documentation/docs/07-misc/99-faq.md @@ -91,17 +91,9 @@ Some resources for getting started with testing: ## Is there a router? -The official routing library is [SvelteKit](/docs/kit). SvelteKit provides a filesystem router, server-side rendering (SSR), and hot module reloading (HMR) in one easy-to-use package. It shares similarities with Next.js for React. +The official routing library is [SvelteKit](/docs/kit). SvelteKit provides a filesystem router, server-side rendering (SSR), and hot module reloading (HMR) in one easy-to-use package. It shares similarities with Next.js for React and Nuxt.js for Vue. -However, you can use any router library. A lot of people use [page.js](https://github.com/visionmedia/page.js). There's also [navaid](https://github.com/lukeed/navaid), which is very similar. And [universal-router](https://github.com/kriasoft/universal-router), which is isomorphic with child routes, but without built-in history support. - -If you prefer a declarative HTML approach, there's the isomorphic [svelte-routing](https://github.com/EmilTholin/svelte-routing) library and a fork of it called [svelte-navigator](https://github.com/mefechoel/svelte-navigator) containing some additional functionality. - -If you need hash-based routing on the client side, check out the [hash option](https://svelte.dev/docs/kit/configuration#router) in SvelteKit, [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router), or [abstract-state-router](https://github.com/TehShrike/abstract-state-router/). - -[Routify](https://routify.dev) is another filesystem-based router, similar to SvelteKit's router. Version 3 supports Svelte's native SSR. - -You can see a [community-maintained list of routers on sveltesociety.dev](https://sveltesociety.dev/packages?category=routers). +However, you can use any router library. A sampling of available routers are highlighted [on the packages page](/packages#routing). ## How do I write a mobile app with Svelte? From 30e2b23b591ec560a8972caffc37b2974fabbfb0 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:29:55 -0700 Subject: [PATCH 15/30] docs: update one more link to the packages page (#16883) --- documentation/docs/07-misc/99-faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/07-misc/99-faq.md b/documentation/docs/07-misc/99-faq.md index cf84180dc2..1816998299 100644 --- a/documentation/docs/07-misc/99-faq.md +++ b/documentation/docs/07-misc/99-faq.md @@ -65,7 +65,7 @@ There will be a blog post about this eventually, but in the meantime, check out ## Is there a UI component library? -There are several UI component libraries as well as standalone components. Find them under the [design systems section of the components page](https://sveltesociety.dev/packages?category=design-system) on the Svelte Society website. +There are several [UI component libraries](/packages#component-libraries) as well as standalone components listed on [the packages page](/packages). ## How do I test Svelte apps? From 2b49a5e3c0851ddba87d78b48551bd5f40e1ee13 Mon Sep 17 00:00:00 2001 From: Rich Harris <rich.harris@vercel.com> Date: Sun, 5 Oct 2025 17:59:58 -0400 Subject: [PATCH 16/30] fix: flush when pending boundaries resolve (#16897) * fix: flush when pending boundaries resolve * note to self * Update packages/svelte/src/internal/client/dom/blocks/boundary.js --- .changeset/swift-starfishes-knock.md | 5 +++++ .../svelte/src/internal/client/dom/blocks/boundary.js | 7 +++++++ .../samples/async-attachment-in-block/_config.js | 11 +++++++++++ .../samples/async-attachment-in-block/main.svelte | 9 +++++++++ 4 files changed, 32 insertions(+) create mode 100644 .changeset/swift-starfishes-knock.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-attachment-in-block/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-attachment-in-block/main.svelte diff --git a/.changeset/swift-starfishes-knock.md b/.changeset/swift-starfishes-knock.md new file mode 100644 index 0000000000..790b3fe697 --- /dev/null +++ b/.changeset/swift-starfishes-knock.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: flush when pending boundaries resolve diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 6dde40d889..88f4a85e20 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -285,6 +285,13 @@ export class Boundary { this.#anchor.before(this.#offscreen_fragment); this.#offscreen_fragment = null; } + + // TODO this feels like a little bit of a kludge, but until we + // overhaul the boundary/batch relationship it's probably + // the most pragmatic solution available to us + queue_micro_task(() => { + Batch.ensure().flush(); + }); } } diff --git a/packages/svelte/tests/runtime-runes/samples/async-attachment-in-block/_config.js b/packages/svelte/tests/runtime-runes/samples/async-attachment-in-block/_config.js new file mode 100644 index 0000000000..97da1bf835 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-attachment-in-block/_config.js @@ -0,0 +1,11 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client', 'hydrate'], + + async test({ assert, target }) { + await tick(); + assert.htmlEqual(target.innerHTML, '<div>attachment ran</div>'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-attachment-in-block/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-attachment-in-block/main.svelte new file mode 100644 index 0000000000..027b980bd1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-attachment-in-block/main.svelte @@ -0,0 +1,9 @@ +{#if await true} + <div + {@attach (node) => { + node.textContent = 'attachment ran'; + }} + > + attachment did not run + </div> +{/if} From acdd93053da6a34aef104521ab87e85fe78081b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 5 Oct 2025 19:11:05 -0400 Subject: [PATCH 17/30] Version Packages (#16899) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/swift-starfishes-knock.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/swift-starfishes-knock.md diff --git a/.changeset/swift-starfishes-knock.md b/.changeset/swift-starfishes-knock.md deleted file mode 100644 index 790b3fe697..0000000000 --- a/.changeset/swift-starfishes-knock.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: flush when pending boundaries resolve diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 18a726277f..13a2b4cc4d 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.39.9 + +### Patch Changes + +- fix: flush when pending boundaries resolve ([#16897](https://github.com/sveltejs/svelte/pull/16897)) + ## 5.39.8 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 21752e2d4b..7a616abbe8 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.39.8", + "version": "5.39.9", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 6d69b57447..efc02968d7 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.39.8'; +export const VERSION = '5.39.9'; export const PUBLIC_VERSION = '5'; From fc39f2ed0d2dd60bf1acd49fefb5c4e934d88b09 Mon Sep 17 00:00:00 2001 From: "Dominik G." <dominik.goepel@gmx.de> Date: Mon, 6 Oct 2025 18:23:59 +0200 Subject: [PATCH 18/30] chore: port security improvements from vite-ecosystem-ci trigger workflow in vite repo (#16902) --- .github/workflows/ecosystem-ci-trigger.yml | 45 ++++++++++++++++++---- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index aa08df2f84..8a6d1bf345 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -11,14 +11,13 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'sveltejs/svelte' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run') permissions: - issues: write # to add / delete reactions + issues: write # to add / delete reactions, post comments pull-requests: write # to read PR data, and to add labels actions: read # to check workflow status contents: read # to clone the repo steps: - - name: monitor action permissions - - name: check user authorization # user needs triage permission - uses: actions/github-script@v7 + - name: Check User Permissions + uses: actions/github-script@v8 id: check-permissions with: script: | @@ -57,7 +56,7 @@ jobs: } - name: Get PR Data - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: get-pr-data with: script: | @@ -67,6 +66,37 @@ jobs: repo: context.repo.repo, pull_number: context.issue.number }) + + const commentCreatedAt = new Date(context.payload.comment.created_at) + const commitPushedAt = new Date(pr.head.repo.pushed_at) + + console.log(`Comment created at: ${commentCreatedAt.toISOString()}`) + console.log(`PR last pushed at: ${commitPushedAt.toISOString()}`) + + // Check if any commits were pushed after the comment was created + if (commitPushedAt > commentCreatedAt) { + const errorMsg = [ + '⚠️ Security warning: PR was updated after the trigger command was posted.', + '', + `Comment posted at: ${commentCreatedAt.toISOString()}`, + `PR last pushed at: ${commitPushedAt.toISOString()}`, + '', + 'This could indicate an attempt to inject code after approval.', + 'Please review the latest changes and re-run /ecosystem-ci run if they are acceptable.' + ].join('\n') + + core.setFailed(errorMsg) + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: errorMsg + }) + + throw new Error('PR was pushed to after comment was created') + } + return { num: context.issue.number, branchName: pr.head.ref, @@ -85,15 +115,16 @@ jobs: svelte-ecosystem-ci - name: Trigger Downstream Workflow - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: trigger env: COMMENT: ${{ github.event.comment.body }} + PR_DATA: ${{ steps.get-pr-data.outputs.result }} with: github-token: ${{ steps.generate-token.outputs.token }} script: | const comment = process.env.COMMENT.trim() - const prData = ${{ steps.get-pr-data.outputs.result }} + const prData = JSON.parse(process.env.PR_DATA) const suite = comment.split('\n')[0].replace(/^\/ecosystem-ci run/, '').trim() From 303750a1241ac0ded3115ff879fa417e23fe7c89 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Tue, 7 Oct 2025 07:21:43 -0700 Subject: [PATCH 19/30] fix: don't replace rest props with `$$props` for excluded props (#16898) Closes #16895 --- .changeset/nervous-flies-laugh.md | 5 +++++ .../2-analyze/visitors/VariableDeclarator.js | 15 +++++++++++++++ .../3-transform/client/visitors/Identifier.js | 6 +++++- packages/svelte/src/compiler/phases/scope.js | 2 +- .../rest-props-excludes-properties/_config.js | 7 +++++++ .../component.svelte | 4 ++++ .../rest-props-excludes-properties/main.svelte | 4 ++++ 7 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .changeset/nervous-flies-laugh.md create mode 100644 packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte diff --git a/.changeset/nervous-flies-laugh.md b/.changeset/nervous-flies-laugh.md new file mode 100644 index 0000000000..88c7694bcd --- /dev/null +++ b/.changeset/nervous-flies-laugh.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't replace rest props with `$$props` for excluded props diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js index f56a665de8..7a85b4a93a 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js @@ -46,6 +46,21 @@ export function VariableDeclarator(node, context) { : path.is_rest ? 'rest_prop' : 'prop'; + if (rune === '$props' && binding.kind === 'rest_prop' && node.id.type === 'ObjectPattern') { + const { properties } = node.id; + /** @type {string[]} */ + const exclude_props = []; + for (const property of properties) { + if (property.type === 'RestElement') { + continue; + } + const key = /** @type {Identifier | Literal & { value: string | number }} */ ( + property.key + ); + exclude_props.push(key.type === 'Identifier' ? key.name : key.value.toString()); + } + (binding.metadata ??= {}).exclude_props = exclude_props; + } } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js index b01ed01bd7..b43ec7891e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js @@ -32,7 +32,11 @@ export function Identifier(node, context) { grand_parent?.type !== 'AssignmentExpression' && grand_parent?.type !== 'UpdateExpression' ) { - return b.id('$$props'); + const key = /** @type {Identifier} */ (parent.property); + + if (!binding.metadata?.exclude_props?.includes(key.name)) { + return b.id('$$props'); + } } } diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 0c6b64dd04..ffccaffba3 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -122,7 +122,7 @@ export class Binding { /** * Additional metadata, varies per binding type - * @type {null | { inside_rest?: boolean; is_template_declaration?: boolean }} + * @type {null | { inside_rest?: boolean; is_template_declaration?: boolean; exclude_props?: string[] }} */ metadata = null; diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js new file mode 100644 index 0000000000..66a42c08db --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + assert.equal(target.textContent, ' false'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte new file mode 100644 index 0000000000..40561218db --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte @@ -0,0 +1,4 @@ +<script> + const { name, ...rest } = $props(); +</script> +{rest.name} {'name' in rest} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte new file mode 100644 index 0000000000..d9f6d8a21c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte @@ -0,0 +1,4 @@ +<script> + import Component from './component.svelte'; +</script> +<Component name='world' /> \ No newline at end of file From 5d1ffcf413aa475dad2bdf975e907e2958615342 Mon Sep 17 00:00:00 2001 From: Yuki Ishii <keryuuki0418@gmail.com> Date: Tue, 7 Oct 2025 23:27:30 +0900 Subject: [PATCH 20/30] fix: allow await in if block consequent and alternate (#16890) Closes #16885 --- .changeset/kind-tigers-retire.md | 5 +++++ .../phases/3-transform/server/visitors/IfBlock.js | 6 +++++- .../samples/async-if-const/_expected.html | 1 + .../samples/async-if-const/main.svelte | 10 ++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .changeset/kind-tigers-retire.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-const/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-const/main.svelte diff --git a/.changeset/kind-tigers-retire.md b/.changeset/kind-tigers-retire.md new file mode 100644 index 0000000000..432ff1858b --- /dev/null +++ b/.changeset/kind-tigers-retire.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow await in if block consequent and alternate diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js index 798cb02e49..a869a8edf1 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js @@ -23,7 +23,11 @@ export function IfBlock(node, context) { /** @type {Statement} */ let statement = b.if(test, consequent, alternate); - if (node.metadata.expression.has_await) { + if ( + node.metadata.expression.has_await || + node.consequent.metadata.has_await || + node.alternate?.metadata.has_await + ) { statement = create_async_block(b.block([statement])); } diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-const/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-if-const/_expected.html new file mode 100644 index 0000000000..e440e5c842 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-if-const/_expected.html @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-const/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-if-const/main.svelte new file mode 100644 index 0000000000..1437ac8f14 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-if-const/main.svelte @@ -0,0 +1,10 @@ +{#if false} + {@const one = await 1} + {one} +{:else if false} + {@const two = await 2} + {two} +{:else} + {@const three = await 3} + {three} +{/if} From edcd4b59e3d06b14e8b5bc216b7e8ea874dd4995 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Tue, 7 Oct 2025 07:29:34 -0700 Subject: [PATCH 21/30] fix: correctly transform `$derived` private fields on server (#16894) Closes #16889 --- .changeset/seven-flies-drop.md | 5 +++++ .../phases/3-transform/server/visitors/MemberExpression.js | 6 +----- .../samples/class-state-derived-private/main.svelte | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 .changeset/seven-flies-drop.md diff --git a/.changeset/seven-flies-drop.md b/.changeset/seven-flies-drop.md new file mode 100644 index 0000000000..fed9c7dcac --- /dev/null +++ b/.changeset/seven-flies-drop.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly transform `$derived` private fields on server diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/MemberExpression.js index 50b5ae793f..6326e9efe2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/MemberExpression.js @@ -7,11 +7,7 @@ import * as b from '#compiler/builders'; * @param {Context} context */ export function MemberExpression(node, context) { - if ( - context.state.analysis.runes && - node.object.type === 'ThisExpression' && - node.property.type === 'PrivateIdentifier' - ) { + if (context.state.analysis.runes && node.property.type === 'PrivateIdentifier') { const field = context.state.state_fields?.get(`#${node.property.name}`); if (field?.type === '$derived' || field?.type === '$derived.by') { diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte index d971566396..92a1f5ab94 100644 --- a/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte @@ -9,7 +9,8 @@ } get embiggened1() { - return this.#doubled; + const self = this; + return self.#doubled; } get embiggened2() { From 1b1f14439660fed4df5074b9a648e9a1858e3b26 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti <ricciutipaolo@gmail.com> Date: Tue, 7 Oct 2025 17:11:39 +0200 Subject: [PATCH 22/30] fix: add `UNKNOWN` evaluation value before breaking for `binding.initial===SnippetBlock` (#16910) --- .changeset/tall-parents-go.md | 5 +++++ packages/svelte/src/compiler/phases/scope.js | 1 + .../const-snippet-reactive/Component.svelte | 5 +++++ .../samples/const-snippet-reactive/_config.js | 10 ++++++++++ .../samples/const-snippet-reactive/main.svelte | 15 +++++++++++++++ 5 files changed, 36 insertions(+) create mode 100644 .changeset/tall-parents-go.md create mode 100644 packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/main.svelte diff --git a/.changeset/tall-parents-go.md b/.changeset/tall-parents-go.md new file mode 100644 index 0000000000..6146a5c675 --- /dev/null +++ b/.changeset/tall-parents-go.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: add `UNKNOWN` evaluation value before breaking for `binding.initial===SnippetBlock` diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index ffccaffba3..f7d3dac0f7 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -263,6 +263,7 @@ class Evaluation { if (binding.initial?.type === 'SnippetBlock') { this.is_defined = true; this.is_known = false; + this.values.add(UNKNOWN); break; } diff --git a/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/Component.svelte b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/Component.svelte new file mode 100644 index 0000000000..ba0e19e0a3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/Component.svelte @@ -0,0 +1,5 @@ +<script> + let { test } = $props(); +</script> + +{@render test?.()} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/_config.js b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/_config.js new file mode 100644 index 0000000000..5f6a0172ca --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/_config.js @@ -0,0 +1,10 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const btn = target.querySelector('button'); + flushSync(() => btn?.click()); + assert.htmlEqual(target.innerHTML, `<button></button><p>snip</p>`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/main.svelte b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/main.svelte new file mode 100644 index 0000000000..c2b75c362f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/main.svelte @@ -0,0 +1,15 @@ +<script> + import Component from "./Component.svelte"; + let count = $state(0); +</script> + +{#snippet snip()} +<p>snip</p> +{/snippet} + +<button onclick={() => count++}></button> +{#if true} + {@const test = count % 2 === 0 ? undefined: snip} + <Component {test} /> +{/if} + From 78481862fb033bfaa1c31d84eba96460b0dfe19e Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:00:05 +0200 Subject: [PATCH 23/30] fix: hydrate each blocks inside element correctly (#16908) We have an each block optimization that omits the comment when the each block is the sole child of an element. This optimization clashes with async which wants to skip ahead to the sibling closing comment. For now we therefore remove that optimization when the each block is async. In the long run we should instead optimize _all_ cases where _any_ block is the sole child of an element, in both async and sync mode, consistently. fixes #16905 fixes #16907 --- .changeset/chilly-bats-build.md | 5 +++++ .../client/visitors/shared/fragment.js | 9 ++++++++- .../samples/async-each-sibling/_config.js | 16 ++++++++++++++++ .../samples/async-each-sibling/main.svelte | 11 +++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .changeset/chilly-bats-build.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-each-sibling/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-each-sibling/main.svelte diff --git a/.changeset/chilly-bats-build.md b/.changeset/chilly-bats-build.md new file mode 100644 index 0000000000..872e4c79c4 --- /dev/null +++ b/.changeset/chilly-bats-build.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: hydrate each blocks inside element correctly diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index 62d07014ee..3588f2843a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -99,7 +99,14 @@ export function process_children(nodes, initial, is_element, context) { if (is_static_element(node, context.state)) { skipped += 1; - } else if (node.type === 'EachBlock' && nodes.length === 1 && is_element) { + } else if ( + node.type === 'EachBlock' && + nodes.length === 1 && + is_element && + // In case it's wrapped in async the async logic will want to skip sibling nodes up until the end, hence we cannot make this controlled + // TODO switch this around and instead optimize for elements with a single block child and not require extra comments (neither for async nor normally) + !(node.body.metadata.has_await || node.metadata.expression.has_await) + ) { node.metadata.is_controlled = true; } else { const id = flush_node(false, node.type === 'RegularElement' ? node.name : 'node'); diff --git a/packages/svelte/tests/runtime-runes/samples/async-each-sibling/_config.js b/packages/svelte/tests/runtime-runes/samples/async-each-sibling/_config.js new file mode 100644 index 0000000000..ff10a48f11 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-each-sibling/_config.js @@ -0,0 +1,16 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['async-server', 'hydrate', 'client'], + ssrHtml: `<ul><li>1</li></ul> <button>add</button>`, + + async test({ assert, target }) { + await tick(); + const [add] = target.querySelectorAll('button'); + + add.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `<ul><li>1</li><li>2</li></ul> <button>add</button>`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-each-sibling/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-each-sibling/main.svelte new file mode 100644 index 0000000000..2c35440511 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-each-sibling/main.svelte @@ -0,0 +1,11 @@ +<script> + let array = $state(Promise.resolve([1])); +</script> + +<ul> +{#each await array as item} + <li>{item}</li> +{/each} +</ul> + +<button onclick={() => array = Promise.resolve([1, 2])}>add</button> From 06bd6a8fbdd1aada619fe68114f3a6cc8ef37c4b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 09:03:07 -0700 Subject: [PATCH 24/30] Version Packages (#16909) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/chilly-bats-build.md | 5 ----- .changeset/kind-tigers-retire.md | 5 ----- .changeset/nervous-flies-laugh.md | 5 ----- .changeset/seven-flies-drop.md | 5 ----- .changeset/tall-parents-go.md | 5 ----- packages/svelte/CHANGELOG.md | 14 ++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 8 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 .changeset/chilly-bats-build.md delete mode 100644 .changeset/kind-tigers-retire.md delete mode 100644 .changeset/nervous-flies-laugh.md delete mode 100644 .changeset/seven-flies-drop.md delete mode 100644 .changeset/tall-parents-go.md diff --git a/.changeset/chilly-bats-build.md b/.changeset/chilly-bats-build.md deleted file mode 100644 index 872e4c79c4..0000000000 --- a/.changeset/chilly-bats-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: hydrate each blocks inside element correctly diff --git a/.changeset/kind-tigers-retire.md b/.changeset/kind-tigers-retire.md deleted file mode 100644 index 432ff1858b..0000000000 --- a/.changeset/kind-tigers-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: allow await in if block consequent and alternate diff --git a/.changeset/nervous-flies-laugh.md b/.changeset/nervous-flies-laugh.md deleted file mode 100644 index 88c7694bcd..0000000000 --- a/.changeset/nervous-flies-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't replace rest props with `$$props` for excluded props diff --git a/.changeset/seven-flies-drop.md b/.changeset/seven-flies-drop.md deleted file mode 100644 index fed9c7dcac..0000000000 --- a/.changeset/seven-flies-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly transform `$derived` private fields on server diff --git a/.changeset/tall-parents-go.md b/.changeset/tall-parents-go.md deleted file mode 100644 index 6146a5c675..0000000000 --- a/.changeset/tall-parents-go.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: add `UNKNOWN` evaluation value before breaking for `binding.initial===SnippetBlock` diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 13a2b4cc4d..84902a4718 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,19 @@ # svelte +## 5.39.10 + +### Patch Changes + +- fix: hydrate each blocks inside element correctly ([#16908](https://github.com/sveltejs/svelte/pull/16908)) + +- fix: allow await in if block consequent and alternate ([#16890](https://github.com/sveltejs/svelte/pull/16890)) + +- fix: don't replace rest props with `$$props` for excluded props ([#16898](https://github.com/sveltejs/svelte/pull/16898)) + +- fix: correctly transform `$derived` private fields on server ([#16894](https://github.com/sveltejs/svelte/pull/16894)) + +- fix: add `UNKNOWN` evaluation value before breaking for `binding.initial===SnippetBlock` ([#16910](https://github.com/sveltejs/svelte/pull/16910)) + ## 5.39.9 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 7a616abbe8..41c43cdd25 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.39.9", + "version": "5.39.10", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index efc02968d7..cd2c8e9440 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.39.9'; +export const VERSION = '5.39.10'; export const PUBLIC_VERSION = '5'; From b5c84371510d39770243a4431e271dd2ae5b2289 Mon Sep 17 00:00:00 2001 From: Rich Harris <rich.harris@vercel.com> Date: Wed, 8 Oct 2025 06:58:21 -0400 Subject: [PATCH 25/30] fix: flush batches whenever an async value resolves (#16912) * fix: flush batches whenever an async value resolves * move some code around * unnecessary --- .changeset/metal-parents-train.md | 5 +++ .../src/internal/client/reactivity/async.js | 2 - .../src/internal/client/reactivity/batch.js | 45 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) create mode 100644 .changeset/metal-parents-train.md diff --git a/.changeset/metal-parents-train.md b/.changeset/metal-parents-train.md new file mode 100644 index 0000000000..57c99453c3 --- /dev/null +++ b/.changeset/metal-parents-train.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: flush batches whenever an async value resolves diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 4d572281b7..45c78ff926 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -52,8 +52,6 @@ export function flatten(sync, async, fn) { Promise.all(async.map((expression) => async_derived(expression))) .then((result) => { - batch?.activate(); - restore(); try { diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index fb704edb13..102d0670b6 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -178,6 +178,8 @@ export class Batch { flush_queued_effects(render_effects); flush_queued_effects(effects); + previous_batch = null; + this.#deferred?.resolve(); } else { this.#defer_effects(this.#render_effects); @@ -280,17 +282,6 @@ export class Batch { deactivate() { current_batch = null; - previous_batch = null; - - for (const update of effect_pending_updates) { - effect_pending_updates.delete(update); - update(); - - if (current_batch !== null) { - // only do one at a time - break; - } - } } flush() { @@ -307,6 +298,16 @@ export class Batch { } this.deactivate(); + + for (const update of effect_pending_updates) { + effect_pending_updates.delete(update); + update(); + + if (current_batch !== null) { + // only do one at a time + break; + } + } } /** @@ -375,21 +376,17 @@ export class Batch { decrement() { this.#pending -= 1; - if (this.#pending === 0) { - for (const e of this.#dirty_effects) { - set_signal_status(e, DIRTY); - schedule_effect(e); - } - - for (const e of this.#maybe_dirty_effects) { - set_signal_status(e, MAYBE_DIRTY); - schedule_effect(e); - } + for (const e of this.#dirty_effects) { + set_signal_status(e, DIRTY); + schedule_effect(e); + } - this.flush(); - } else { - this.deactivate(); + for (const e of this.#maybe_dirty_effects) { + set_signal_status(e, MAYBE_DIRTY); + schedule_effect(e); } + + this.flush(); } /** @param {() => void} fn */ From 844fab7798dd242bed0460ceeebd18c50674f83b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:01:06 +0200 Subject: [PATCH 26/30] Version Packages (#16913) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/metal-parents-train.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/metal-parents-train.md diff --git a/.changeset/metal-parents-train.md b/.changeset/metal-parents-train.md deleted file mode 100644 index 57c99453c3..0000000000 --- a/.changeset/metal-parents-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: flush batches whenever an async value resolves diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 84902a4718..2302acd033 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.39.11 + +### Patch Changes + +- fix: flush batches whenever an async value resolves ([#16912](https://github.com/sveltejs/svelte/pull/16912)) + ## 5.39.10 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 41c43cdd25..822f19f001 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.39.10", + "version": "5.39.11", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index cd2c8e9440..0f723c4eb0 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.39.10'; +export const VERSION = '5.39.11'; export const PUBLIC_VERSION = '5'; From 9c350cfdab1a55e7d7960e434e2a3349e9da0946 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:59:40 +0200 Subject: [PATCH 27/30] chore: test for batches flush fix (#16915) Test for #16912 Also some explanation what the bug was: 1. async batch kicks off 2. outer async work succeeds, still something pending, so doesn't do anything for now 3. something unrelated writes to a signal (in the remote functions case it's the query writing to loading, raw etc), which creates a new batch 4. new batch executes. since there are multiple batches it takes the previous value which means if block is still alive. commits that, since no async work from the perspective of this branch 5. inner async work succeeds. now the batch has zero pending async work so it can flush. But the if block is no longer dirty since it was done by the other batch already -> never undos the other work #16912 fixes it by still traversing the tree which means the if block deletion is scheduled to commit later, which it then does --- .../async-inner-after-outer/_config.js | 57 +++++++++++++++++++ .../async-inner-after-outer/main.svelte | 41 +++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/_config.js b/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/_config.js new file mode 100644 index 0000000000..8905ee4bf5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/_config.js @@ -0,0 +1,57 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const shift = document.querySelector('button'); + shift?.click(); + await tick(); + shift?.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + <p>true</p> + <button>toggle</button> + <button>shift</button> + ` + ); + + const toggle = target.querySelector('button'); + toggle?.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + <p>true</p> + <button>toggle</button> + <button>shift</button> + ` + ); + + shift?.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + <p>true</p> + <button>toggle</button> + <button>shift</button> + ` + ); + + shift?.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + <button>toggle</button> + <button>shift</button> + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/main.svelte new file mode 100644 index 0000000000..0b3b21f28c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/main.svelte @@ -0,0 +1,41 @@ +<script> + let show = $state(true); + let count = $state(0); + let queue = []; + + function foo() { + const {promise, resolve} = Promise.withResolvers(); + const s = show; + queue.push(() => resolve(s)); + return promise; + } + + function bar() { + const {promise, resolve} = Promise.withResolvers(); + const s = show; + queue.push(() => { + // This will create a new batch while the other batch is still in flight + count++ + resolve(s); + }); + return promise; + } + + $effect(() => { count; }); +</script> + +<svelte:boundary> + {#if await foo()} + <p>{await bar()}</p> + {/if} + + <button onclick={() => { + show = !show + }}>toggle</button> + + {#snippet pending()} + <p>loading...</p> + {/snippet} +</svelte:boundary> + +<button onclick={() => queue.shift()()}>shift</button> \ No newline at end of file From 7f8e60fd8a53e1eed28a60b5514bd07b49e97423 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti <ricciutipaolo@gmail.com> Date: Thu, 9 Oct 2025 13:32:45 +0200 Subject: [PATCH 28/30] fix: generate correct SSR code for the case where `pending` is an attribute (#16919) Closes #16886 --- .changeset/spicy-rabbits-drive.md | 5 +++++ .../phases/3-transform/server/visitors/SvelteBoundary.js | 7 +++++-- .../samples/boundary-pending-prop-async/_config.js | 3 +++ .../samples/boundary-pending-prop-async/_expected.html | 1 + .../samples/boundary-pending-prop-async/main.svelte | 8 ++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 .changeset/spicy-rabbits-drive.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/main.svelte diff --git a/.changeset/spicy-rabbits-drive.md b/.changeset/spicy-rabbits-drive.md new file mode 100644 index 0000000000..01834294e1 --- /dev/null +++ b/.changeset/spicy-rabbits-drive.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: generate correct SSR code for the case where `pending` is an attribute diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js index b272870ac3..41a18cf52d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js @@ -43,11 +43,14 @@ export function SvelteBoundary(node, context) { ); const pending = b.call(callee, b.id('$$renderer')); const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); + const statement = node.fragment.metadata.has_await + ? create_async_block(b.block([block])) + : block; context.state.template.push( b.if( callee, - b.block(build_template([block_open_else, pending, block_close])), - b.block(build_template([block_open, block, block_close])) + b.block([b.stmt(pending)]), + b.block(build_template([block_open, statement, block_close])) ) ); } else { diff --git a/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_config.js b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_config.js new file mode 100644 index 0000000000..f47bee71df --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_expected.html b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_expected.html new file mode 100644 index 0000000000..ac7cd985f1 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_expected.html @@ -0,0 +1 @@ +<!--[--><!---->Loading...<!--]--> \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/main.svelte b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/main.svelte new file mode 100644 index 0000000000..7e0d4c62ea --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/main.svelte @@ -0,0 +1,8 @@ +{#snippet pending()} + Loading... +{/snippet} + +<svelte:boundary pending={pending}> + {@const data = await Promise.resolve('hello')} + <p>{data}</p> +</svelte:boundary> \ No newline at end of file From 297afd057828a1bf04f15d5b22b4123fd3e522d8 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti <ricciutipaolo@gmail.com> Date: Thu, 9 Oct 2025 18:43:41 +0200 Subject: [PATCH 29/30] fix: generate correct code for `each` blocks with async body (#16923) * fix: generate correct code for `each` blocks with async body * fix: else branch of `each` block * chore: add expected html --- .changeset/wicked-goats-begin.md | 5 +++++ .../phases/3-transform/server/visitors/EachBlock.js | 6 ++++-- .../samples/each-body-async/_config.js | 5 +++++ .../samples/each-body-async/_expected.html | 1 + .../samples/each-body-async/main.svelte | 11 +++++++++++ 5 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .changeset/wicked-goats-begin.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/each-body-async/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/each-body-async/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/each-body-async/main.svelte diff --git a/.changeset/wicked-goats-begin.md b/.changeset/wicked-goats-begin.md new file mode 100644 index 0000000000..04a22aa310 --- /dev/null +++ b/.changeset/wicked-goats-begin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: generate correct code for `each` blocks with async body diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js index 5a7ca8b566..2d5b0c8931 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js @@ -32,7 +32,9 @@ export function EachBlock(node, context) { each.push(b.let(node.index, index)); } - each.push(.../** @type {BlockStatement} */ (context.visit(node.body)).body); + const new_body = /** @type {BlockStatement} */ (context.visit(node.body)).body; + + each.push(...(node.body.metadata.has_await ? [create_async_block(b.block(new_body))] : new_body)); const for_loop = b.for( b.declaration('let', [ @@ -55,7 +57,7 @@ export function EachBlock(node, context) { b.if( b.binary('!==', b.member(array_id, 'length'), b.literal(0)), b.block([open, for_loop]), - fallback + node.fallback.metadata.has_await ? create_async_block(fallback) : fallback ) ); } else { diff --git a/packages/svelte/tests/server-side-rendering/samples/each-body-async/_config.js b/packages/svelte/tests/server-side-rendering/samples/each-body-async/_config.js new file mode 100644 index 0000000000..05de37a8bd --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/each-body-async/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + mode: ['async'] +}); diff --git a/packages/svelte/tests/server-side-rendering/samples/each-body-async/_expected.html b/packages/svelte/tests/server-side-rendering/samples/each-body-async/_expected.html new file mode 100644 index 0000000000..605299c2ae --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/each-body-async/_expected.html @@ -0,0 +1 @@ +<!--[--><!--[--><!--[--><!---->each<!--]--><!--]--> <!--[--><!--[!--><!---->else<!--]--><!--]--><!--]--> \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/each-body-async/main.svelte b/packages/svelte/tests/server-side-rendering/samples/each-body-async/main.svelte new file mode 100644 index 0000000000..86e15c4e7e --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/each-body-async/main.svelte @@ -0,0 +1,11 @@ +{#each { length: 1 }} + {@const data = await Promise.resolve("each")} + {data} +{/each} + +{#each { length: 0 }} + should not see this +{:else} + {@const data = await Promise.resolve("else")} + {data} +{/each} \ No newline at end of file From 93012e1e6fce4d26b75d065301330cbe8a9e489a Mon Sep 17 00:00:00 2001 From: 7nik <kifiranet@gmail.com> Date: Thu, 9 Oct 2025 23:26:52 +0300 Subject: [PATCH 30/30] fix: track the user's getter of `bind:this` (#16916) --- .changeset/rude-frogs-train.md | 5 ++ .../docs/03-template-syntax/12-bind.md | 2 + .../client/visitors/AssignmentExpression.js | 11 +++-- .../client/visitors/shared/utils.js | 48 +++++++++++++------ .../bind-getter-setter-loop/_config.js | 16 +++++++ .../bind-getter-setter-loop/main.svelte | 13 +++++ 6 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 .changeset/rude-frogs-train.md create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/main.svelte diff --git a/.changeset/rude-frogs-train.md b/.changeset/rude-frogs-train.md new file mode 100644 index 0000000000..06da5dcc1e --- /dev/null +++ b/.changeset/rude-frogs-train.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: track the user's getter of `bind:this` diff --git a/documentation/docs/03-template-syntax/12-bind.md b/documentation/docs/03-template-syntax/12-bind.md index c21ed35919..be84969b87 100644 --- a/documentation/docs/03-template-syntax/12-bind.md +++ b/documentation/docs/03-template-syntax/12-bind.md @@ -364,6 +364,8 @@ Components also support `bind:this`, allowing you to interact with component ins </script> ``` +> [!NOTE] In case of using [the function bindings](#Function-bindings), the getter is required to ensure that the correct value is nullified on component or element destruction. + ## bind:_property_ for components ```svelte diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js index 7d64d60bca..731569aaae 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js @@ -192,17 +192,18 @@ function build_assignment(operator, left, right, context) { path.at(-1) === 'Component' || path.at(-1) === 'SvelteComponent' || (path.at(-1) === 'ArrowFunctionExpression' && - path.at(-2) === 'SequenceExpression' && - (path.at(-3) === 'Component' || - path.at(-3) === 'SvelteComponent' || - path.at(-3) === 'BindDirective')) + (path.at(-2) === 'BindDirective' || + (path.at(-2) === 'Component' && path.at(-3) === 'Fragment') || + (path.at(-2) === 'SequenceExpression' && + (path.at(-3) === 'Component' || + path.at(-3) === 'SvelteComponent' || + path.at(-3) === 'BindDirective')))) ) { should_transform = false; } if (left.type === 'MemberExpression' && should_transform) { const callee = callees[operator]; - return /** @type {Expression} */ ( context.visit( b.call( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index ba140a153e..a42063b2e2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -209,10 +209,8 @@ export function parse_directive_name(name) { * @param {import('zimmerframe').Context<AST.SvelteNode, ComponentClientTransformState>} context */ export function build_bind_this(expression, value, { state, visit }) { - if (expression.type === 'SequenceExpression') { - const [get, set] = /** @type {SequenceExpression} */ (visit(expression)).expressions; - return b.call('$.bind_this', value, set, get); - } + const [getter, setter] = + expression.type === 'SequenceExpression' ? expression.expressions : [null, null]; /** @type {Identifier[]} */ const ids = []; @@ -229,7 +227,7 @@ export function build_bind_this(expression, value, { state, visit }) { // Note that we only do this for each context variables, the consequence is that the value might be stale in // some scenarios where the value is a member expression with changing computed parts or using a combination of multiple // variables, but that was the same case in Svelte 4, too. Once legacy mode is gone completely, we can revisit this. - walk(expression, null, { + walk(getter ?? expression, null, { Identifier(node, { path }) { if (seen.includes(node.name)) return; seen.push(node.name); @@ -260,9 +258,17 @@ export function build_bind_this(expression, value, { state, visit }) { const child_state = { ...state, transform }; - const get = /** @type {Expression} */ (visit(expression, child_state)); - const set = /** @type {Expression} */ ( - visit(b.assignment('=', expression, b.id('$$value')), child_state) + let get = /** @type {Expression} */ (visit(getter ?? expression, child_state)); + let set = /** @type {Expression} */ ( + visit( + setter ?? + b.assignment( + '=', + /** @type {Identifier | MemberExpression} */ (expression), + b.id('$$value') + ), + child_state + ) ); // If we're mutating a property, then it might already be non-existent. @@ -275,13 +281,25 @@ export function build_bind_this(expression, value, { state, visit }) { node = node.object; } - return b.call( - '$.bind_this', - value, - b.arrow([b.id('$$value'), ...ids], set), - b.arrow([...ids], get), - values.length > 0 && b.thunk(b.array(values)) - ); + get = + get.type === 'ArrowFunctionExpression' + ? b.arrow([...ids], get.body) + : get.type === 'FunctionExpression' + ? b.function(null, [...ids], get.body) + : getter + ? get + : b.arrow([...ids], get); + + set = + set.type === 'ArrowFunctionExpression' + ? b.arrow([set.params[0] ?? b.id('_'), ...ids], set.body) + : set.type === 'FunctionExpression' + ? b.function(null, [set.params[0] ?? b.id('_'), ...ids], set.body) + : setter + ? set + : b.arrow([b.id('$$value'), ...ids], set); + + return b.call('$.bind_this', value, set, get, values.length > 0 && b.thunk(b.array(values))); } /** diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/_config.js new file mode 100644 index 0000000000..9c8b4fc6c0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [btn] = target.querySelectorAll('button'); + + flushSync(() => { + btn.click(); + }); + assert.htmlEqual( + target.innerHTML, + '<button>Shuffle</button> <br> <b>5</b><b>1</b><b>4</b><b>2</b><b>3</b> <br> 51423' + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/main.svelte new file mode 100644 index 0000000000..60444e8978 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/main.svelte @@ -0,0 +1,13 @@ +<script> + let arr = $state([1, 2, 3, 4, 5]); + let elements = $state([]); +</script> + +<button onclick={() => arr = [5, 1, 4, 2, 3]}>Shuffle</button><br> +{#each arr as item, i (item)} + <b bind:this={() => elements[i], (v) => elements[i] = v }>{item}</b> +{/each} +<br> +{#each elements as elem} + {elem.textContent} +{/each} \ No newline at end of file