} values
* @param {(node: AST.SvelteNode, state: any) => any} visit
diff --git a/packages/svelte/tests/runtime-runes/samples/random/_config.js b/packages/svelte/tests/runtime-runes/samples/random/_config.js
new file mode 100644
index 000000000000..368dd20c6c8f
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/random/_config.js
@@ -0,0 +1,8 @@
+import { test } from '../../test';
+
+export default test({
+ test({ assert, target }) {
+ const [p1, p2] = target.querySelectorAll('p');
+ assert.notEqual(p1.textContent, p2.textContent);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/random/main.svelte b/packages/svelte/tests/runtime-runes/samples/random/main.svelte
new file mode 100644
index 000000000000..e1ec0b564903
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/random/main.svelte
@@ -0,0 +1,6 @@
+
+
+{(Math.random() * m).toFixed(10)}
+{(Math.random() * m).toFixed(10)}
diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js
index d97a58bf40d8..219db6ffd529 100644
--- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js
@@ -21,13 +21,13 @@ export default function Main($$anchor) {
$.template_effect(() => $.set_custom_element_data(custom_element_1, 'fooBar', y()));
$.template_effect(
- ($0) => {
+ ($0, $1) => {
$.set_attribute(div, 'foobar', x);
$.set_attribute(svg, 'viewBox', x);
$.set_attribute(div_1, 'foobar', $0);
- $.set_attribute(svg_1, 'viewBox', $0);
+ $.set_attribute(svg_1, 'viewBox', $1);
},
- [y]
+ [y, y]
);
$.append($$anchor, fragment);
From 43ff3047ac525742c020565781b48dcd6bd06c39 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 5 Mar 2025 11:37:21 -0500
Subject: [PATCH 05/97] Version Packages (#15452)
Co-authored-by: github-actions[bot]
---
.changeset/five-wolves-swim.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/five-wolves-swim.md
diff --git a/.changeset/five-wolves-swim.md b/.changeset/five-wolves-swim.md
deleted file mode 100644
index 92178bed9047..000000000000
--- a/.changeset/five-wolves-swim.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: never deduplicate expressions in templates
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index e1bc27b51d90..0ff6b62fe007 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.22.4
+
+### Patch Changes
+
+- fix: never deduplicate expressions in templates ([#15451](https://github.com/sveltejs/svelte/pull/15451))
+
## 5.22.3
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 1657b59577bc..1f95811cd5b8 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.22.3",
+ "version": "5.22.4",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 29b56b5b2e32..845375314f63 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.22.3';
+export const VERSION = '5.22.4';
export const PUBLIC_VERSION = '5';
From 2d3818463a6ef4b888d6ca387725eb45e2143059 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 5 Mar 2025 11:47:59 -0500
Subject: [PATCH 06/97] chore: check namespace inside set attributes (#15443)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* set_attributes for
* changeset
* Update .changeset/wise-hats-wonder.md
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
* add changeset
* remove `chore` changeset — no need for non-user-facing changes to appear in changelog
* Update packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
* determine element traits inside set_attributes
* unused
* stash lookup
---------
Co-authored-by: adiguba
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
---
.changeset/quiet-baboons-listen.md | 5 ++
.../client/visitors/RegularElement.js | 11 +----
.../client/visitors/SvelteElement.js | 4 +-
.../client/visitors/shared/element.js | 8 +--
packages/svelte/src/constants.js | 1 +
.../client/dom/elements/attributes.js | 49 +++++++++++--------
6 files changed, 37 insertions(+), 41 deletions(-)
create mode 100644 .changeset/quiet-baboons-listen.md
diff --git a/.changeset/quiet-baboons-listen.md b/.changeset/quiet-baboons-listen.md
new file mode 100644
index 000000000000..eb5b4cc69927
--- /dev/null
+++ b/.changeset/quiet-baboons-listen.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: respect `svelte-ignore hydration_attribute_changed` on elements with spread attributes
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
index 434b49caa1e6..3dd303921369 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
@@ -221,16 +221,7 @@ export function RegularElement(node, context) {
if (has_spread) {
const attributes_id = b.id(context.state.scope.generate('attributes'));
- build_set_attributes(
- attributes,
- class_directives,
- context,
- node,
- node_id,
- attributes_id,
- (node.metadata.svg || node.metadata.mathml || is_custom_element_node(node)) && b.true,
- is_custom_element_node(node) && b.true
- );
+ build_set_attributes(attributes, class_directives, context, node, node_id, attributes_id);
// If value binding exists, that one takes care of calling $.init_select
if (node.name === 'select' && !bindings.has('value')) {
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js
index ac284c818d3d..3250c2439290 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js
@@ -114,9 +114,7 @@ export function SvelteElement(node, context) {
inner_context,
node,
element_id,
- attributes_id,
- b.binary('===', b.member(element_id, 'namespaceURI'), b.id('$.NAMESPACE_SVG')),
- b.call(b.member(b.member(element_id, 'nodeName'), 'includes'), b.literal('-'))
+ attributes_id
);
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
index 81a4b45288eb..db8f2e4aa083 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
@@ -17,8 +17,6 @@ import { build_template_chunk, get_expression_id } from './utils.js';
* @param {AST.RegularElement | AST.SvelteElement} element
* @param {Identifier} element_id
* @param {Identifier} attributes_id
- * @param {false | Expression} preserve_attribute_case
- * @param {false | Expression} is_custom_element
*/
export function build_set_attributes(
attributes,
@@ -26,9 +24,7 @@ export function build_set_attributes(
context,
element,
element_id,
- attributes_id,
- preserve_attribute_case,
- is_custom_element
+ attributes_id
) {
let is_dynamic = false;
@@ -91,8 +87,6 @@ export function build_set_attributes(
element.metadata.scoped &&
context.state.analysis.css.hash !== '' &&
b.literal(context.state.analysis.css.hash),
- preserve_attribute_case,
- is_custom_element,
is_ignored(element, 'hydration_attribute_changed') && b.true
);
diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js
index 03fddc5ebd28..8861e440fc30 100644
--- a/packages/svelte/src/constants.js
+++ b/packages/svelte/src/constants.js
@@ -33,6 +33,7 @@ export const UNINITIALIZED = Symbol();
export const FILENAME = Symbol('filename');
export const HMR = Symbol('hmr');
+export const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml';
export const NAMESPACE_SVG = 'http://www.w3.org/2000/svg';
export const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index dd408dcf8715..44e67155fc76 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -15,10 +15,14 @@ import {
} from '../../runtime.js';
import { clsx } from '../../../shared/attributes.js';
import { set_class } from './class.js';
+import { NAMESPACE_HTML } from '../../../../constants.js';
export const CLASS = Symbol('class');
export const STYLE = Symbol('style');
+const IS_CUSTOM_ELEMENT = Symbol('is custom element');
+const IS_HTML = Symbol('is html');
+
/**
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need
* to remove it upon hydration to avoid a bug when someone resets the form value.
@@ -63,8 +67,7 @@ export function remove_input_defaults(input) {
* @param {any} value
*/
export function set_value(element, value) {
- // @ts-expect-error
- var attributes = (element.__attributes ??= {});
+ var attributes = get_attributes(element);
if (
attributes.value ===
@@ -87,8 +90,7 @@ export function set_value(element, value) {
* @param {boolean} checked
*/
export function set_checked(element, checked) {
- // @ts-expect-error
- var attributes = (element.__attributes ??= {});
+ var attributes = get_attributes(element);
if (
attributes.checked ===
@@ -151,8 +153,7 @@ export function set_default_value(element, value) {
* @param {boolean} [skip_warning]
*/
export function set_attribute(element, attribute, value, skip_warning) {
- // @ts-expect-error
- var attributes = (element.__attributes ??= {});
+ var attributes = get_attributes(element);
if (hydrating) {
attributes[attribute] = element.getAttribute(attribute);
@@ -261,20 +262,15 @@ export function set_custom_element_data(node, prop, value) {
* @param {Record | undefined} prev
* @param {Record} next New attributes - this function mutates this object
* @param {string} [css_hash]
- * @param {boolean} [preserve_attribute_case]
- * @param {boolean} [is_custom_element]
* @param {boolean} [skip_warning]
* @returns {Record}
*/
-export function set_attributes(
- element,
- prev,
- next,
- css_hash,
- preserve_attribute_case = false,
- is_custom_element = false,
- skip_warning = false
-) {
+export function set_attributes(element, prev, next, css_hash, skip_warning = false) {
+ var attributes = get_attributes(element);
+
+ var is_custom_element = attributes[IS_CUSTOM_ELEMENT];
+ var preserve_attribute_case = !attributes[IS_HTML];
+
// If we're hydrating but the custom element is from Svelte, and it already scaffolded,
// then it might run block logic in hydration mode, which we have to prevent.
let is_hydrating_custom_element = hydrating && is_custom_element;
@@ -299,9 +295,6 @@ export function set_attributes(
var setters = get_setters(element);
- // @ts-expect-error
- var attributes = /** @type {Record} **/ (element.__attributes ??= {});
-
// since key is captured we use const
for (const key in next) {
// let instead of var because referenced in a closure
@@ -432,7 +425,7 @@ export function set_attributes(
// @ts-ignore
element[name] = value;
} else if (typeof value !== 'function') {
- set_attribute(element, name, value);
+ set_attribute(element, name, value, skip_warning);
}
}
if (key === 'style' && '__styles' in element) {
@@ -448,6 +441,20 @@ export function set_attributes(
return current;
}
+/**
+ *
+ * @param {Element} element
+ */
+function get_attributes(element) {
+ return /** @type {Record} **/ (
+ // @ts-expect-error
+ element.__attributes ??= {
+ [IS_CUSTOM_ELEMENT]: element.nodeName.includes('-'),
+ [IS_HTML]: element.namespaceURI === NAMESPACE_HTML
+ }
+ );
+}
+
/** @type {Map} */
var setters_cache = new Map();
From 1efad3f6e23442f296aa9910762512a42e450b87 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 5 Mar 2025 12:26:18 -0500
Subject: [PATCH 07/97] chore: add monitoring to github actions (#15436)
* chore: add monitoring to github actions
* try this
---
.github/workflows/ci.yml | 3 +++
.github/workflows/ecosystem-ci-trigger.yml | 1 +
.github/workflows/pkg.pr.new-comment.yml | 1 +
.github/workflows/pkg.pr.new.yml | 2 ++
.github/workflows/release.yml | 1 +
5 files changed, 8 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b2bcb088480b..cf73a1f6cb02 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,6 +12,7 @@ env:
jobs:
Tests:
+ permissions: {}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
@@ -41,6 +42,7 @@ jobs:
env:
CI: true
Lint:
+ permissions: {}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
@@ -61,6 +63,7 @@ jobs:
if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail
run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); }
Benchmarks:
+ permissions: {}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml
index ce7bf04136ac..71df3242e8f1 100644
--- a/.github/workflows/ecosystem-ci-trigger.yml
+++ b/.github/workflows/ecosystem-ci-trigger.yml
@@ -9,6 +9,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'sveltejs/svelte' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
steps:
+ - uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/github-script@v6
with:
script: |
diff --git a/.github/workflows/pkg.pr.new-comment.yml b/.github/workflows/pkg.pr.new-comment.yml
index 1698a456d3df..b1fba0b04b30 100644
--- a/.github/workflows/pkg.pr.new-comment.yml
+++ b/.github/workflows/pkg.pr.new-comment.yml
@@ -11,6 +11,7 @@ jobs:
name: 'Update comment'
runs-on: ubuntu-latest
steps:
+ - uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Download artifact
uses: actions/download-artifact@v4
with:
diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml
index 99f8153517f9..90d219faae6a 100644
--- a/.github/workflows/pkg.pr.new.yml
+++ b/.github/workflows/pkg.pr.new.yml
@@ -3,6 +3,8 @@ on: [push, pull_request]
jobs:
build:
+ permissions: {}
+
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1daef0b89cc3..6debe5662a88 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,6 +17,7 @@ jobs:
name: Release
runs-on: ubuntu-latest
steps:
+ - uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Checkout Repo
uses: actions/checkout@v4
with:
From 2f685c1dbadcf7e158282109f746400834f5eaf5 Mon Sep 17 00:00:00 2001
From: adiGuba
Date: Wed, 5 Mar 2025 20:20:56 +0100
Subject: [PATCH 08/97] fix: spreading style is not consistent with attribute
(#15323)
* style must be set via set_attribute
* test
* changeset
* add empty string and null in test
* explanatory comment
* this is now redundant, set_attribute takes care of it
* drive-by
* tweak changeset
---------
Co-authored-by: Rich Harris
---
.changeset/real-cameras-attack.md | 5 ++
.../client/dom/elements/attributes.js | 17 ++---
.../samples/style-update/_config.js | 64 +++++++++++++++++++
.../samples/style-update/main.svelte | 9 +++
4 files changed, 87 insertions(+), 8 deletions(-)
create mode 100644 .changeset/real-cameras-attack.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/style-update/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/style-update/main.svelte
diff --git a/.changeset/real-cameras-attack.md b/.changeset/real-cameras-attack.md
new file mode 100644
index 000000000000..35e276478508
--- /dev/null
+++ b/.changeset/real-cameras-attack.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: always use `setAttribute` when setting `style`
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index 44e67155fc76..cebc9173bab2 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -218,6 +218,7 @@ export function set_custom_element_data(node, prop, value) {
// or effect
var previous_reaction = active_reaction;
var previous_effect = active_effect;
+
// If we're hydrating but the custom element is from Svelte, and it already scaffolded,
// then it might run block logic in hydration mode, which we have to prevent.
let was_hydrating = hydrating;
@@ -227,17 +228,20 @@ export function set_custom_element_data(node, prop, value) {
set_active_reaction(null);
set_active_effect(null);
+
try {
if (
+ // `style` should use `set_attribute` rather than the setter
+ prop !== 'style' &&
// Don't compute setters for custom elements while they aren't registered yet,
// because during their upgrade/instantiation they might add more setters.
// Instead, fall back to a simple "an object, then set as property" heuristic.
- setters_cache.has(node.nodeName) ||
+ (setters_cache.has(node.nodeName) ||
// customElements may not be available in browser extension contexts
!customElements ||
customElements.get(node.tagName.toLowerCase())
? get_setters(node).includes(prop)
- : value && typeof value === 'object'
+ : value && typeof value === 'object')
) {
// @ts-expect-error
node[prop] = value;
@@ -378,8 +382,9 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
// @ts-ignore
element[`__${event_name}`] = undefined;
}
- } else if (key === 'style' && value != null) {
- element.style.cssText = value + '';
+ } else if (key === 'style') {
+ // avoid using the setter
+ set_attribute(element, key, value);
} else if (key === 'autofocus') {
autofocus(/** @type {HTMLElement} */ (element), Boolean(value));
} else if (!is_custom_element && (key === '__value' || (key === 'value' && value != null))) {
@@ -428,10 +433,6 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
set_attribute(element, name, value, skip_warning);
}
}
- if (key === 'style' && '__styles' in element) {
- // reset styles to force style: directive to update
- element.__styles = {};
- }
}
if (is_hydrating_custom_element) {
diff --git a/packages/svelte/tests/runtime-runes/samples/style-update/_config.js b/packages/svelte/tests/runtime-runes/samples/style-update/_config.js
new file mode 100644
index 000000000000..f0b7f2648e6d
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/style-update/_config.js
@@ -0,0 +1,64 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+const style_1 = 'invalid-key:0; margin:4px;;color: green ;color:blue ';
+const style_2 = ' other-key : 0 ; padding:2px; COLOR:green; color: blue';
+
+// https://github.com/sveltejs/svelte/issues/15309
+export default test({
+ props: {
+ style: style_1
+ },
+
+ html: `
+
+
+
+
+
+ `,
+
+ async test({ assert, target, component }) {
+ component.style = style_2;
+ flushSync();
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+
+
+
+
+
+ `
+ );
+
+ component.style = '';
+ flushSync();
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+
+
+
+
+
+ `
+ );
+
+ component.style = null;
+ flushSync();
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+
+
+
+
+
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/style-update/main.svelte b/packages/svelte/tests/runtime-runes/samples/style-update/main.svelte
new file mode 100644
index 000000000000..d29590d67035
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/style-update/main.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
From 30562b87802c7c814229c2774d2b264a89fda0d4 Mon Sep 17 00:00:00 2001
From: adiGuba
Date: Thu, 6 Mar 2025 01:34:10 +0100
Subject: [PATCH 09/97] chore: rewrite set_style() to handle directives
(#15418)
* add style attribute when needed
* set_style()
* to_style()
* remove `style=""`
* use cssTest for perfs
* base test
* test
* changeset
* revert dom.style.cssText
* format name
* use style.cssText + adapt test
* Apply suggestions from code review
suggestions from dummdidumm
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
* fix priority
* lint
* yawn
* update test
* we can simplify some stuff now
* simplify
* more
* simplify some more
* more
* more
* more
* more
* more
* remove continue
* tweak
* tweak
* tweak
* skip hash argument where possible
* tweak
* tweak
* tweak
* tweak
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Rich Harris
---
.changeset/strange-planes-shout.md | 5 +
.../src/compiler/phases/2-analyze/index.js | 26 +-
.../client/visitors/RegularElement.js | 230 ++++++++----------
.../client/visitors/SvelteElement.js | 34 +--
.../client/visitors/shared/element.js | 136 +++++------
.../server/visitors/shared/element.js | 185 ++++++--------
.../client/dom/elements/attributes.js | 17 +-
.../src/internal/client/dom/elements/style.js | 63 +++--
.../src/internal/client/dom/operations.js | 2 +-
packages/svelte/src/internal/server/index.js | 46 ++--
.../svelte/src/internal/shared/attributes.js | 137 ++++++++++-
.../_config.js | 2 +-
.../_config.js | 2 +-
.../main.svelte | 2 +-
.../samples/dynamic-style-attr/_config.js | 4 +-
.../samples/dynamic-style-attr/main.svelte | 2 +-
.../style-directive-mutations/_config.js | 95 ++++++++
.../style-directive-mutations/main.svelte | 54 ++++
.../samples/style-update/_config.js | 11 +-
19 files changed, 654 insertions(+), 399 deletions(-)
create mode 100644 .changeset/strange-planes-shout.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte
diff --git a/.changeset/strange-planes-shout.md b/.changeset/strange-planes-shout.md
new file mode 100644
index 000000000000..58ef25274022
--- /dev/null
+++ b/.changeset/strange-planes-shout.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: make `style:` directive and CSS handling more robust
diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index 322293bf6b91..1f636c32df6d 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -769,17 +769,24 @@ export function analyze_component(root, source, options) {
}
let has_class = false;
+ let has_style = false;
let has_spread = false;
let has_class_directive = false;
+ let has_style_directive = false;
for (const attribute of node.attributes) {
// The spread method appends the hash to the end of the class attribute on its own
if (attribute.type === 'SpreadAttribute') {
has_spread = true;
break;
+ } else if (attribute.type === 'Attribute') {
+ has_class ||= attribute.name.toLowerCase() === 'class';
+ has_style ||= attribute.name.toLowerCase() === 'style';
+ } else if (attribute.type === 'ClassDirective') {
+ has_class_directive = true;
+ } else if (attribute.type === 'StyleDirective') {
+ has_style_directive = true;
}
- has_class_directive ||= attribute.type === 'ClassDirective';
- has_class ||= attribute.type === 'Attribute' && attribute.name.toLowerCase() === 'class';
}
// We need an empty class to generate the set_class() or class="" correctly
@@ -796,6 +803,21 @@ export function analyze_component(root, source, options) {
])
);
}
+
+ // We need an empty style to generate the set_style() correctly
+ if (!has_spread && !has_style && has_style_directive) {
+ node.attributes.push(
+ create_attribute('style', -1, -1, [
+ {
+ type: 'Text',
+ data: '',
+ raw: '',
+ start: -1,
+ end: -1
+ }
+ ])
+ );
+ }
}
// TODO
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
index 3dd303921369..6122dc4e0e66 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
@@ -1,4 +1,4 @@
-/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
+/** @import { ArrayExpression, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { SourceLocation } from '#shared' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
@@ -20,9 +20,9 @@ import { build_getter } from '../utils.js';
import {
get_attribute_name,
build_attribute_value,
- build_style_directives,
build_set_attributes,
- build_set_class
+ build_set_class,
+ build_set_style
} from './shared/element.js';
import { process_children } from './shared/fragment.js';
import {
@@ -215,13 +215,18 @@ export function RegularElement(node, context) {
const node_id = context.state.node;
- // Then do attributes
- let is_attributes_reactive = has_spread;
-
if (has_spread) {
const attributes_id = b.id(context.state.scope.generate('attributes'));
- build_set_attributes(attributes, class_directives, context, node, node_id, attributes_id);
+ build_set_attributes(
+ attributes,
+ class_directives,
+ style_directives,
+ context,
+ node,
+ node_id,
+ attributes_id
+ );
// If value binding exists, that one takes care of calling $.init_select
if (node.name === 'select' && !bindings.has('value')) {
@@ -262,11 +267,13 @@ export function RegularElement(node, context) {
}
const name = get_attribute_name(node, attribute);
+
if (
!is_custom_element &&
!cannot_be_set_statically(attribute.name) &&
(attribute.value === true || is_text_attribute(attribute)) &&
- (name !== 'class' || class_directives.length === 0)
+ (name !== 'class' || class_directives.length === 0) &&
+ (name !== 'style' || style_directives.length === 0)
) {
let value = is_text_attribute(attribute) ? attribute.value[0].data : true;
@@ -287,27 +294,30 @@ export function RegularElement(node, context) {
}`
);
}
- continue;
- }
+ } else if (name === 'autofocus') {
+ let { value } = build_attribute_value(attribute.value, context);
+ context.state.init.push(b.stmt(b.call('$.autofocus', node_id, value)));
+ } else if (name === 'class') {
+ const is_html = context.state.metadata.namespace === 'html' && node.name !== 'svg';
+ build_set_class(node, node_id, attribute, class_directives, context, is_html);
+ } else if (name === 'style') {
+ build_set_style(node_id, attribute, style_directives, context);
+ } else if (is_custom_element) {
+ build_custom_element_attribute_update_assignment(node_id, attribute, context);
+ } else {
+ const { value, has_state } = build_attribute_value(
+ attribute.value,
+ context,
+ (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value)
+ );
+
+ const update = build_element_attribute_update(node, node_id, name, value, attributes);
- const is =
- is_custom_element && name !== 'class'
- ? build_custom_element_attribute_update_assignment(node_id, attribute, context)
- : build_element_attribute_update_assignment(
- node,
- node_id,
- attribute,
- attributes,
- class_directives,
- context
- );
- if (is) is_attributes_reactive = true;
+ (has_state ? context.state.update : context.state.init).push(b.stmt(update));
+ }
}
}
- // style directives must be applied last since they could override class/style attributes
- build_style_directives(style_directives, node_id, context, is_attributes_reactive);
-
if (
is_load_error_element(node.name) &&
(has_spread || has_use || lookup.has('onload') || lookup.has('onerror'))
@@ -519,6 +529,36 @@ export function build_class_directives_object(class_directives, context) {
return b.object(properties);
}
+/**
+ * @param {AST.StyleDirective[]} style_directives
+ * @param {ComponentContext} context
+ * @return {ObjectExpression | ArrayExpression}}
+ */
+export function build_style_directives_object(style_directives, context) {
+ let normal_properties = [];
+ let important_properties = [];
+
+ for (const directive of style_directives) {
+ const expression =
+ directive.value === true
+ ? build_getter({ name: directive.name, type: 'Identifier' }, context.state)
+ : build_attribute_value(directive.value, context, (value, metadata) =>
+ metadata.has_call ? get_expression_id(context.state, value) : value
+ ).value;
+ const property = b.init(directive.name, expression);
+
+ if (directive.modifiers.includes('important')) {
+ important_properties.push(property);
+ } else {
+ normal_properties.push(property);
+ }
+ }
+
+ return important_properties.length
+ ? b.array([b.object(normal_properties), b.object(important_properties)])
+ : b.object(normal_properties);
+}
+
/**
* Serializes an assignment to an element property by adding relevant statements to either only
* the init or the the init and update arrays, depending on whether or not the value is dynamic.
@@ -543,73 +583,29 @@ export function build_class_directives_object(class_directives, context) {
* Returns true if attribute is deemed reactive, false otherwise.
* @param {AST.RegularElement} element
* @param {Identifier} node_id
- * @param {AST.Attribute} attribute
+ * @param {string} name
+ * @param {Expression} value
* @param {Array} attributes
- * @param {AST.ClassDirective[]} class_directives
- * @param {ComponentContext} context
- * @returns {boolean}
*/
-function build_element_attribute_update_assignment(
- element,
- node_id,
- attribute,
- attributes,
- class_directives,
- context
-) {
- const state = context.state;
- const name = get_attribute_name(element, attribute);
- const is_svg = context.state.metadata.namespace === 'svg' || element.name === 'svg';
- const is_mathml = context.state.metadata.namespace === 'mathml';
-
- const is_autofocus = name === 'autofocus';
-
- let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
- metadata.has_call
- ? // if it's autofocus we will not add this to a template effect so we don't want to get the expression id
- // but separately memoize the expression
- is_autofocus
- ? memoize_expression(state, value)
- : get_expression_id(state, value)
- : value
- );
+function build_element_attribute_update(element, node_id, name, value, attributes) {
+ if (name === 'muted') {
+ // Special case for Firefox who needs it set as a property in order to work
+ return b.assignment('=', b.member(node_id, b.id('muted')), value);
+ }
- if (is_autofocus) {
- state.init.push(b.stmt(b.call('$.autofocus', node_id, value)));
- return false;
+ if (name === 'value') {
+ return b.call('$.set_value', node_id, value);
}
- // Special case for Firefox who needs it set as a property in order to work
- if (name === 'muted') {
- if (!has_state) {
- state.init.push(b.stmt(b.assignment('=', b.member(node_id, b.id('muted')), value)));
- return false;
- }
- state.update.push(b.stmt(b.assignment('=', b.member(node_id, b.id('muted')), value)));
- return false;
+ if (name === 'checked') {
+ return b.call('$.set_checked', node_id, value);
}
- /** @type {Statement} */
- let update;
+ if (name === 'selected') {
+ return b.call('$.set_selected', node_id, value);
+ }
- if (name === 'class') {
- return build_set_class(
- element,
- node_id,
- attribute,
- value,
- has_state,
- class_directives,
- context,
- !is_svg && !is_mathml
- );
- } else if (name === 'value') {
- update = b.stmt(b.call('$.set_value', node_id, value));
- } else if (name === 'checked') {
- update = b.stmt(b.call('$.set_checked', node_id, value));
- } else if (name === 'selected') {
- update = b.stmt(b.call('$.set_selected', node_id, value));
- } else if (
+ if (
// If we would just set the defaultValue property, it would override the value property,
// because it is set in the template which implicitly means it's also setting the default value,
// and if one updates the default value while the input is pristine it will also update the
@@ -620,62 +616,49 @@ function build_element_attribute_update_assignment(
) ||
(element.name === 'textarea' && element.fragment.nodes.length > 0))
) {
- update = b.stmt(b.call('$.set_default_value', node_id, value));
- } else if (
+ return b.call('$.set_default_value', node_id, value);
+ }
+
+ if (
// See defaultValue comment
name === 'defaultChecked' &&
attributes.some(
(attr) => attr.type === 'Attribute' && attr.name === 'checked' && attr.value === true
)
) {
- update = b.stmt(b.call('$.set_default_checked', node_id, value));
- } else if (is_dom_property(name)) {
- update = b.stmt(b.assignment('=', b.member(node_id, name), value));
- } else {
- const callee = name.startsWith('xlink') ? '$.set_xlink_attribute' : '$.set_attribute';
- update = b.stmt(
- b.call(
- callee,
- node_id,
- b.literal(name),
- value,
- is_ignored(element, 'hydration_attribute_changed') && b.true
- )
- );
+ return b.call('$.set_default_checked', node_id, value);
}
- if (has_state) {
- state.update.push(update);
- return true;
- } else {
- state.init.push(update);
- return false;
+ if (is_dom_property(name)) {
+ return b.assignment('=', b.member(node_id, name), value);
}
+
+ return b.call(
+ name.startsWith('xlink') ? '$.set_xlink_attribute' : '$.set_attribute',
+ node_id,
+ b.literal(name),
+ value,
+ is_ignored(element, 'hydration_attribute_changed') && b.true
+ );
}
/**
- * Like `build_element_attribute_update_assignment` but without any special attribute treatment.
+ * Like `build_element_attribute_update` but without any special attribute treatment.
* @param {Identifier} node_id
* @param {AST.Attribute} attribute
* @param {ComponentContext} context
- * @returns {boolean}
*/
function build_custom_element_attribute_update_assignment(node_id, attribute, context) {
- const state = context.state;
- const name = attribute.name; // don't lowercase, as we set the element's property, which might be case sensitive
- let { value, has_state } = build_attribute_value(attribute.value, context);
+ const { value, has_state } = build_attribute_value(attribute.value, context);
- const update = b.stmt(b.call('$.set_custom_element_data', node_id, b.literal(name), value));
+ // don't lowercase name, as we set the element's property, which might be case sensitive
+ const call = b.call('$.set_custom_element_data', node_id, b.literal(attribute.name), value);
- if (has_state) {
- // this is different from other updates — it doesn't get grouped,
- // because set_custom_element_data may not be idempotent
- state.init.push(b.stmt(b.call('$.template_effect', b.thunk(update.expression))));
- return true;
- } else {
- state.init.push(update);
- return false;
- }
+ // this is different from other updates — it doesn't get grouped,
+ // because set_custom_element_data may not be idempotent
+ const update = has_state ? b.call('$.template_effect', b.thunk(call)) : call;
+
+ context.state.init.push(b.stmt(update));
}
/**
@@ -686,7 +669,6 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co
* @param {Identifier} node_id
* @param {AST.Attribute} attribute
* @param {ComponentContext} context
- * @returns {boolean}
*/
function build_element_special_value_attribute(element, node_id, attribute, context) {
const state = context.state;
@@ -699,7 +681,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
metadata.has_call
? // if is a select with value we will also invoke `init_select` which need a reference before the template effect so we memoize separately
is_select_with_value
- ? memoize_expression(context.state, value)
+ ? memoize_expression(state, value)
: get_expression_id(state, value)
: value
);
@@ -743,9 +725,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
value,
update
);
- return true;
} else {
state.init.push(update);
- return false;
}
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js
index 3250c2439290..115eb6ccc11e 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js
@@ -5,12 +5,7 @@ import { dev, locator } from '../../../../state.js';
import { is_text_attribute } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js';
import { determine_namespace_for_children } from '../../utils.js';
-import {
- build_attribute_value,
- build_set_attributes,
- build_set_class,
- build_style_directives
-} from './shared/element.js';
+import { build_attribute_value, build_set_attributes, build_set_class } from './shared/element.js';
import { build_render_statement, get_expression_id } from './shared/utils.js';
/**
@@ -77,40 +72,22 @@ export function SvelteElement(node, context) {
// Let bindings first, they can be used on attributes
context.state.init.push(...lets); // create computeds in the outer context; the dynamic element is the single child of this slot
- // Then do attributes
- let is_attributes_reactive = false;
-
if (
attributes.length === 1 &&
attributes[0].type === 'Attribute' &&
attributes[0].name.toLowerCase() === 'class' &&
is_text_attribute(attributes[0])
) {
- // special case when there only a class attribute, without call expression
- let { value, has_state } = build_attribute_value(
- attributes[0].value,
- context,
- (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value)
- );
-
- is_attributes_reactive = build_set_class(
- node,
- element_id,
- attributes[0],
- value,
- has_state,
- class_directives,
- inner_context,
- false
- );
+ build_set_class(node, element_id, attributes[0], class_directives, inner_context, false);
} else if (attributes.length) {
const attributes_id = b.id(context.state.scope.generate('attributes'));
// Always use spread because we don't know whether the element is a custom element or not,
// therefore we need to do the "how to set an attribute" logic at runtime.
- is_attributes_reactive = build_set_attributes(
+ build_set_attributes(
attributes,
class_directives,
+ style_directives,
inner_context,
node,
element_id,
@@ -118,9 +95,6 @@ export function SvelteElement(node, context) {
);
}
- // style directives must be applied last since they could override class/style attributes
- build_style_directives(style_directives, element_id, inner_context, is_attributes_reactive);
-
const get_tag = b.thunk(/** @type {Expression} */ (context.visit(node.tag)));
if (dev) {
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
index db8f2e4aa083..e0eb04d8236a 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
@@ -1,4 +1,4 @@
-/** @import { Expression, Identifier, ObjectExpression } from 'estree' */
+/** @import { ArrayExpression, Expression, Identifier, ObjectExpression } from 'estree' */
/** @import { AST, ExpressionMetadata } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../../types' */
import { escape_html } from '../../../../../../escaping.js';
@@ -6,13 +6,13 @@ import { normalize_attribute } from '../../../../../../utils.js';
import { is_ignored } from '../../../../../state.js';
import { is_event_attribute } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
-import { build_getter } from '../../utils.js';
-import { build_class_directives_object } from '../RegularElement.js';
+import { build_class_directives_object, build_style_directives_object } from '../RegularElement.js';
import { build_template_chunk, get_expression_id } from './utils.js';
/**
* @param {Array} attributes
* @param {AST.ClassDirective[]} class_directives
+ * @param {AST.StyleDirective[]} style_directives
* @param {ComponentContext} context
* @param {AST.RegularElement | AST.SvelteElement} element
* @param {Identifier} element_id
@@ -21,6 +21,7 @@ import { build_template_chunk, get_expression_id } from './utils.js';
export function build_set_attributes(
attributes,
class_directives,
+ style_directives,
context,
element,
element_id,
@@ -79,6 +80,18 @@ export function build_set_attributes(
class_directives.find((directive) => directive.metadata.expression.has_state) !== null;
}
+ if (style_directives.length) {
+ values.push(
+ b.prop(
+ 'init',
+ b.array([b.id('$.STYLE')]),
+ build_style_directives_object(style_directives, context)
+ )
+ );
+
+ is_dynamic ||= style_directives.some((directive) => directive.metadata.expression.has_state);
+ }
+
const call = b.call(
'$.set_attributes',
element_id,
@@ -94,54 +107,8 @@ export function build_set_attributes(
context.state.init.push(b.let(attributes_id));
const update = b.stmt(b.assignment('=', attributes_id, call));
context.state.update.push(update);
- return true;
- }
-
- context.state.init.push(b.stmt(call));
- return false;
-}
-
-/**
- * Serializes each style directive into something like `$.set_style(element, style_property, value)`
- * and adds it either to init or update, depending on whether or not the value or the attributes are dynamic.
- * @param {AST.StyleDirective[]} style_directives
- * @param {Identifier} element_id
- * @param {ComponentContext} context
- * @param {boolean} is_attributes_reactive
- */
-export function build_style_directives(
- style_directives,
- element_id,
- context,
- is_attributes_reactive
-) {
- const state = context.state;
-
- for (const directive of style_directives) {
- const { has_state } = directive.metadata.expression;
-
- let value =
- directive.value === true
- ? build_getter({ name: directive.name, type: 'Identifier' }, context.state)
- : build_attribute_value(directive.value, context, (value, metadata) =>
- metadata.has_call ? get_expression_id(context.state, value) : value
- ).value;
-
- const update = b.stmt(
- b.call(
- '$.set_style',
- element_id,
- b.literal(directive.name),
- value,
- /** @type {Expression} */ (directive.modifiers.includes('important') ? b.true : undefined)
- )
- );
-
- if (has_state || is_attributes_reactive) {
- state.update.push(update);
- } else {
- state.init.push(update);
- }
+ } else {
+ context.state.init.push(b.stmt(call));
}
}
@@ -189,24 +156,16 @@ export function get_attribute_name(element, attribute) {
/**
* @param {AST.RegularElement | AST.SvelteElement} element
* @param {Identifier} node_id
- * @param {AST.Attribute | null} attribute
- * @param {Expression} value
- * @param {boolean} has_state
+ * @param {AST.Attribute} attribute
* @param {AST.ClassDirective[]} class_directives
* @param {ComponentContext} context
* @param {boolean} is_html
- * @returns {boolean}
*/
-export function build_set_class(
- element,
- node_id,
- attribute,
- value,
- has_state,
- class_directives,
- context,
- is_html
-) {
+export function build_set_class(element, node_id, attribute, class_directives, context, is_html) {
+ let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
+ metadata.has_call ? get_expression_id(context.state, value) : value
+ );
+
if (attribute && attribute.metadata.needs_clsx) {
value = b.call('$.clsx', value);
}
@@ -265,13 +224,48 @@ export function build_set_class(
set_class = b.assignment('=', previous_id, set_class);
}
- const update = b.stmt(set_class);
+ (has_state ? context.state.update : context.state.init).push(b.stmt(set_class));
+}
- if (has_state) {
- context.state.update.push(update);
- return true;
+/**
+ * @param {Identifier} node_id
+ * @param {AST.Attribute} attribute
+ * @param {AST.StyleDirective[]} style_directives
+ * @param {ComponentContext} context
+ */
+export function build_set_style(node_id, attribute, style_directives, context) {
+ let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
+ metadata.has_call ? get_expression_id(context.state, value) : value
+ );
+
+ /** @type {Identifier | undefined} */
+ let previous_id;
+
+ /** @type {ObjectExpression | Identifier | undefined} */
+ let prev;
+
+ /** @type {ArrayExpression | ObjectExpression | undefined} */
+ let next;
+
+ if (style_directives.length) {
+ next = build_style_directives_object(style_directives, context);
+ has_state ||= style_directives.some((d) => d.metadata.expression.has_state);
+
+ if (has_state) {
+ previous_id = b.id(context.state.scope.generate('styles'));
+ context.state.init.push(b.declaration('let', [b.declarator(previous_id)]));
+ prev = previous_id;
+ } else {
+ prev = b.object([]);
+ }
+ }
+
+ /** @type {Expression} */
+ let set_style = b.call('$.set_style', node_id, value, prev, next);
+
+ if (previous_id) {
+ set_style = b.assignment('=', previous_id, set_style);
}
- context.state.init.push(update);
- return false;
+ (has_state ? context.state.update : context.state.init).push(b.stmt(set_style));
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
index 281d8f061768..4a5becfb2fc6 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
@@ -1,4 +1,4 @@
-/** @import { Expression, Literal, ObjectExpression } from 'estree' */
+/** @import { ArrayExpression, Expression, Literal, ObjectExpression } from 'estree' */
/** @import { AST, Namespace } from '#compiler' */
/** @import { ComponentContext, ComponentServerTransformState } from '../../types.js' */
import {
@@ -48,9 +48,6 @@ export function build_element_attributes(node, context) {
let content = null;
let has_spread = false;
- // Use the index to keep the attributes order which is important for spreading
- let class_index = -1;
- let style_index = -1;
let events_to_capture = new Set();
for (const attribute of node.attributes) {
@@ -86,7 +83,6 @@ export function build_element_attributes(node, context) {
// the defaultValue/defaultChecked properties don't exist as attributes
} else if (attribute.name !== 'defaultValue' && attribute.name !== 'defaultChecked') {
if (attribute.name === 'class') {
- class_index = attributes.length;
if (attribute.metadata.needs_clsx) {
attributes.push({
...attribute,
@@ -102,10 +98,6 @@ export function build_element_attributes(node, context) {
attributes.push(attribute);
}
} else {
- if (attribute.name === 'style') {
- style_index = attributes.length;
- }
-
attributes.push(attribute);
}
}
@@ -212,41 +204,30 @@ export function build_element_attributes(node, context) {
}
}
- if ((node.metadata.scoped || class_directives.length) && !has_spread) {
- const class_attribute = build_to_class(
- node.metadata.scoped ? context.state.analysis.css.hash : null,
- class_directives,
- /** @type {AST.Attribute | null} */ (attributes[class_index] ?? null)
- );
- if (class_index === -1) {
- attributes.push(class_attribute);
- }
- }
-
- if (style_directives.length > 0 && !has_spread) {
- build_style_directives(
- style_directives,
- /** @type {AST.Attribute | null} */ (attributes[style_index] ?? null),
- context
- );
- if (style_index > -1) {
- attributes.splice(style_index, 1);
- }
- }
-
if (has_spread) {
build_element_spread_attributes(node, attributes, style_directives, class_directives, context);
} else {
+ const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null;
+
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
- if (attribute.value === true || is_text_attribute(attribute)) {
- const name = get_attribute_name(node, attribute);
- const literal_value = /** @type {Literal} */ (
+ const name = get_attribute_name(node, attribute);
+ const can_use_literal =
+ (name !== 'class' || class_directives.length === 0) &&
+ (name !== 'style' || style_directives.length === 0);
+
+ if (can_use_literal && (attribute.value === true || is_text_attribute(attribute))) {
+ let literal_value = /** @type {Literal} */ (
build_attribute_value(
attribute.value,
context,
WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name)
)
).value;
+
+ if (name === 'class' && css_hash) {
+ literal_value = (String(literal_value) + ' ' + css_hash).trim();
+ }
+
if (name !== 'class' || literal_value) {
context.state.template.push(
b.literal(
@@ -258,10 +239,10 @@ export function build_element_attributes(node, context) {
)
);
}
+
continue;
}
- const name = get_attribute_name(node, attribute);
const value = build_attribute_value(
attribute.value,
context,
@@ -269,8 +250,15 @@ export function build_element_attributes(node, context) {
);
// pre-escape and inline literal attributes :
- if (value.type === 'Literal' && typeof value.value === 'string') {
+ if (can_use_literal && value.type === 'Literal' && typeof value.value === 'string') {
+ if (name === 'class' && css_hash) {
+ value.value = (value.value + ' ' + css_hash).trim();
+ }
context.state.template.push(b.literal(` ${name}="${escape_html(value.value, true)}"`));
+ } else if (name === 'class') {
+ context.state.template.push(build_attr_class(class_directives, value, context, css_hash));
+ } else if (name === 'style') {
+ context.state.template.push(build_attr_style(style_directives, value, context));
} else {
context.state.template.push(
b.call('$.attr', b.literal(name), value, is_boolean_attribute(name) && b.true)
@@ -379,100 +367,79 @@ function build_element_spread_attributes(
/**
*
- * @param {string | null} hash
* @param {AST.ClassDirective[]} class_directives
- * @param {AST.Attribute | null} class_attribute
- * @returns
+ * @param {Expression} expression
+ * @param {ComponentContext} context
+ * @param {string | null} hash
*/
-function build_to_class(hash, class_directives, class_attribute) {
- if (class_attribute === null) {
- class_attribute = create_attribute('class', -1, -1, []);
- }
-
+function build_attr_class(class_directives, expression, context, hash) {
/** @type {ObjectExpression | undefined} */
- let classes;
+ let directives;
if (class_directives.length) {
- classes = b.object(
+ directives = b.object(
class_directives.map((directive) =>
- b.prop('init', b.literal(directive.name), directive.expression)
+ b.prop(
+ 'init',
+ b.literal(directive.name),
+ /** @type {Expression} */ (context.visit(directive.expression, context.state))
+ )
)
);
}
- /** @type {Expression} */
- let class_name;
-
- if (class_attribute.value === true) {
- class_name = b.literal('');
- } else if (Array.isArray(class_attribute.value)) {
- if (class_attribute.value.length === 0) {
- class_name = b.null;
- } else {
- class_name = class_attribute.value
- .map((val) => (val.type === 'Text' ? b.literal(val.data) : val.expression))
- .reduce((left, right) => b.binary('+', left, right));
- }
- } else {
- class_name = class_attribute.value.expression;
- }
+ let css_hash;
- /** @type {Expression} */
- let expression;
-
- if (
- hash &&
- !classes &&
- class_name.type === 'Literal' &&
- (class_name.value === null || class_name.value === '' || typeof class_name.value === 'string')
- ) {
- if (class_name.value === null || class_name.value === '') {
- expression = b.literal(hash);
+ if (hash) {
+ if (expression.type === 'Literal' && typeof expression.value === 'string') {
+ expression.value = (expression.value + ' ' + hash).trim();
} else {
- expression = b.literal(escape_html(class_name.value, true) + ' ' + hash);
+ css_hash = b.literal(hash);
}
- } else {
- expression = b.call('$.to_class', class_name, b.literal(hash), classes);
}
- class_attribute.value = {
- type: 'ExpressionTag',
- start: -1,
- end: -1,
- expression: expression,
- metadata: {
- expression: create_expression_metadata()
- }
- };
-
- return class_attribute;
+ return b.call('$.attr_class', expression, css_hash, directives);
}
/**
+ *
* @param {AST.StyleDirective[]} style_directives
- * @param {AST.Attribute | null} style_attribute
+ * @param {Expression} expression
* @param {ComponentContext} context
*/
-function build_style_directives(style_directives, style_attribute, context) {
- const styles = style_directives.map((directive) => {
- let value =
- directive.value === true
- ? b.id(directive.name)
- : build_attribute_value(directive.value, context, true);
- if (directive.modifiers.includes('important')) {
- value = b.binary('+', value, b.literal(' !important'));
+function build_attr_style(style_directives, expression, context) {
+ /** @type {ArrayExpression | ObjectExpression | undefined} */
+ let directives;
+
+ if (style_directives.length) {
+ let normal_properties = [];
+ let important_properties = [];
+
+ for (const directive of style_directives) {
+ const expression =
+ directive.value === true
+ ? b.id(directive.name)
+ : build_attribute_value(directive.value, context, true);
+
+ let name = directive.name;
+ if (name[0] !== '-' || name[1] !== '-') {
+ name = name.toLowerCase();
+ }
+
+ const property = b.init(directive.name, expression);
+ if (directive.modifiers.includes('important')) {
+ important_properties.push(property);
+ } else {
+ normal_properties.push(property);
+ }
}
- return b.init(directive.name, value);
- });
-
- const arg =
- style_attribute === null
- ? b.object(styles)
- : b.call(
- '$.merge_styles',
- build_attribute_value(style_attribute.value, context, true),
- b.object(styles)
- );
- context.state.template.push(b.call('$.add_styles', arg));
+ if (important_properties.length) {
+ directives = b.array([b.object(normal_properties), b.object(important_properties)]);
+ } else {
+ directives = b.object(normal_properties);
+ }
+ }
+
+ return b.call('$.attr_style', expression, directives);
}
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index cebc9173bab2..5a5d5d7c9b20 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -15,6 +15,7 @@ import {
} from '../../runtime.js';
import { clsx } from '../../../shared/attributes.js';
import { set_class } from './class.js';
+import { set_style } from './style.js';
import { NAMESPACE_HTML } from '../../../../constants.js';
export const CLASS = Symbol('class');
@@ -177,11 +178,6 @@ export function set_attribute(element, attribute, value, skip_warning) {
if (attributes[attribute] === (attributes[attribute] = value)) return;
- if (attribute === 'style' && '__styles' in element) {
- // reset styles to force style: directive to update
- element.__styles = {};
- }
-
if (attribute === 'loading') {
// @ts-expect-error
element[LOADING_ATTR_SYMBOL] = value;
@@ -297,6 +293,10 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
next.class = null; /* force call to set_class() */
}
+ if (next[STYLE]) {
+ next.style ??= null; /* force call to set_style() */
+ }
+
var setters = get_setters(element);
// since key is captured we use const
@@ -331,6 +331,13 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
continue;
}
+ if (key === 'style') {
+ set_style(element, value, prev?.[STYLE], next[STYLE]);
+ current[key] = value;
+ current[STYLE] = next[STYLE];
+ continue;
+ }
+
var prev_value = current[key];
if (value === prev_value) continue;
diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js
index 34531029c9c6..3e05eec30efa 100644
--- a/packages/svelte/src/internal/client/dom/elements/style.js
+++ b/packages/svelte/src/internal/client/dom/elements/style.js
@@ -1,22 +1,57 @@
+import { to_style } from '../../../shared/attributes.js';
+import { hydrating } from '../hydration.js';
+
/**
- * @param {HTMLElement} dom
- * @param {string} key
- * @param {string} value
- * @param {boolean} [important]
+ * @param {Element & ElementCSSInlineStyle} dom
+ * @param {Record} prev
+ * @param {Record} next
+ * @param {string} [priority]
*/
-export function set_style(dom, key, value, important) {
- // @ts-expect-error
- var styles = (dom.__styles ??= {});
+function update_styles(dom, prev = {}, next, priority) {
+ for (var key in next) {
+ var value = next[key];
- if (styles[key] === value) {
- return;
+ if (prev[key] !== value) {
+ if (next[key] == null) {
+ dom.style.removeProperty(key);
+ } else {
+ dom.style.setProperty(key, value, priority);
+ }
+ }
}
+}
- styles[key] = value;
+/**
+ * @param {Element & ElementCSSInlineStyle} dom
+ * @param {string | null} value
+ * @param {Record | [Record, Record]} [prev_styles]
+ * @param {Record | [Record, Record]} [next_styles]
+ */
+export function set_style(dom, value, prev_styles, next_styles) {
+ // @ts-expect-error
+ var prev = dom.__style;
+
+ if (hydrating || prev !== value) {
+ var next_style_attr = to_style(value, next_styles);
- if (value == null) {
- dom.style.removeProperty(key);
- } else {
- dom.style.setProperty(key, value, important ? 'important' : '');
+ if (!hydrating || next_style_attr !== dom.getAttribute('style')) {
+ if (next_style_attr == null) {
+ dom.removeAttribute('style');
+ } else {
+ dom.style.cssText = next_style_attr;
+ }
+ }
+
+ // @ts-expect-error
+ dom.__style = value;
+ } else if (next_styles) {
+ if (Array.isArray(next_styles)) {
+ update_styles(dom, prev_styles?.[0], next_styles[0]);
+ update_styles(dom, prev_styles?.[1], next_styles[1], 'important');
+ } else {
+ update_styles(dom, prev_styles, next_styles);
+ }
}
+
+ return next_styles;
}
diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js
index f6ac92456e78..0ad9045b2062 100644
--- a/packages/svelte/src/internal/client/dom/operations.js
+++ b/packages/svelte/src/internal/client/dom/operations.js
@@ -48,7 +48,7 @@ export function init_operations() {
// @ts-expect-error
element_prototype.__attributes = null;
// @ts-expect-error
- element_prototype.__styles = null;
+ element_prototype.__style = undefined;
// @ts-expect-error
element_prototype.__e = undefined;
diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js
index 2591dbe4eaab..6098b496c5ac 100644
--- a/packages/svelte/src/internal/server/index.js
+++ b/packages/svelte/src/internal/server/index.js
@@ -2,7 +2,7 @@
/** @import { Component, Payload, RenderOutput } from '#server' */
/** @import { Store } from '#shared' */
export { FILENAME, HMR } from '../../constants.js';
-import { attr, clsx, to_class } from '../shared/attributes.js';
+import { attr, clsx, to_class, to_style } from '../shared/attributes.js';
import { is_promise, noop } from '../shared/utils.js';
import { subscribe_to_store } from '../../store/utils.js';
import {
@@ -210,9 +210,7 @@ export function css_props(payload, is_html, props, component, dynamic = false) {
*/
export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) {
if (styles) {
- attrs.style = attrs.style
- ? style_object_to_string(merge_styles(/** @type {string} */ (attrs.style), styles))
- : style_object_to_string(styles);
+ attrs.style = to_style(attrs.style, styles);
}
if (attrs.class) {
@@ -286,35 +284,23 @@ function style_object_to_string(style_object) {
.join(' ');
}
-/** @param {Record} style_object */
-export function add_styles(style_object) {
- const styles = style_object_to_string(style_object);
- return styles ? ` style="${styles}"` : '';
+/**
+ * @param {any} value
+ * @param {string | undefined} [hash]
+ * @param {Record} [directives]
+ */
+export function attr_class(value, hash, directives) {
+ var result = to_class(value, hash, directives);
+ return result ? ` class="${escape_html(result, true)}"` : '';
}
/**
- * @param {string} attribute
- * @param {Record} styles
+ * @param {any} value
+ * @param {Record|[Record,Record]} [directives]
*/
-export function merge_styles(attribute, styles) {
- /** @type {Record} */
- var merged = {};
-
- if (attribute) {
- for (var declaration of attribute.split(';')) {
- var i = declaration.indexOf(':');
- var name = declaration.slice(0, i).trim();
- var value = declaration.slice(i + 1).trim();
-
- if (name !== '') merged[name] = value;
- }
- }
-
- for (name in styles) {
- merged[name] = styles[name];
- }
-
- return merged;
+export function attr_style(value, directives) {
+ var result = to_style(value, directives);
+ return result ? ` style="${escape_html(result, true)}"` : '';
}
/**
@@ -549,7 +535,7 @@ export function props_id(payload) {
return uid;
}
-export { attr, clsx, to_class };
+export { attr, clsx };
export { html } from './blocks/html.js';
diff --git a/packages/svelte/src/internal/shared/attributes.js b/packages/svelte/src/internal/shared/attributes.js
index 89cc17e51b9d..c8758c1d4d4d 100644
--- a/packages/svelte/src/internal/shared/attributes.js
+++ b/packages/svelte/src/internal/shared/attributes.js
@@ -22,7 +22,7 @@ const replacements = {
* @returns {string}
*/
export function attr(name, value, is_boolean = false) {
- if (value == null || (!value && is_boolean) || (value === '' && name === 'class')) return '';
+ if (value == null || (!value && is_boolean)) return '';
const normalized = (name in replacements && replacements[name].get(value)) || value;
const assignment = is_boolean ? '' : `="${escape_html(normalized, true)}"`;
return ` ${name}${assignment}`;
@@ -82,3 +82,138 @@ export function to_class(value, hash, directives) {
return classname === '' ? null : classname;
}
+
+/**
+ *
+ * @param {Record} styles
+ * @param {boolean} important
+ */
+function append_styles(styles, important = false) {
+ var separator = important ? ' !important;' : ';';
+ var css = '';
+
+ for (var key in styles) {
+ var value = styles[key];
+ if (value != null && value !== '') {
+ css += ' ' + key + ': ' + value + separator;
+ }
+ }
+
+ return css;
+}
+
+/**
+ * @param {string} name
+ * @returns {string}
+ */
+function to_css_name(name) {
+ if (name[0] !== '-' || name[1] !== '-') {
+ return name.toLowerCase();
+ }
+ return name;
+}
+
+/**
+ * @param {any} value
+ * @param {Record | [Record, Record]} [styles]
+ * @returns {string | null}
+ */
+export function to_style(value, styles) {
+ if (styles) {
+ var new_style = '';
+
+ /** @type {Record | undefined} */
+ var normal_styles;
+
+ /** @type {Record | undefined} */
+ var important_styles;
+
+ if (Array.isArray(styles)) {
+ normal_styles = styles[0];
+ important_styles = styles[1];
+ } else {
+ normal_styles = styles;
+ }
+
+ if (value) {
+ value = String(value)
+ .replaceAll(/\s*\/\*.*?\*\/\s*/g, '')
+ .trim();
+
+ /** @type {boolean | '"' | "'"} */
+ var in_str = false;
+ var in_apo = 0;
+ var in_comment = false;
+
+ var reserved_names = [];
+
+ if (normal_styles) {
+ reserved_names.push(...Object.keys(normal_styles).map(to_css_name));
+ }
+ if (important_styles) {
+ reserved_names.push(...Object.keys(important_styles).map(to_css_name));
+ }
+
+ var start_index = 0;
+ var name_index = -1;
+
+ const len = value.length;
+ for (var i = 0; i < len; i++) {
+ var c = value[i];
+
+ if (in_comment) {
+ if (c === '/' && value[i - 1] === '*') {
+ in_comment = false;
+ }
+ } else if (in_str) {
+ if (in_str === c) {
+ in_str = false;
+ }
+ } else if (c === '/' && value[i + 1] === '*') {
+ in_comment = true;
+ } else if (c === '"' || c === "'") {
+ in_str = c;
+ } else if (c === '(') {
+ in_apo++;
+ } else if (c === ')') {
+ in_apo--;
+ }
+
+ if (!in_comment && in_str === false && in_apo === 0) {
+ if (c === ':' && name_index === -1) {
+ name_index = i;
+ } else if (c === ';' || i === len - 1) {
+ if (name_index !== -1) {
+ var name = to_css_name(value.substring(start_index, name_index).trim());
+
+ if (!reserved_names.includes(name)) {
+ if (c !== ';') {
+ i++;
+ }
+
+ var property = value.substring(start_index, i).trim();
+ new_style += ' ' + property + ';';
+ }
+ }
+
+ start_index = i + 1;
+ name_index = -1;
+ }
+ }
+ }
+ }
+
+ if (normal_styles) {
+ new_style += append_styles(normal_styles);
+ }
+
+ if (important_styles) {
+ new_style += append_styles(important_styles, true);
+ }
+
+ new_style = new_style.trim();
+ return new_style === '' ? null : new_style;
+ }
+
+ return value == null ? null : String(value);
+}
diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-style-directive-spread-and-attr-empty/_config.js b/packages/svelte/tests/runtime-legacy/samples/inline-style-directive-spread-and-attr-empty/_config.js
index 9ff0007c3713..04c9868ac378 100644
--- a/packages/svelte/tests/runtime-legacy/samples/inline-style-directive-spread-and-attr-empty/_config.js
+++ b/packages/svelte/tests/runtime-legacy/samples/inline-style-directive-spread-and-attr-empty/_config.js
@@ -2,6 +2,6 @@ import { test } from '../../test';
export default test({
html: `
-
+
`
});
diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/_config.js b/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/_config.js
index adcdc4706d88..e9965b2b1e26 100644
--- a/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/_config.js
+++ b/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/_config.js
@@ -2,7 +2,7 @@ import { ok, test } from '../../test';
export default test({
html: `
- color: red
+ color: red;
`,
test({ assert, component, target, window }) {
diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/main.svelte b/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/main.svelte
index 35b768547e25..e07adaa1c9d8 100644
--- a/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/main.svelte
+++ b/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/main.svelte
@@ -1,5 +1,5 @@
{styles}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js b/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js
index f6829721795c..20092ddadf34 100644
--- a/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js
@@ -2,7 +2,7 @@ import { test } from '../../test';
import { flushSync } from 'svelte';
export default test({
- html: `Hello world
Make blue Hello worldMake blue Hello worldMake blue Hello worldMake blue color;
-Hello world
+Hello world
color = 'blue'}>Make blue
diff --git a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js
new file mode 100644
index 000000000000..bd76e4e6b929
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js
@@ -0,0 +1,95 @@
+import { flushSync, tick } from 'svelte';
+import { test } from '../../test';
+
+// This test counts mutations on hydration
+// set_style() should not mutate style on hydration, except if mismatch
+export default test({
+ mode: ['server', 'hydrate'],
+
+ server_props: {
+ browser: false
+ },
+
+ props: {
+ browser: true
+ },
+
+ ssrHtml: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ html: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ async test({ target, assert, component, instance }) {
+ flushSync();
+ tick();
+ assert.deepEqual(instance.get_and_clear_mutations(), ['MAIN']);
+
+ let divs = target.querySelectorAll('div');
+
+ // Note : we cannot compare HTML because set_style() use dom.style.cssText
+ // which can alter the format of the attribute...
+
+ divs.forEach((d) => assert.equal(d.style.margin, ''));
+ divs.forEach((d) => assert.equal(d.style.color, 'red'));
+ divs.forEach((d) => assert.equal(d.style.fontSize, '18px'));
+
+ component.margin = '1px';
+ flushSync();
+ assert.deepEqual(
+ instance.get_and_clear_mutations(),
+ ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'],
+ 'margin'
+ );
+ divs.forEach((d) => assert.equal(d.style.margin, '1px'));
+
+ component.color = 'yellow';
+ flushSync();
+ assert.deepEqual(
+ instance.get_and_clear_mutations(),
+ ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'],
+ 'color'
+ );
+ divs.forEach((d) => assert.equal(d.style.color, 'yellow'));
+
+ component.fontSize = '10px';
+ flushSync();
+ assert.deepEqual(
+ instance.get_and_clear_mutations(),
+ ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'],
+ 'fontSize'
+ );
+ divs.forEach((d) => assert.equal(d.style.fontSize, '10px'));
+
+ component.fontSize = null;
+ flushSync();
+ assert.deepEqual(
+ instance.get_and_clear_mutations(),
+ ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'],
+ 'fontSize'
+ );
+ divs.forEach((d) => assert.equal(d.style.fontSize, ''));
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte
new file mode 100644
index 000000000000..ae4da8ae37c7
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/style-update/_config.js b/packages/svelte/tests/runtime-runes/samples/style-update/_config.js
index f0b7f2648e6d..52690a431a4d 100644
--- a/packages/svelte/tests/runtime-runes/samples/style-update/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/style-update/_config.js
@@ -3,6 +3,7 @@ import { test } from '../../test';
const style_1 = 'invalid-key:0; margin:4px;;color: green ;color:blue ';
const style_2 = ' other-key : 0 ; padding:2px; COLOR:green; color: blue';
+const style_2_normalized = 'padding: 2px; color: blue;';
// https://github.com/sveltejs/svelte/issues/15309
export default test({
@@ -10,7 +11,7 @@ export default test({
style: style_1
},
- html: `
+ ssrHtml: `
@@ -25,11 +26,11 @@ export default test({
assert.htmlEqual(
target.innerHTML,
`
-
-
+
+
-
-
+
+
`
);
From ae615ae2acb0a5574547b9161390a2761f60d2d4 Mon Sep 17 00:00:00 2001
From: adiGuba
Date: Thu, 6 Mar 2025 02:52:34 +0100
Subject: [PATCH 10/97] chore: memoize clsx() (alternative) (#15456)
* memoize clsx + directives
* changeset
* unused
* tweak
* tweak changeset
---------
Co-authored-by: Rich Harris
---
.changeset/flat-jars-search.md | 5 +++++
.../3-transform/client/visitors/RegularElement.js | 15 +++++++--------
.../3-transform/client/visitors/shared/element.js | 14 +++++++-------
.../src/internal/client/dom/elements/class.js | 2 +-
4 files changed, 20 insertions(+), 16 deletions(-)
create mode 100644 .changeset/flat-jars-search.md
diff --git a/.changeset/flat-jars-search.md b/.changeset/flat-jars-search.md
new file mode 100644
index 000000000000..fc0de76f95b4
--- /dev/null
+++ b/.changeset/flat-jars-search.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: memoize `clsx` calls
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
index 6122dc4e0e66..9b3ecc922d89 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
@@ -511,22 +511,21 @@ function setup_select_synchronization(value_binding, context) {
/**
* @param {AST.ClassDirective[]} class_directives
* @param {ComponentContext} context
- * @return {ObjectExpression}
+ * @return {ObjectExpression | Identifier}
*/
export function build_class_directives_object(class_directives, context) {
let properties = [];
+ let has_call_or_state = false;
for (const d of class_directives) {
- let expression = /** @type Expression */ (context.visit(d.expression));
-
- if (d.metadata.expression.has_call) {
- expression = get_expression_id(context.state, expression);
- }
-
+ const expression = /** @type Expression */ (context.visit(d.expression));
properties.push(b.init(d.name, expression));
+ has_call_or_state ||= d.metadata.expression.has_call || d.metadata.expression.has_state;
}
- return b.object(properties);
+ const directives = b.object(properties);
+
+ return has_call_or_state ? get_expression_id(context.state, directives) : directives;
}
/**
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
index e0eb04d8236a..084c1e7c675e 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
@@ -162,13 +162,13 @@ export function get_attribute_name(element, attribute) {
* @param {boolean} is_html
*/
export function build_set_class(element, node_id, attribute, class_directives, context, is_html) {
- let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
- metadata.has_call ? get_expression_id(context.state, value) : value
- );
+ let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => {
+ if (attribute.metadata.needs_clsx) {
+ value = b.call('$.clsx', value);
+ }
- if (attribute && attribute.metadata.needs_clsx) {
- value = b.call('$.clsx', value);
- }
+ return metadata.has_call ? get_expression_id(context.state, value) : value;
+ });
/** @type {Identifier | undefined} */
let previous_id;
@@ -176,7 +176,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c
/** @type {ObjectExpression | Identifier | undefined} */
let prev;
- /** @type {ObjectExpression | undefined} */
+ /** @type {ObjectExpression | Identifier | undefined} */
let next;
if (class_directives.length) {
diff --git a/packages/svelte/src/internal/client/dom/elements/class.js b/packages/svelte/src/internal/client/dom/elements/class.js
index 7027c84f6260..ecbfcbc010fd 100644
--- a/packages/svelte/src/internal/client/dom/elements/class.js
+++ b/packages/svelte/src/internal/client/dom/elements/class.js
@@ -33,7 +33,7 @@ export function set_class(dom, is_html, value, hash, prev_classes, next_classes)
// @ts-expect-error need to add __className to patched prototype
dom.__className = value;
- } else if (next_classes) {
+ } else if (next_classes && prev_classes !== next_classes) {
for (var key in next_classes) {
var is_present = !!next_classes[key];
From d513304dd063f8d01d470b9a4a476cdb24d9d20c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 5 Mar 2025 20:55:52 -0500
Subject: [PATCH 11/97] Version Packages (#15453)
Co-authored-by: github-actions[bot]
---
.changeset/flat-jars-search.md | 5 -----
.changeset/quiet-baboons-listen.md | 5 -----
.changeset/real-cameras-attack.md | 5 -----
.changeset/strange-planes-shout.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/flat-jars-search.md
delete mode 100644 .changeset/quiet-baboons-listen.md
delete mode 100644 .changeset/real-cameras-attack.md
delete mode 100644 .changeset/strange-planes-shout.md
diff --git a/.changeset/flat-jars-search.md b/.changeset/flat-jars-search.md
deleted file mode 100644
index fc0de76f95b4..000000000000
--- a/.changeset/flat-jars-search.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: memoize `clsx` calls
diff --git a/.changeset/quiet-baboons-listen.md b/.changeset/quiet-baboons-listen.md
deleted file mode 100644
index eb5b4cc69927..000000000000
--- a/.changeset/quiet-baboons-listen.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"svelte": patch
----
-
-fix: respect `svelte-ignore hydration_attribute_changed` on elements with spread attributes
diff --git a/.changeset/real-cameras-attack.md b/.changeset/real-cameras-attack.md
deleted file mode 100644
index 35e276478508..000000000000
--- a/.changeset/real-cameras-attack.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: always use `setAttribute` when setting `style`
diff --git a/.changeset/strange-planes-shout.md b/.changeset/strange-planes-shout.md
deleted file mode 100644
index 58ef25274022..000000000000
--- a/.changeset/strange-planes-shout.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: make `style:` directive and CSS handling more robust
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 0ff6b62fe007..44f1f7cb7972 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,17 @@
# svelte
+## 5.22.5
+
+### Patch Changes
+
+- fix: memoize `clsx` calls ([#15456](https://github.com/sveltejs/svelte/pull/15456))
+
+- fix: respect `svelte-ignore hydration_attribute_changed` on elements with spread attributes ([#15443](https://github.com/sveltejs/svelte/pull/15443))
+
+- fix: always use `setAttribute` when setting `style` ([#15323](https://github.com/sveltejs/svelte/pull/15323))
+
+- fix: make `style:` directive and CSS handling more robust ([#15418](https://github.com/sveltejs/svelte/pull/15418))
+
## 5.22.4
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 1f95811cd5b8..fb20167a4e73 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.22.4",
+ "version": "5.22.5",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 845375314f63..e20a9683ddc6 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.22.4';
+export const VERSION = '5.22.5';
export const PUBLIC_VERSION = '5';
From 2c4d85bcec74f78a6291b07920c423908714aefc Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Wed, 5 Mar 2025 18:49:52 -0800
Subject: [PATCH 12/97] docs: address `$effect` feedback (#15107)
* docs: address $effect feedback
* also add a note to the migration guide
* minor wording tweak
* update onMount docs
* Update documentation/docs/02-runes/05-$effect.md
Co-authored-by: Rich Harris
* restore order
* soften a bit
* add back mention of updating template in response to effects
* define parent effect
* state that they don't run on the server
* Update documentation/docs/02-runes/04-$effect.md
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
* format
* Apply suggestions from code review
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
* update onMount docs
* add 'Understanding lifecycle' section
* note
* tweak wording
---------
Co-authored-by: Rich Harris
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
---
documentation/docs/02-runes/04-$effect.md | 30 +++++++++++--------
.../docs/07-misc/07-v5-migration-guide.md | 2 ++
packages/svelte/src/index-client.js | 11 +++----
packages/svelte/types/index.d.ts | 11 +++----
4 files changed, 32 insertions(+), 22 deletions(-)
diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md
index da24084d4dd0..e346bceba81c 100644
--- a/documentation/docs/02-runes/04-$effect.md
+++ b/documentation/docs/02-runes/04-$effect.md
@@ -2,15 +2,11 @@
title: $effect
---
-Effects are what make your application _do things_. When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes.
+Effects are functions that run when state updates, and can be used for things like calling third-party libraries, drawing on `` elements, or making network requests. They only run in the browser, not during server-side rendering.
-Most of the effects in a Svelte app are created by Svelte itself — they're the bits that update the text in `hello {name}! ` when `name` changes, for example.
+Generally speaking, you should _not_ update state inside effects, as it will make code more convoluted and will often lead to never-ending update cycles. If you find yourself doing so, see [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches.
-But you can also create your own effects with the `$effect` rune, which is useful when you need to synchronize an external system (whether that's a library, or a `` element, or something across a network) with state inside your Svelte app.
-
-> [!NOTE] Avoid overusing `$effect`! When you do too much work in effects, code often becomes difficult to understand and maintain. See [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches.
-
-Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)):
+You can create an effect with the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)):
```svelte
```
+Note that [when `$effect` runs is different]($effect#Understanding-dependencies) than when `$:` runs.
+
> [!DETAILS] Why we did this
> `$:` was a great shorthand and easy to get started with: you could slap a `$:` in front of most code and it would somehow work. This intuitiveness was also its drawback the more complicated your code became, because it wasn't as easy to reason about. Was the intent of the code to create a derivation, or a side effect? With `$derived` and `$effect`, you have a bit more up-front decision making to do (spoiler alert: 90% of the time you want `$derived`), but future-you and other developers on your team will have an easier time.
>
diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js
index efcf7b727b8d..fd8e999da763 100644
--- a/packages/svelte/src/index-client.js
+++ b/packages/svelte/src/index-client.js
@@ -45,13 +45,14 @@ if (DEV) {
}
/**
- * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.
- * It must be called during the component's initialisation (but doesn't need to live *inside* the component;
- * it can be called from an external module).
+ * `onMount`, like [`$effect`](https://svelte.dev/docs/svelte/$effect), schedules a function to run as soon as the component has been mounted to the DOM.
+ * Unlike `$effect`, the provided function only runs once.
*
- * If a function is returned _synchronously_ from `onMount`, it will be called when the component is unmounted.
+ * It must be called during the component's initialisation (but doesn't need to live _inside_ the component;
+ * it can be called from an external module). If a function is returned _synchronously_ from `onMount`,
+ * it will be called when the component is unmounted.
*
- * `onMount` does not run inside [server-side components](https://svelte.dev/docs/svelte/svelte-server#render).
+ * `onMount` functions do not run during [server-side rendering](https://svelte.dev/docs/svelte/svelte-server#render).
*
* @template T
* @param {() => NotFunction | Promise> | (() => any)} fn
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index c3dbdcac791e..c6000fc4b67f 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -349,13 +349,14 @@ declare module 'svelte' {
props: Props;
});
/**
- * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.
- * It must be called during the component's initialisation (but doesn't need to live *inside* the component;
- * it can be called from an external module).
+ * `onMount`, like [`$effect`](https://svelte.dev/docs/svelte/$effect), schedules a function to run as soon as the component has been mounted to the DOM.
+ * Unlike `$effect`, the provided function only runs once.
*
- * If a function is returned _synchronously_ from `onMount`, it will be called when the component is unmounted.
+ * It must be called during the component's initialisation (but doesn't need to live _inside_ the component;
+ * it can be called from an external module). If a function is returned _synchronously_ from `onMount`,
+ * it will be called when the component is unmounted.
*
- * `onMount` does not run inside [server-side components](https://svelte.dev/docs/svelte/svelte-server#render).
+ * `onMount` functions do not run during [server-side rendering](https://svelte.dev/docs/svelte/svelte-server#render).
*
* */
export function onMount(fn: () => NotFunction | Promise> | (() => any)): void;
From c5912aad71e77e75e4dcec8c27acb49e412f92f2 Mon Sep 17 00:00:00 2001
From: adiGuba
Date: Fri, 7 Mar 2025 15:30:56 +0100
Subject: [PATCH 13/97] fix: null and warnings for local handlers (#15460)
* fix null and warning for local handlers
* test
* changeset
* treat `let handler = () => {...}` the same as `function handler() {...}`
---------
Co-authored-by: Rich Harris
---
.changeset/shy-mirrors-remain.md | 5 ++
.../client/visitors/shared/events.js | 26 +++++++---
packages/svelte/src/compiler/phases/scope.js | 15 ++++++
.../internal/client/dom/elements/events.js | 11 ++---
.../event-handler-invalid-values/_config.js | 48 +++++++++++++++++++
.../event-handler-invalid-values/main.svelte | 10 ++++
6 files changed, 102 insertions(+), 13 deletions(-)
create mode 100644 .changeset/shy-mirrors-remain.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/main.svelte
diff --git a/.changeset/shy-mirrors-remain.md b/.changeset/shy-mirrors-remain.md
new file mode 100644
index 000000000000..028f7beb6898
--- /dev/null
+++ b/.changeset/shy-mirrors-remain.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: null and warnings for local handlers
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js
index f23f7548ece1..2667a96f6aef 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js
@@ -46,8 +46,12 @@ export function visit_event_attribute(node, context) {
// When we hoist a function we assign an array with the function and all
// hoisted closure params.
- const args = [handler, ...hoisted_params];
- delegated_assignment = b.array(args);
+ if (hoisted_params) {
+ const args = [handler, ...hoisted_params];
+ delegated_assignment = b.array(args);
+ } else {
+ delegated_assignment = handler;
+ }
} else {
delegated_assignment = handler;
}
@@ -123,11 +127,19 @@ export function build_event_handler(node, metadata, context) {
}
// function declared in the script
- if (
- handler.type === 'Identifier' &&
- context.state.scope.get(handler.name)?.declaration_kind !== 'import'
- ) {
- return handler;
+ if (handler.type === 'Identifier') {
+ const binding = context.state.scope.get(handler.name);
+
+ if (binding?.is_function()) {
+ return handler;
+ }
+
+ // local variable can be assigned directly
+ // except in dev mode where when need $.apply()
+ // in order to handle warnings.
+ if (!dev && binding?.declaration_kind !== 'import') {
+ return handler;
+ }
}
if (metadata.has_call) {
diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js
index 7d9f90982afb..a5227c1b5113 100644
--- a/packages/svelte/src/compiler/phases/scope.js
+++ b/packages/svelte/src/compiler/phases/scope.js
@@ -79,6 +79,21 @@ export class Binding {
get updated() {
return this.mutated || this.reassigned;
}
+
+ is_function() {
+ if (this.reassigned) {
+ // even if it's reassigned to another function,
+ // we can't use it directly as e.g. an event handler
+ return false;
+ }
+
+ if (this.declaration_kind === 'function') {
+ return true;
+ }
+
+ const type = this.initial?.type;
+ return type === 'ArrowFunctionExpression' || type === 'FunctionExpression';
+ }
}
export class Scope {
diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js
index 25ece5f569d7..0c1bb1dada83 100644
--- a/packages/svelte/src/internal/client/dom/elements/events.js
+++ b/packages/svelte/src/internal/client/dom/elements/events.js
@@ -238,7 +238,7 @@ export function handle_event_propagation(event) {
var delegated = current_target['__' + event_name];
if (
- delegated !== undefined &&
+ delegated != null &&
(!(/** @type {any} */ (current_target).disabled) ||
// DOM could've been updated already by the time this is reached, so we check this as well
// -> the target could not have been disabled because it emits the event in the first place
@@ -311,13 +311,11 @@ export function apply(
error = e;
}
- if (typeof handler === 'function') {
- handler.apply(element, args);
- } else if (has_side_effects || handler != null || error) {
+ if (typeof handler !== 'function' && (has_side_effects || handler != null || error)) {
const filename = component?.[FILENAME];
const location = loc ? ` at ${filename}:${loc[0]}:${loc[1]}` : ` in ${filename}`;
-
- const event_name = args[0].type;
+ const phase = args[0]?.eventPhase < Event.BUBBLING_PHASE ? 'capture' : '';
+ const event_name = args[0]?.type + phase;
const description = `\`${event_name}\` handler${location}`;
const suggestion = remove_parens ? 'remove the trailing `()`' : 'add a leading `() =>`';
@@ -327,4 +325,5 @@ export function apply(
throw error;
}
}
+ handler?.apply(element, args);
}
diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/_config.js b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/_config.js
new file mode 100644
index 000000000000..d53812d4c39e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/_config.js
@@ -0,0 +1,48 @@
+import { assertType } from 'vitest';
+import { test } from '../../test';
+
+export default test({
+ mode: ['client'],
+
+ compileOptions: {
+ dev: true
+ },
+
+ test({ assert, target, warnings, logs }) {
+ /** @type {any} */
+ let error = null;
+
+ const handler = (/** @type {any} */ e) => {
+ error = e.error;
+ e.stopImmediatePropagation();
+ };
+
+ window.addEventListener('error', handler, true);
+
+ const [b1, b2, b3] = target.querySelectorAll('button');
+
+ b1.click();
+ assert.deepEqual(logs, []);
+ assert.equal(error, null);
+
+ error = null;
+ logs.length = 0;
+
+ b2.click();
+ assert.deepEqual(logs, ['clicked']);
+ assert.equal(error, null);
+
+ error = null;
+ logs.length = 0;
+
+ b3.click();
+ assert.deepEqual(logs, []);
+ assert.deepEqual(warnings, [
+ '`click` handler at main.svelte:10:17 should be a function. Did you mean to add a leading `() =>`?'
+ ]);
+ assert.isNotNull(error);
+ assert.match(error.message, /is not a function/);
+
+ window.removeEventListener('error', handler, true);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/main.svelte
new file mode 100644
index 000000000000..f6e344ece8cf
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/main.svelte
@@ -0,0 +1,10 @@
+
+
+click
+click
+click
From 3326bd8ae77a43b6ae2b03970969066e716f6063 Mon Sep 17 00:00:00 2001
From: "Trevor N. Suarez"
Date: Fri, 7 Mar 2025 08:02:02 -0700
Subject: [PATCH 14/97] feat: Add `closedby` to `HTMLDialogAttributes` (dialog
element) (#15458)
* Adding the `closedby` attribute to dialog element
Spec: https://html.spec.whatwg.org/#attr-dialog-closedby
* Adding changeset
* Update .changeset/metal-spoons-scream.md
---------
Co-authored-by: Rich Harris
---
.changeset/metal-spoons-scream.md | 5 +++++
packages/svelte/elements.d.ts | 1 +
2 files changed, 6 insertions(+)
create mode 100644 .changeset/metal-spoons-scream.md
diff --git a/.changeset/metal-spoons-scream.md b/.changeset/metal-spoons-scream.md
new file mode 100644
index 000000000000..2eb7b7140cf9
--- /dev/null
+++ b/.changeset/metal-spoons-scream.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: Add `closedby` property to HTMLDialogAttributes type
diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts
index 6d256b56205c..08687cafaf14 100644
--- a/packages/svelte/elements.d.ts
+++ b/packages/svelte/elements.d.ts
@@ -957,6 +957,7 @@ export interface HTMLDelAttributes extends HTMLAttributes {
export interface HTMLDialogAttributes extends HTMLAttributes {
open?: boolean | undefined | null;
+ closedby?: 'any' | 'closerequest' | 'none' | undefined | null;
}
export interface HTMLEmbedAttributes extends HTMLAttributes {
From eaf0087d7c5671794ee25521667860b1a8af1828 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 7 Mar 2025 10:02:58 -0500
Subject: [PATCH 15/97] fix: skip `log_if_contains_state` if only logging
literals (#15468)
---
.changeset/hungry-monkeys-fly.md | 5 +++++
.../phases/3-transform/client/visitors/CallExpression.js | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)
create mode 100644 .changeset/hungry-monkeys-fly.md
diff --git a/.changeset/hungry-monkeys-fly.md b/.changeset/hungry-monkeys-fly.md
new file mode 100644
index 000000000000..f52c8dad9254
--- /dev/null
+++ b/.changeset/hungry-monkeys-fly.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: skip `log_if_contains_state` if only logging literals
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js
index 7a3057451aa1..fda43ad7911a 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js
@@ -44,7 +44,8 @@ export function CallExpression(node, context) {
node.callee.property.type === 'Identifier' &&
['debug', 'dir', 'error', 'group', 'groupCollapsed', 'info', 'log', 'trace', 'warn'].includes(
node.callee.property.name
- )
+ ) &&
+ node.arguments.some((arg) => arg.type !== 'Literal') // TODO more cases?
) {
return b.call(
node.callee,
From e2bbc560e434df055402eb018789754a41afc456 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 7 Mar 2025 10:07:50 -0500
Subject: [PATCH 16/97] Version Packages (#15466)
Co-authored-by: github-actions[bot]
---
.changeset/hungry-monkeys-fly.md | 5 -----
.changeset/metal-spoons-scream.md | 5 -----
.changeset/shy-mirrors-remain.md | 5 -----
packages/svelte/CHANGELOG.md | 10 ++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
6 files changed, 12 insertions(+), 17 deletions(-)
delete mode 100644 .changeset/hungry-monkeys-fly.md
delete mode 100644 .changeset/metal-spoons-scream.md
delete mode 100644 .changeset/shy-mirrors-remain.md
diff --git a/.changeset/hungry-monkeys-fly.md b/.changeset/hungry-monkeys-fly.md
deleted file mode 100644
index f52c8dad9254..000000000000
--- a/.changeset/hungry-monkeys-fly.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: skip `log_if_contains_state` if only logging literals
diff --git a/.changeset/metal-spoons-scream.md b/.changeset/metal-spoons-scream.md
deleted file mode 100644
index 2eb7b7140cf9..000000000000
--- a/.changeset/metal-spoons-scream.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: Add `closedby` property to HTMLDialogAttributes type
diff --git a/.changeset/shy-mirrors-remain.md b/.changeset/shy-mirrors-remain.md
deleted file mode 100644
index 028f7beb6898..000000000000
--- a/.changeset/shy-mirrors-remain.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: null and warnings for local handlers
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 44f1f7cb7972..a05938dacce3 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,15 @@
# svelte
+## 5.22.6
+
+### Patch Changes
+
+- fix: skip `log_if_contains_state` if only logging literals ([#15468](https://github.com/sveltejs/svelte/pull/15468))
+
+- fix: Add `closedby` property to HTMLDialogAttributes type ([#15458](https://github.com/sveltejs/svelte/pull/15458))
+
+- fix: null and warnings for local handlers ([#15460](https://github.com/sveltejs/svelte/pull/15460))
+
## 5.22.5
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index fb20167a4e73..53f03e3543fc 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.22.5",
+ "version": "5.22.6",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index e20a9683ddc6..01e1b390a39e 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.22.5';
+export const VERSION = '5.22.6';
export const PUBLIC_VERSION = '5';
From 1c0e24013fff52e106870dfc7e4b38a817c25610 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 10 Mar 2025 11:22:21 -0400
Subject: [PATCH 17/97] chore: reuse is_function helper (#15467)
---
.../phases/2-analyze/visitors/Attribute.js | 12 ++----------
packages/svelte/src/compiler/phases/scope.js | 18 +++++++++++-------
2 files changed, 13 insertions(+), 17 deletions(-)
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js
index 561a00452684..9124a8822f58 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js
@@ -162,16 +162,8 @@ function get_delegated_event(event_name, handler, context) {
return unhoisted;
}
- if (binding !== null && binding.initial !== null && !binding.updated) {
- const binding_type = binding.initial.type;
-
- if (
- binding_type === 'ArrowFunctionExpression' ||
- binding_type === 'FunctionDeclaration' ||
- binding_type === 'FunctionExpression'
- ) {
- target_function = binding.initial;
- }
+ if (binding?.is_function()) {
+ target_function = binding.initial;
}
}
diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js
index a5227c1b5113..b6063c32343f 100644
--- a/packages/svelte/src/compiler/phases/scope.js
+++ b/packages/svelte/src/compiler/phases/scope.js
@@ -1,4 +1,4 @@
-/** @import { ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, MemberExpression, Node, Pattern, VariableDeclarator } from 'estree' */
+/** @import { ArrowFunctionExpression, ClassDeclaration, Expression, FunctionDeclaration, FunctionExpression, Identifier, ImportDeclaration, MemberExpression, Node, Pattern, VariableDeclarator } from 'estree' */
/** @import { Context, Visitor } from 'zimmerframe' */
/** @import { AST, BindingKind, DeclarationKind } from '#compiler' */
import is_reference from 'is-reference';
@@ -80,19 +80,23 @@ export class Binding {
return this.mutated || this.reassigned;
}
+ /**
+ * @returns {this is Binding & { initial: ArrowFunctionExpression | FunctionDeclaration | FunctionExpression }}
+ */
is_function() {
- if (this.reassigned) {
+ if (this.updated) {
// even if it's reassigned to another function,
// we can't use it directly as e.g. an event handler
return false;
}
- if (this.declaration_kind === 'function') {
- return true;
- }
-
const type = this.initial?.type;
- return type === 'ArrowFunctionExpression' || type === 'FunctionExpression';
+
+ return (
+ type === 'ArrowFunctionExpression' ||
+ type === 'FunctionExpression' ||
+ type === 'FunctionDeclaration'
+ );
}
}
From 81480c40a02678bd02d89e2e20d50a6ddd3ec383 Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Mon, 10 Mar 2025 12:48:50 -0700
Subject: [PATCH 18/97] chore: add missing permissions for `pkg.pr.new-comment`
(#15489)
---
.github/workflows/pkg.pr.new-comment.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/pkg.pr.new-comment.yml b/.github/workflows/pkg.pr.new-comment.yml
index b1fba0b04b30..3f1fca5a0bea 100644
--- a/.github/workflows/pkg.pr.new-comment.yml
+++ b/.github/workflows/pkg.pr.new-comment.yml
@@ -6,6 +6,9 @@ on:
types:
- completed
+permissions:
+ pull-requests: write
+
jobs:
build:
name: 'Update comment'
From dbd4617ac42a4bf5e97af05f2d5bc6a0517e24b2 Mon Sep 17 00:00:00 2001
From: Maple <52185471+fuuki12@users.noreply.github.com>
Date: Tue, 11 Mar 2025 06:52:22 +0900
Subject: [PATCH 19/97] docs: correct toggle function in lifecycle hooks
example (#15486)
---
documentation/docs/06-runtime/03-lifecycle-hooks.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/documentation/docs/06-runtime/03-lifecycle-hooks.md b/documentation/docs/06-runtime/03-lifecycle-hooks.md
index 2b97ca796fed..f051c46d73ba 100644
--- a/documentation/docs/06-runtime/03-lifecycle-hooks.md
+++ b/documentation/docs/06-runtime/03-lifecycle-hooks.md
@@ -147,7 +147,7 @@ With runes, we can use `$effect.pre`, which behaves the same as `$effect` but ru
}
function toggle() {
- toggleValue = !toggleValue;
+ theme = theme === 'dark' ? 'light' : 'dark';
}
From 1cc5bcdc999716673d15844bd1190758f882ba05 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 11 Mar 2025 18:57:58 +0100
Subject: [PATCH 20/97] chore: clarify fuzzyset adaption (#15491)
it was BSD in 2016 but has undergone some license changes since then - clarify in the comment that the adaption was from the 2016 BSD version
---
.../svelte/src/compiler/phases/1-parse/utils/fuzzymatch.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/fuzzymatch.js b/packages/svelte/src/compiler/phases/1-parse/utils/fuzzymatch.js
index cd72d73005c2..28b314cdd567 100644
--- a/packages/svelte/src/compiler/phases/1-parse/utils/fuzzymatch.js
+++ b/packages/svelte/src/compiler/phases/1-parse/utils/fuzzymatch.js
@@ -12,8 +12,8 @@ export default function fuzzymatch(name, names) {
return matches && matches[0][0] > 0.7 ? matches[0][1] : null;
}
-// adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js
-// BSD Licensed
+// adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js in 2016
+// BSD Licensed (see https://github.com/Glench/fuzzyset.js/issues/10)
const GRAM_SIZE_LOWER = 2;
const GRAM_SIZE_UPPER = 3;
From 110d42062fb1a98698d2a68a39919cc44962613d Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Tue, 11 Mar 2025 18:48:55 +0000
Subject: [PATCH 21/97] fix: on teardown, use the last known value for the
signal before the set (#15469)
* fix: on teardown, use the last known value for the signal before the se
* fix: on teardown, use the last known value for the signal before the se
* fix: on teardown, use the last known value for the signal before the se
* fix: on teardown, use the last known value for the signal before the se
* fix: on teardown, use the last known value for the signal before the se
* Update packages/svelte/src/internal/client/reactivity/props.js
Co-authored-by: Rich Harris
* Update packages/svelte/src/internal/client/reactivity/props.js
Co-authored-by: Rich Harris
* Update packages/svelte/src/internal/client/reactivity/props.js
Co-authored-by: Rich Harris
* lint
* lint
* lint
* Update .changeset/sharp-elephants-invite.md
---------
Co-authored-by: Rich Harris
---
.changeset/sharp-elephants-invite.md | 5 ++
.../2-analyze/visitors/CallExpression.js | 3 +
.../svelte/src/internal/client/context.js | 11 ++-
.../src/internal/client/reactivity/props.js | 43 +++++++-----
.../src/internal/client/reactivity/sources.js | 11 ++-
.../svelte/src/internal/client/runtime.js | 7 +-
.../svelte/src/internal/client/types.d.ts | 2 +
.../ondestroy-prop-access-2/Component.svelte | 11 +++
.../ondestroy-prop-access-2/_config.js | 14 ++++
.../ondestroy-prop-access-2/main.svelte | 15 ++++
.../ondestroy-prop-access-3/Component.svelte | 5 ++
.../ondestroy-prop-access-3/_config.js | 11 +++
.../ondestroy-prop-access-3/main.svelte | 16 +++++
.../ondestroy-prop-access/Component.svelte | 12 ++++
.../samples/ondestroy-prop-access/_config.js | 68 +++++++++++++++++++
.../samples/ondestroy-prop-access/main.svelte | 41 +++++++++++
.../samples/nested-effect-conflict/_config.js | 10 +--
17 files changed, 254 insertions(+), 31 deletions(-)
create mode 100644 .changeset/sharp-elephants-invite.md
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/Component.svelte
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/_config.js
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/main.svelte
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/Component.svelte
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/_config.js
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/main.svelte
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/Component.svelte
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/_config.js
create mode 100644 packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/main.svelte
diff --git a/.changeset/sharp-elephants-invite.md b/.changeset/sharp-elephants-invite.md
new file mode 100644
index 000000000000..3a106cd45054
--- /dev/null
+++ b/.changeset/sharp-elephants-invite.md
@@ -0,0 +1,5 @@
+---
+'svelte': minor
+---
+
+fix: make values consistent between effects and their cleanup functions
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
index 4d09d9293fb2..6c2171785244 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
@@ -42,6 +42,9 @@ export function CallExpression(node, context) {
e.bindable_invalid_location(node);
}
+ // We need context in case the bound prop is stale
+ context.state.analysis.needs_context = true;
+
break;
case '$host':
diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js
index bd94d5ad8a19..bfca9d5e6a72 100644
--- a/packages/svelte/src/internal/client/context.js
+++ b/packages/svelte/src/internal/client/context.js
@@ -11,7 +11,7 @@ import {
set_active_reaction,
untrack
} from './runtime.js';
-import { effect } from './reactivity/effects.js';
+import { effect, teardown } from './reactivity/effects.js';
import { legacy_mode_flag } from '../flags/index.js';
/** @type {ComponentContext | null} */
@@ -112,15 +112,16 @@ export function getAllContexts() {
* @returns {void}
*/
export function push(props, runes = false, fn) {
- component_context = {
+ var ctx = (component_context = {
p: component_context,
c: null,
+ d: false,
e: null,
m: false,
s: props,
x: null,
l: null
- };
+ });
if (legacy_mode_flag && !runes) {
component_context.l = {
@@ -131,6 +132,10 @@ export function push(props, runes = false, fn) {
};
}
+ teardown(() => {
+ /** @type {ComponentContext} */ (ctx).d = true;
+ });
+
if (DEV) {
// component function
component_context.function = fn;
diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js
index 5a3b30281f9f..bd85b14df088 100644
--- a/packages/svelte/src/internal/client/reactivity/props.js
+++ b/packages/svelte/src/internal/client/reactivity/props.js
@@ -1,4 +1,4 @@
-/** @import { Source } from './types.js' */
+/** @import { Derived, Source } from './types.js' */
import { DEV } from 'esm-env';
import {
PROPS_IS_BINDABLE,
@@ -10,24 +10,10 @@ import {
import { get_descriptor, is_function } from '../../shared/utils.js';
import { mutable_source, set, source, update } from './sources.js';
import { derived, derived_safe_equal } from './deriveds.js';
-import {
- active_effect,
- get,
- captured_signals,
- set_active_effect,
- untrack,
- active_reaction,
- set_active_reaction
-} from '../runtime.js';
+import { get, captured_signals, untrack } from '../runtime.js';
import { safe_equals } from './equality.js';
import * as e from '../errors.js';
-import {
- BRANCH_EFFECT,
- LEGACY_DERIVED_PROP,
- LEGACY_PROPS,
- ROOT_EFFECT,
- STATE_SYMBOL
-} from '../constants.js';
+import { LEGACY_DERIVED_PROP, LEGACY_PROPS, STATE_SYMBOL } from '../constants.js';
import { proxy } from '../proxy.js';
import { capture_store_binding } from './store.js';
import { legacy_mode_flag } from '../../flags/index.js';
@@ -249,6 +235,14 @@ export function spread_props(...props) {
return new Proxy({ props }, spread_props_handler);
}
+/**
+ * @param {Derived} current_value
+ * @returns {boolean}
+ */
+function has_destroyed_component_ctx(current_value) {
+ return current_value.ctx?.d ?? false;
+}
+
/**
* This function is responsible for synchronizing a possibly bound prop with the inner component state.
* It is used whenever the compiler sees that the component writes to the prop, or when it has a default prop_value.
@@ -382,6 +376,11 @@ export function prop(props, key, flags, fallback) {
return (inner_current_value.v = parent_value);
});
+ // Ensure we eagerly capture the initial value if it's bindable
+ if (bindable) {
+ get(current_value);
+ }
+
if (!immutable) current_value.equals = safe_equals;
return function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
@@ -408,11 +407,21 @@ export function prop(props, key, flags, fallback) {
if (fallback_used && fallback_value !== undefined) {
fallback_value = new_value;
}
+
+ if (has_destroyed_component_ctx(current_value)) {
+ return value;
+ }
+
untrack(() => get(current_value)); // force a synchronisation immediately
}
return value;
}
+
+ if (has_destroyed_component_ctx(current_value)) {
+ return current_value.v;
+ }
+
return get(current_value);
};
}
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index f6a3fd7e330a..49584e862624 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -14,7 +14,8 @@ import {
derived_sources,
set_derived_sources,
check_dirtiness,
- untracking
+ untracking,
+ is_destroying_effect
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import {
@@ -34,6 +35,7 @@ import { get_stack } from '../dev/tracing.js';
import { component_context, is_runes } from '../context.js';
export let inspect_effects = new Set();
+export const old_values = new Map();
/**
* @param {Set} v
@@ -168,6 +170,13 @@ export function set(source, value) {
export function internal_set(source, value) {
if (!source.equals(value)) {
var old_value = source.v;
+
+ if (is_destroying_effect) {
+ old_values.set(source, value);
+ } else {
+ old_values.set(source, old_value);
+ }
+
source.v = value;
source.wv = increment_write_version();
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index bbe4dc3d9b8f..aa0a41e71fd9 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -25,7 +25,7 @@ import {
BOUNDARY_EFFECT
} from './constants.js';
import { flush_tasks } from './dom/task.js';
-import { internal_set } from './reactivity/sources.js';
+import { internal_set, old_values } from './reactivity/sources.js';
import { destroy_derived_effects, update_derived } from './reactivity/deriveds.js';
import * as e from './errors.js';
import { FILENAME } from '../../constants.js';
@@ -673,6 +673,7 @@ function flush_queued_root_effects() {
if (DEV) {
dev_effect_stack = [];
}
+ old_values.clear();
}
}
@@ -923,6 +924,10 @@ export function get(signal) {
}
}
+ if (is_destroying_effect && old_values.has(signal)) {
+ return old_values.get(signal);
+ }
+
return signal.v;
}
diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts
index 7208ed77837e..0c260a0a9ffa 100644
--- a/packages/svelte/src/internal/client/types.d.ts
+++ b/packages/svelte/src/internal/client/types.d.ts
@@ -14,6 +14,8 @@ export type ComponentContext = {
p: null | ComponentContext;
/** context */
c: null | Map;
+ /** destroyed */
+ d: boolean;
/** deferred effects */
e: null | Array<{
fn: () => void | (() => void);
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/Component.svelte b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/Component.svelte
new file mode 100644
index 000000000000..73347c4d7ff1
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/Component.svelte
@@ -0,0 +1,11 @@
+
+
+{my_prop.foo}
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/_config.js b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/_config.js
new file mode 100644
index 000000000000..81005cf73760
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/_config.js
@@ -0,0 +1,14 @@
+import { test } from '../../test';
+import { flushSync } from 'svelte';
+
+export default test({
+ async test({ assert, target, logs }) {
+ const [btn1] = target.querySelectorAll('button');
+
+ flushSync(() => {
+ btn1.click();
+ });
+
+ assert.deepEqual(logs, ['bar']);
+ }
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/main.svelte b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/main.svelte
new file mode 100644
index 000000000000..f38b37fb7f7c
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/main.svelte
@@ -0,0 +1,15 @@
+
+
+ {
+ value = undefined;
+ }}>Reset value
+
+{#if value !== undefined}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/Component.svelte b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/Component.svelte
new file mode 100644
index 000000000000..5bfb7771289d
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/Component.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/_config.js b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/_config.js
new file mode 100644
index 000000000000..0eb68310cbb6
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/_config.js
@@ -0,0 +1,11 @@
+import { test } from '../../test';
+import { flushSync } from 'svelte';
+
+export default test({
+ async test({ target }) {
+ const [btn1] = target.querySelectorAll('button');
+
+ btn1.click();
+ flushSync();
+ }
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/main.svelte b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/main.svelte
new file mode 100644
index 000000000000..9c72d2c48ac1
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-3/main.svelte
@@ -0,0 +1,16 @@
+
+
+{#if state}
+ {@const attributes = { title: state.title }}
+
+{/if}
+ {
+ state = undefined;
+ }}
+>
+ Del
+
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/Component.svelte b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/Component.svelte
new file mode 100644
index 000000000000..761f303c2e0c
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/Component.svelte
@@ -0,0 +1,12 @@
+
+
+{count}
+
+ count-- }>
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/_config.js b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/_config.js
new file mode 100644
index 000000000000..2ffb7e653f15
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/_config.js
@@ -0,0 +1,68 @@
+import { test } from '../../test';
+import { flushSync } from 'svelte';
+
+export default test({
+ async test({ assert, target, logs }) {
+ const [btn1, btn2, btn3] = target.querySelectorAll('button');
+ let ps = [...target.querySelectorAll('p')];
+
+ for (const p of ps) {
+ assert.equal(p.innerHTML, '0');
+ }
+
+ flushSync(() => {
+ btn1.click();
+ });
+
+ // prop update normally if we are not unmounting
+ for (const p of ps) {
+ assert.equal(p.innerHTML, '1');
+ }
+
+ flushSync(() => {
+ btn3.click();
+ });
+
+ // binding still works and update the value correctly
+ for (const p of ps) {
+ assert.equal(p.innerHTML, '0');
+ }
+
+ flushSync(() => {
+ btn1.click();
+ });
+
+ flushSync(() => {
+ btn1.click();
+ });
+
+ console.warn(logs);
+
+ // the five components guarded by `count < 2` unmount and log
+ assert.deepEqual(logs, [1, true, 1, true, 1, true, 1, true, 1, true]);
+
+ flushSync(() => {
+ btn2.click();
+ });
+
+ // the three components guarded by `show` unmount and log
+ assert.deepEqual(logs, [
+ 1,
+ true,
+ 1,
+ true,
+ 1,
+ true,
+ 1,
+ true,
+ 1,
+ true,
+ 2,
+ true,
+ 2,
+ true,
+ 2,
+ true
+ ]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/main.svelte b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/main.svelte
new file mode 100644
index 000000000000..73a7501e9db2
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/main.svelte
@@ -0,0 +1,41 @@
+
+
+ count++ }>
+ show = !show }>
+
+
+{#if count < 2}
+
+{/if}
+
+
+{#if count < 2}
+
+{/if}
+
+
+{#if count < 2}
+
+{/if}
+
+
+{#if show}
+
+{/if}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/nested-effect-conflict/_config.js b/packages/svelte/tests/runtime-runes/samples/nested-effect-conflict/_config.js
index a8c16b7008c9..eb631bc9f4bc 100644
--- a/packages/svelte/tests/runtime-runes/samples/nested-effect-conflict/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/nested-effect-conflict/_config.js
@@ -10,14 +10,6 @@ export default test({
});
await Promise.resolve();
- assert.deepEqual(logs, [
- 'top level',
- 'inner',
- 0,
- 'destroy inner',
- undefined,
- 'destroy outer',
- undefined
- ]);
+ assert.deepEqual(logs, ['top level', 'inner', 0, 'destroy inner', 0, 'destroy outer', 0]);
}
});
From a1257c17f5ba93bdbf7c470a5720ff9a69a224dc Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 14:51:01 -0400
Subject: [PATCH 22/97] Version Packages (#15493)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/sharp-elephants-invite.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/sharp-elephants-invite.md
diff --git a/.changeset/sharp-elephants-invite.md b/.changeset/sharp-elephants-invite.md
deleted file mode 100644
index 3a106cd45054..000000000000
--- a/.changeset/sharp-elephants-invite.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': minor
----
-
-fix: make values consistent between effects and their cleanup functions
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index a05938dacce3..65b3edd1fdad 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.23.0
+
+### Minor Changes
+
+- fix: make values consistent between effects and their cleanup functions ([#15469](https://github.com/sveltejs/svelte/pull/15469))
+
## 5.22.6
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 53f03e3543fc..b3ac0a5b518e 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.22.6",
+ "version": "5.23.0",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 01e1b390a39e..5f06fd07536e 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.22.6';
+export const VERSION = '5.23.0';
export const PUBLIC_VERSION = '5';
From b27ca425c792d53d58346d76850add7db1cef3ad Mon Sep 17 00:00:00 2001
From: "D.V. Colomban" <17250935+dvcol@users.noreply.github.com>
Date: Wed, 12 Mar 2025 23:58:40 +0100
Subject: [PATCH 23/97] fix: add files and group properties to
HTMLInputAttributes (#15492)
Fixes #14579
Although this isn't 100% correct because there's no `group` attribute, there's no better way to make people have components' props just be `HTMLInputAttributes` and allow them to reference `group`
---
.changeset/plenty-bats-lay.md | 5 +++++
packages/svelte/elements.d.ts | 2 ++
2 files changed, 7 insertions(+)
create mode 100644 .changeset/plenty-bats-lay.md
diff --git a/.changeset/plenty-bats-lay.md b/.changeset/plenty-bats-lay.md
new file mode 100644
index 000000000000..cd5ce66e424e
--- /dev/null
+++ b/.changeset/plenty-bats-lay.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: add `files` and `group` to HTMLInputAttributes in elements.d.ts
diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts
index 08687cafaf14..99d87b4c09a4 100644
--- a/packages/svelte/elements.d.ts
+++ b/packages/svelte/elements.d.ts
@@ -1076,6 +1076,7 @@ export interface HTMLInputAttributes extends HTMLAttributes {
checked?: boolean | undefined | null;
dirname?: string | undefined | null;
disabled?: boolean | undefined | null;
+ files?: FileList | undefined | null;
form?: string | undefined | null;
formaction?: string | undefined | null;
formenctype?:
@@ -1087,6 +1088,7 @@ export interface HTMLInputAttributes extends HTMLAttributes {
formmethod?: 'dialog' | 'get' | 'post' | 'DIALOG' | 'GET' | 'POST' | undefined | null;
formnovalidate?: boolean | undefined | null;
formtarget?: string | undefined | null;
+ group?: any | undefined | null;
height?: number | string | undefined | null;
indeterminate?: boolean | undefined | null;
list?: string | undefined | null;
From 5d3aa2bda4bea7af39607a4a01ce8f0eff6cd56b Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Thu, 13 Mar 2025 19:49:26 +0000
Subject: [PATCH 24/97] fix: ensure transient writes to tracked parent effects
works as expected (#15506)
* ix: ensure transient writes to tracked parent effects works as expected
* lint
* format test
* tweak changeset
---------
Co-authored-by: Rich Harris
---
.changeset/brown-rockets-shake.md | 5 +++++
packages/svelte/src/internal/client/runtime.js | 8 ++++++++
.../samples/untracked-write-pre/_config.js | 7 +++++++
.../samples/untracked-write-pre/main.svelte | 13 +++++++++++++
4 files changed, 33 insertions(+)
create mode 100644 .changeset/brown-rockets-shake.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte
diff --git a/.changeset/brown-rockets-shake.md b/.changeset/brown-rockets-shake.md
new file mode 100644
index 000000000000..3772a88f6ebd
--- /dev/null
+++ b/.changeset/brown-rockets-shake.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: invalidate parent effects when child effects update parent dependencies
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index aa0a41e71fd9..0a65c6e45a13 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -460,6 +460,14 @@ export function update_reaction(reaction) {
// the same version
if (previous_reaction !== null) {
read_version++;
+
+ if (untracked_writes !== null) {
+ if (previous_untracked_writes === null) {
+ previous_untracked_writes = untracked_writes;
+ } else {
+ previous_untracked_writes.push(.../** @type {Source[]} */ (untracked_writes));
+ }
+ }
}
return result;
diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js
new file mode 100644
index 000000000000..0310ec4fbb52
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js
@@ -0,0 +1,7 @@
+import { test } from '../../test';
+
+export default test({
+ test({ assert, target, logs }) {
+ assert.deepEqual(logs, ['Outer', 'Inner', 'Outer', 'Inner']);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte
new file mode 100644
index 000000000000..5e95dbfd411a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte
@@ -0,0 +1,13 @@
+
From 489f463d7bf80da22c92b686e507eb7c0dc41967 Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Fri, 14 Mar 2025 03:26:45 -0700
Subject: [PATCH 25/97] fix: replace `undefined` with `void 0` to avoid edge
case (#15511)
* replace 'undefined' with 'void 0'
* lint
* YALF
* reuse expression
* oops
---------
Co-authored-by: Rich Harris
---
.changeset/curvy-countries-flow.md | 5 +++++
.../3-transform/client/visitors/VariableDeclaration.js | 3 +--
.../phases/3-transform/server/visitors/CallExpression.js | 4 ++--
.../3-transform/server/visitors/VariableDeclaration.js | 5 ++---
packages/svelte/src/compiler/utils/builders.js | 2 ++
5 files changed, 12 insertions(+), 7 deletions(-)
create mode 100644 .changeset/curvy-countries-flow.md
diff --git a/.changeset/curvy-countries-flow.md b/.changeset/curvy-countries-flow.md
new file mode 100644
index 000000000000..6ef85458043d
--- /dev/null
+++ b/.changeset/curvy-countries-flow.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: replace `undefined` with `void 0` to avoid edge case
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
index 31e712cdcc4d..baffc5dec374 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
@@ -116,8 +116,7 @@ export function VariableDeclaration(node, context) {
}
const args = /** @type {CallExpression} */ (init).arguments;
- const value =
- args.length === 0 ? b.id('undefined') : /** @type {Expression} */ (context.visit(args[0]));
+ const value = args.length > 0 ? /** @type {Expression} */ (context.visit(args[0])) : b.void0;
if (rune === '$state' || rune === '$state.raw') {
/**
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
index 386c6b6ff393..a425bc5ec430 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
@@ -13,11 +13,11 @@ export function CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);
if (rune === '$host') {
- return b.id('undefined');
+ return b.void0;
}
if (rune === '$effect.tracking') {
- return b.literal(false);
+ return b.false;
}
if (rune === '$effect.root') {
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
index c4c31d7eb304..a9c9777335ff 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
@@ -45,7 +45,7 @@ export function VariableDeclaration(node, context) {
) {
const right = node.right.arguments.length
? /** @type {Expression} */ (context.visit(node.right.arguments[0]))
- : b.id('undefined');
+ : b.void0;
return b.assignment_pattern(node.left, right);
}
}
@@ -75,8 +75,7 @@ export function VariableDeclaration(node, context) {
}
const args = /** @type {CallExpression} */ (init).arguments;
- const value =
- args.length === 0 ? b.id('undefined') : /** @type {Expression} */ (context.visit(args[0]));
+ const value = args.length > 0 ? /** @type {Expression} */ (context.visit(args[0])) : b.void0;
if (rune === '$derived.by') {
declarations.push(
diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js
index ecb595d74dbd..4ec2930cc2f5 100644
--- a/packages/svelte/src/compiler/utils/builders.js
+++ b/packages/svelte/src/compiler/utils/builders.js
@@ -154,6 +154,8 @@ export function unary(operator, argument) {
return { type: 'UnaryExpression', argument, operator, prefix: true };
}
+export const void0 = unary('void', literal(0));
+
/**
* @param {ESTree.Expression} test
* @param {ESTree.Expression} consequent
From dab1a1b467b8c5e85964d74c378357bea00e5fe9 Mon Sep 17 00:00:00 2001
From: Scott Wu
Date: Fri, 14 Mar 2025 19:11:08 +0800
Subject: [PATCH 26/97] docs: Update 99-faq.md (#15510)
---
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 b56c27af86b6..7e25cdab5596 100644
--- a/documentation/docs/07-misc/99-faq.md
+++ b/documentation/docs/07-misc/99-faq.md
@@ -46,7 +46,7 @@ It will show up on hover.
- You can use markdown here.
- You can also use code blocks here.
- Usage:
- ```tsx
+ ```svelte
```
-->
From 18d71fd5288c04d5574f5abe18e7c990053c5876 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 14 Mar 2025 07:12:02 -0400
Subject: [PATCH 27/97] chore: reuse expression nodes (#15513)
---
.../compiler/phases/3-transform/client/transform-client.js | 2 +-
.../phases/3-transform/client/visitors/AnimateDirective.js | 2 +-
.../compiler/phases/3-transform/client/visitors/AwaitBlock.js | 2 +-
.../phases/3-transform/client/visitors/BinaryExpression.js | 4 ++--
.../compiler/phases/3-transform/client/visitors/IfBlock.js | 4 +---
.../phases/3-transform/client/visitors/RegularElement.js | 2 +-
.../phases/3-transform/client/visitors/SlotElement.js | 2 +-
.../phases/3-transform/client/visitors/shared/element.js | 4 ++--
.../phases/3-transform/server/visitors/SlotElement.js | 2 +-
packages/svelte/src/compiler/utils/builders.js | 4 ++--
10 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index cf5ba285cbf3..ac8263b91669 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -596,7 +596,7 @@ export function client_component(analysis, options) {
/** @type {ESTree.Property[]} */ (
[
prop_def.attribute ? b.init('attribute', b.literal(prop_def.attribute)) : undefined,
- prop_def.reflect ? b.init('reflect', b.literal(true)) : undefined,
+ prop_def.reflect ? b.init('reflect', b.true) : undefined,
prop_def.type ? b.init('type', b.literal(prop_def.type)) : undefined
].filter(Boolean)
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js
index 510f32cde502..2e051ec67465 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js
@@ -11,7 +11,7 @@ import { parse_directive_name } from './shared/utils.js';
export function AnimateDirective(node, context) {
const expression =
node.expression === null
- ? b.literal(null)
+ ? b.null
: b.thunk(/** @type {Expression} */ (context.visit(node.expression)));
// in after_update to ensure it always happens after bind:this
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
index e0aef2d316a7..7588b24280d8 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
@@ -60,7 +60,7 @@ export function AwaitBlock(node, context) {
expression,
node.pending
? b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.pending)))
- : b.literal(null),
+ : b.null,
then_block,
catch_block
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BinaryExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BinaryExpression.js
index c8c54a5a599b..c5639208553d 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BinaryExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BinaryExpression.js
@@ -16,7 +16,7 @@ export function BinaryExpression(node, context) {
'$.strict_equals',
/** @type {Expression} */ (context.visit(node.left)),
/** @type {Expression} */ (context.visit(node.right)),
- operator === '!==' && b.literal(false)
+ operator === '!==' && b.false
);
}
@@ -25,7 +25,7 @@ export function BinaryExpression(node, context) {
'$.equals',
/** @type {Expression} */ (context.visit(node.left)),
/** @type {Expression} */ (context.visit(node.right)),
- operator === '!=' && b.literal(false)
+ operator === '!=' && b.false
);
}
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
index 0876fa30b6a5..fdd21b2b7ed8 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
@@ -40,9 +40,7 @@ export function IfBlock(node, context) {
b.if(
/** @type {Expression} */ (context.visit(node.test)),
b.stmt(b.call(b.id('$$render'), b.id(consequent_id))),
- alternate_id
- ? b.stmt(b.call(b.id('$$render'), b.id(alternate_id), b.literal(false)))
- : undefined
+ alternate_id ? b.stmt(b.call(b.id('$$render'), b.id(alternate_id), b.false)) : undefined
)
])
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
index 9b3ecc922d89..45a594af1f06 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
@@ -689,7 +689,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
'=',
b.member(node_id, 'value'),
b.conditional(
- b.binary('==', b.literal(null), b.assignment('=', b.member(node_id, '__value'), value)),
+ b.binary('==', b.null, b.assignment('=', b.member(node_id, '__value'), value)),
b.literal(''), // render null/undefined values as empty string to support placeholder options
value
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
index fdd705e32e75..c6f4ba1ed383 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
@@ -59,7 +59,7 @@ export function SlotElement(node, context) {
const fallback =
node.fragment.nodes.length === 0
- ? b.literal(null)
+ ? b.null
: b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.fragment)));
const slot = b.call(
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
index 084c1e7c675e..97cec7a729cd 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
@@ -95,7 +95,7 @@ export function build_set_attributes(
const call = b.call(
'$.set_attributes',
element_id,
- is_dynamic ? attributes_id : b.literal(null),
+ is_dynamic ? attributes_id : b.null,
b.object(values),
element.metadata.scoped &&
context.state.analysis.css.hash !== '' &&
@@ -120,7 +120,7 @@ export function build_set_attributes(
*/
export function build_attribute_value(value, context, memoize = (value) => value) {
if (value === true) {
- return { value: b.literal(true), has_state: false };
+ return { value: b.true, has_state: false };
}
if (!Array.isArray(value) || value.length === 1) {
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
index 7ece04ae3d66..e7925071cd2f 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
@@ -38,7 +38,7 @@ export function SlotElement(node, context) {
const fallback =
node.fragment.nodes.length === 0
- ? b.literal(null)
+ ? b.null
: b.thunk(/** @type {BlockStatement} */ (context.visit(node.fragment)));
const slot = b.call(
diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js
index 4ec2930cc2f5..736738d19f15 100644
--- a/packages/svelte/src/compiler/utils/builders.js
+++ b/packages/svelte/src/compiler/utils/builders.js
@@ -485,7 +485,7 @@ export function do_while(test, body) {
const true_instance = literal(true);
const false_instance = literal(false);
-const null_instane = literal(null);
+const null_instance = literal(null);
/** @type {ESTree.DebuggerStatement} */
const debugger_builder = {
@@ -647,7 +647,7 @@ export {
return_builder as return,
if_builder as if,
this_instance as this,
- null_instane as null,
+ null_instance as null,
debugger_builder as debugger
};
From e74fbcbbacc529bbb7ff8cc7d6b8b5d75d647cfa Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Fri, 14 Mar 2025 13:19:24 -0700
Subject: [PATCH 28/97] chore: don't distribute unused types definitions
(#15473)
hopefully helps with #15182, also makes the package smaller
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
---
.changeset/hungry-dancers-tap.md | 5 +++++
packages/svelte/package.json | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)
create mode 100644 .changeset/hungry-dancers-tap.md
diff --git a/.changeset/hungry-dancers-tap.md b/.changeset/hungry-dancers-tap.md
new file mode 100644
index 000000000000..51b2f86019af
--- /dev/null
+++ b/.changeset/hungry-dancers-tap.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+chore: don't distribute unused types definitions
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index b3ac0a5b518e..c74c9d34ca58 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -9,11 +9,12 @@
"node": ">=18"
},
"files": [
+ "*.d.ts",
"src",
"!src/**/*.test.*",
+ "!src/**/*.d.ts",
"types",
"compiler",
- "*.d.ts",
"README.md"
],
"module": "src/index-client.js",
From f227cfcea86b46e6a8ee389484d855c306ed66eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?E=CC=81ric=20NICOLAS?=
Date: Fri, 14 Mar 2025 22:30:49 +0100
Subject: [PATCH 29/97] fix: Allow global-like pseudo-selectors refinement
(#15313)
For instance, specifying a tree-structural pseudo-class to
`::view-transition-new` should still constitute a valid global-like
selector.
Fixes #15312
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
---
.changeset/gold-hairs-jog.md | 5 +++++
.../src/compiler/phases/2-analyze/css/css-analyze.js | 8 +++++++-
.../svelte/tests/css/samples/view-transition/expected.css | 6 ++++++
.../svelte/tests/css/samples/view-transition/input.svelte | 6 ++++++
4 files changed, 24 insertions(+), 1 deletion(-)
create mode 100644 .changeset/gold-hairs-jog.md
diff --git a/.changeset/gold-hairs-jog.md b/.changeset/gold-hairs-jog.md
new file mode 100644
index 000000000000..eaafced31447
--- /dev/null
+++ b/.changeset/gold-hairs-jog.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: allow global-like pseudo-selectors refinement
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
index ed228385820a..362ac9dcad50 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
@@ -133,7 +133,13 @@ const css_visitors = {
node.metadata.is_global = node.selectors.length >= 1 && is_global(node);
- if (node.selectors.length === 1) {
+ if (
+ node.selectors.length >= 1 &&
+ node.selectors.every(
+ (selector) =>
+ selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector'
+ )
+ ) {
const first = node.selectors[0];
node.metadata.is_global_like ||=
(first.type === 'PseudoClassSelector' && first.name === 'host') ||
diff --git a/packages/svelte/tests/css/samples/view-transition/expected.css b/packages/svelte/tests/css/samples/view-transition/expected.css
index afc84d52ebf5..e216a4d3ad9b 100644
--- a/packages/svelte/tests/css/samples/view-transition/expected.css
+++ b/packages/svelte/tests/css/samples/view-transition/expected.css
@@ -8,9 +8,15 @@
::view-transition-old {
animation-duration: 0.5s;
}
+ ::view-transition-old:only-child {
+ animation-duration: 0.5s;
+ }
::view-transition-new {
animation-duration: 0.5s;
}
+ ::view-transition-new:only-child {
+ animation-duration: 0.5s;
+ }
::view-transition-image-pair {
animation-duration: 0.5s;
}
diff --git a/packages/svelte/tests/css/samples/view-transition/input.svelte b/packages/svelte/tests/css/samples/view-transition/input.svelte
index ebb2b3fd88e0..345213ccd3f5 100644
--- a/packages/svelte/tests/css/samples/view-transition/input.svelte
+++ b/packages/svelte/tests/css/samples/view-transition/input.svelte
@@ -8,9 +8,15 @@
::view-transition-old {
animation-duration: 0.5s;
}
+ ::view-transition-old:only-child {
+ animation-duration: 0.5s;
+ }
::view-transition-new {
animation-duration: 0.5s;
}
+ ::view-transition-new:only-child {
+ animation-duration: 0.5s;
+ }
::view-transition-image-pair {
animation-duration: 0.5s;
}
From 8e9a21e374c3bafb79e046e64ef4d625f466749f Mon Sep 17 00:00:00 2001
From: 7nik
Date: Fri, 14 Mar 2025 23:44:28 +0200
Subject: [PATCH 30/97] fix: correctly match `:has()`'s selector during css
pruning (#15277)
Fixes #14072
`:has()` was matching only against descendants or siblings, but not sibling's descendants. This makes the logic be able to go forward or backwards, simplifying a lot of cases along the way.
---
.changeset/cuddly-chefs-refuse.md | 5 +
.../phases/2-analyze/css/css-prune.js | 427 +++++++++---------
.../svelte/tests/css/samples/has/_config.js | 148 +++---
.../svelte/tests/css/samples/has/expected.css | 13 +
.../svelte/tests/css/samples/has/input.svelte | 21 +
.../css/samples/render-tag-loop/_config.js | 17 +-
.../css/samples/render-tag-loop/expected.css | 9 +-
.../css/samples/render-tag-loop/input.svelte | 10 +-
8 files changed, 353 insertions(+), 297 deletions(-)
create mode 100644 .changeset/cuddly-chefs-refuse.md
diff --git a/.changeset/cuddly-chefs-refuse.md b/.changeset/cuddly-chefs-refuse.md
new file mode 100644
index 000000000000..6672ac4ab35d
--- /dev/null
+++ b/.changeset/cuddly-chefs-refuse.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: correctly match `:has()` selector during css pruning
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
index fc8108e46e8e..070ec7cd347e 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
@@ -5,9 +5,12 @@ import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../
import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js';
/** @typedef {NODE_PROBABLY_EXISTS | NODE_DEFINITELY_EXISTS} NodeExistsValue */
+/** @typedef {FORWARD | BACKWARD} Direction */
const NODE_PROBABLY_EXISTS = 0;
const NODE_DEFINITELY_EXISTS = 1;
+const FORWARD = 0;
+const BACKWARD = 1;
const whitelist_attribute_selector = new Map([
['details', ['open']],
@@ -43,6 +46,27 @@ const nesting_selector = {
}
};
+/** @type {Compiler.AST.CSS.RelativeSelector} */
+const any_selector = {
+ type: 'RelativeSelector',
+ start: -1,
+ end: -1,
+ combinator: null,
+ selectors: [
+ {
+ type: 'TypeSelector',
+ name: '*',
+ start: -1,
+ end: -1
+ }
+ ],
+ metadata: {
+ is_global: false,
+ is_global_like: false,
+ scoped: false
+ }
+};
+
/**
* Snippets encountered already (avoids infinite loops)
* @type {Set}
@@ -72,7 +96,8 @@ export function prune(stylesheet, element) {
apply_selector(
selectors,
/** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule),
- element
+ element,
+ BACKWARD
)
) {
node.metadata.used = true;
@@ -159,16 +184,17 @@ function truncate(node) {
* @param {Compiler.AST.CSS.RelativeSelector[]} relative_selectors
* @param {Compiler.AST.CSS.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
+ * @param {Direction} direction
* @returns {boolean}
*/
-function apply_selector(relative_selectors, rule, element) {
- const parent_selectors = relative_selectors.slice();
- const relative_selector = parent_selectors.pop();
+function apply_selector(relative_selectors, rule, element, direction) {
+ const rest_selectors = relative_selectors.slice();
+ const relative_selector = direction === FORWARD ? rest_selectors.shift() : rest_selectors.pop();
const matched =
!!relative_selector &&
- relative_selector_might_apply_to_node(relative_selector, rule, element) &&
- apply_combinator(relative_selector, parent_selectors, rule, element);
+ relative_selector_might_apply_to_node(relative_selector, rule, element, direction) &&
+ apply_combinator(relative_selector, rest_selectors, rule, element, direction);
if (matched) {
if (!is_outer_global(relative_selector)) {
@@ -183,76 +209,63 @@ function apply_selector(relative_selectors, rule, element) {
/**
* @param {Compiler.AST.CSS.RelativeSelector} relative_selector
- * @param {Compiler.AST.CSS.RelativeSelector[]} parent_selectors
+ * @param {Compiler.AST.CSS.RelativeSelector[]} rest_selectors
* @param {Compiler.AST.CSS.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
+ * @param {Direction} direction
* @returns {boolean}
*/
-function apply_combinator(relative_selector, parent_selectors, rule, node) {
- if (!relative_selector.combinator) return true;
+function apply_combinator(relative_selector, rest_selectors, rule, node, direction) {
+ const combinator =
+ direction == FORWARD ? rest_selectors[0]?.combinator : relative_selector.combinator;
+ if (!combinator) return true;
- const name = relative_selector.combinator.name;
-
- switch (name) {
+ switch (combinator.name) {
case ' ':
case '>': {
+ const is_adjacent = combinator.name === '>';
+ const parents =
+ direction === FORWARD
+ ? get_descendant_elements(node, is_adjacent)
+ : get_ancestor_elements(node, is_adjacent);
let parent_matched = false;
- const path = node.metadata.path;
- let i = path.length;
-
- while (i--) {
- const parent = path[i];
-
- if (parent.type === 'SnippetBlock') {
- if (seen.has(parent)) {
- parent_matched = true;
- } else {
- seen.add(parent);
-
- for (const site of parent.metadata.sites) {
- if (apply_combinator(relative_selector, parent_selectors, rule, site)) {
- parent_matched = true;
- }
- }
- }
-
- break;
- }
-
- if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
- if (apply_selector(parent_selectors, rule, parent)) {
- parent_matched = true;
- }
-
- if (name === '>') return parent_matched;
+ for (const parent of parents) {
+ if (apply_selector(rest_selectors, rule, parent, direction)) {
+ parent_matched = true;
}
}
- return parent_matched || parent_selectors.every((selector) => is_global(selector, rule));
+ return (
+ parent_matched ||
+ (direction === BACKWARD &&
+ (!is_adjacent || parents.length === 0) &&
+ rest_selectors.every((selector) => is_global(selector, rule)))
+ );
}
case '+':
case '~': {
- const siblings = get_possible_element_siblings(node, name === '+');
+ const siblings = get_possible_element_siblings(node, direction, combinator.name === '+');
let sibling_matched = false;
for (const possible_sibling of siblings.keys()) {
if (possible_sibling.type === 'RenderTag' || possible_sibling.type === 'SlotElement') {
// `{@render foo()}foo
` with `:global(.x) + p` is a match
- if (parent_selectors.length === 1 && parent_selectors[0].metadata.is_global) {
+ if (rest_selectors.length === 1 && rest_selectors[0].metadata.is_global) {
sibling_matched = true;
}
- } else if (apply_selector(parent_selectors, rule, possible_sibling)) {
+ } else if (apply_selector(rest_selectors, rule, possible_sibling, direction)) {
sibling_matched = true;
}
}
return (
sibling_matched ||
- (get_element_parent(node) === null &&
- parent_selectors.every((selector) => is_global(selector, rule)))
+ (direction === BACKWARD &&
+ get_element_parent(node) === null &&
+ rest_selectors.every((selector) => is_global(selector, rule)))
);
}
@@ -313,9 +326,10 @@ const regex_backslash_and_following_character = /\\(.)/g;
* @param {Compiler.AST.CSS.RelativeSelector} relative_selector
* @param {Compiler.AST.CSS.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
+ * @param {Direction} direction
* @returns {boolean}
*/
-function relative_selector_might_apply_to_node(relative_selector, rule, element) {
+function relative_selector_might_apply_to_node(relative_selector, rule, element, direction) {
// Sort :has(...) selectors in one bucket and everything else into another
const has_selectors = [];
const other_selectors = [];
@@ -331,13 +345,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
// If we're called recursively from a :has(...) selector, we're on the way of checking if the other selectors match.
// In that case ignore this check (because we just came from this) to avoid an infinite loop.
if (has_selectors.length > 0) {
- /** @type {Array} */
- const child_elements = [];
- /** @type {Array} */
- const descendant_elements = [];
- /** @type {Array} */
- let sibling_elements; // do them lazy because it's rarely used and expensive to calculate
-
// If this is a :has inside a global selector, we gotta include the element itself, too,
// because the global selector might be for an element that's outside the component,
// e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} }
@@ -353,46 +360,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
)
)
);
- if (include_self) {
- child_elements.push(element);
- descendant_elements.push(element);
- }
-
- const seen = new Set();
-
- /**
- * @param {Compiler.AST.SvelteNode} node
- * @param {{ is_child: boolean }} state
- */
- function walk_children(node, state) {
- walk(node, state, {
- _(node, context) {
- if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
- descendant_elements.push(node);
-
- if (context.state.is_child) {
- child_elements.push(node);
- context.state.is_child = false;
- context.next();
- context.state.is_child = true;
- } else {
- context.next();
- }
- } else if (node.type === 'RenderTag') {
- for (const snippet of node.metadata.snippets) {
- if (seen.has(snippet)) continue;
-
- seen.add(snippet);
- walk_children(snippet.body, context.state);
- }
- } else {
- context.next();
- }
- }
- });
- }
-
- walk_children(element.fragment, { is_child: true });
// :has(...) is special in that it means "look downwards in the CSS tree". Since our matching algorithm goes
// upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the
@@ -403,37 +370,34 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
let matched = false;
for (const complex_selector of complex_selectors) {
- const selectors = truncate(complex_selector);
- const left_most_combinator = selectors[0]?.combinator ?? descendant_combinator;
- // In .x:has(> y), we want to search for y, ignoring the left-most combinator
- // (else it would try to walk further up and fail because there are no selectors left)
- if (selectors.length > 0) {
- selectors[0] = {
- ...selectors[0],
- combinator: null
- };
+ const [first, ...rest] = truncate(complex_selector);
+ // if it was just a :global(...)
+ if (!first) {
+ complex_selector.metadata.used = true;
+ matched = true;
+ continue;
}
- const descendants =
- left_most_combinator.name === '+' || left_most_combinator.name === '~'
- ? (sibling_elements ??= get_following_sibling_elements(element, include_self))
- : left_most_combinator.name === '>'
- ? child_elements
- : descendant_elements;
-
- let selector_matched = false;
-
- // Iterate over all descendant elements and check if the selector inside :has matches
- for (const element of descendants) {
- if (
- selectors.length === 0 /* is :global(...) */ ||
- (element.metadata.scoped && selector_matched) ||
- apply_selector(selectors, rule, element)
- ) {
+ if (include_self) {
+ const selector_including_self = [
+ first.combinator ? { ...first, combinator: null } : first,
+ ...rest
+ ];
+ if (apply_selector(selector_including_self, rule, element, FORWARD)) {
complex_selector.metadata.used = true;
- selector_matched = matched = true;
+ matched = true;
}
}
+
+ const selector_excluding_self = [
+ any_selector,
+ first.combinator ? first : { ...first, combinator: descendant_combinator },
+ ...rest
+ ];
+ if (apply_selector(selector_excluding_self, rule, element, FORWARD)) {
+ complex_selector.metadata.used = true;
+ matched = true;
+ }
}
if (!matched) {
@@ -458,7 +422,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
) {
const args = selector.args;
const complex_selector = args.children[0];
- return apply_selector(complex_selector.children, rule, element);
+ return apply_selector(complex_selector.children, rule, element, BACKWARD);
}
// We came across a :global, everything beyond it is global and therefore a potential match
@@ -507,7 +471,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
if (is_global) {
complex_selector.metadata.used = true;
matched = true;
- } else if (apply_selector(relative, rule, element)) {
+ } else if (apply_selector(relative, rule, element, BACKWARD)) {
complex_selector.metadata.used = true;
matched = true;
} else if (complex_selector.children.length > 1 && (name == 'is' || name == 'where')) {
@@ -591,7 +555,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
for (const complex_selector of parent.prelude.children) {
if (
- apply_selector(get_relative_selectors(complex_selector), parent, element) ||
+ apply_selector(get_relative_selectors(complex_selector), parent, element, direction) ||
complex_selector.children.every((s) => is_global(s, parent))
) {
complex_selector.metadata.used = true;
@@ -612,80 +576,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
return true;
}
-/**
- * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
- * @param {boolean} include_self
- */
-function get_following_sibling_elements(element, include_self) {
- const path = element.metadata.path;
- let i = path.length;
-
- /** @type {Compiler.AST.SvelteNode} */
- let start = element;
- let nodes = /** @type {Compiler.AST.SvelteNode[]} */ (
- /** @type {Compiler.AST.Fragment} */ (path[0]).nodes
- );
-
- // find the set of nodes to walk...
- while (i--) {
- const node = path[i];
-
- if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
- nodes = node.fragment.nodes;
- break;
- }
-
- if (node.type !== 'Fragment') {
- start = node;
- }
- }
-
- /** @type {Array} */
- const siblings = [];
-
- // ...then walk them, starting from the node containing the element in question
- // skipping nodes that appears before the element
-
- const seen = new Set();
- let skip = true;
-
- /** @param {Compiler.AST.SvelteNode} node */
- function get_siblings(node) {
- walk(node, null, {
- RegularElement(node) {
- if (node === element) {
- skip = false;
- if (include_self) siblings.push(node);
- } else if (!skip) {
- siblings.push(node);
- }
- },
- SvelteElement(node) {
- if (node === element) {
- skip = false;
- if (include_self) siblings.push(node);
- } else if (!skip) {
- siblings.push(node);
- }
- },
- RenderTag(node) {
- for (const snippet of node.metadata.snippets) {
- if (seen.has(snippet)) continue;
-
- seen.add(snippet);
- get_siblings(snippet.body);
- }
- }
- });
- }
-
- for (const node of nodes.slice(nodes.indexOf(start))) {
- get_siblings(node);
- }
-
- return siblings;
-}
-
/**
* @param {any} operator
* @param {any} expected_value
@@ -822,6 +712,84 @@ function unquote(str) {
return str;
}
+/**
+ * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
+ * @param {boolean} adjacent_only
+ * @param {Set} seen
+ */
+function get_ancestor_elements(node, adjacent_only, seen = new Set()) {
+ /** @type {Array} */
+ const ancestors = [];
+
+ const path = node.metadata.path;
+ let i = path.length;
+
+ while (i--) {
+ const parent = path[i];
+
+ if (parent.type === 'SnippetBlock') {
+ if (!seen.has(parent)) {
+ seen.add(parent);
+
+ for (const site of parent.metadata.sites) {
+ ancestors.push(...get_ancestor_elements(site, adjacent_only, seen));
+ }
+ }
+
+ break;
+ }
+
+ if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
+ ancestors.push(parent);
+ if (adjacent_only) {
+ break;
+ }
+ }
+ }
+
+ return ancestors;
+}
+
+/**
+ * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
+ * @param {boolean} adjacent_only
+ * @param {Set} seen
+ */
+function get_descendant_elements(node, adjacent_only, seen = new Set()) {
+ /** @type {Array} */
+ const descendants = [];
+
+ /**
+ * @param {Compiler.AST.SvelteNode} node
+ */
+ function walk_children(node) {
+ walk(node, null, {
+ _(node, context) {
+ if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
+ descendants.push(node);
+
+ if (!adjacent_only) {
+ context.next();
+ }
+ } else if (node.type === 'RenderTag') {
+ for (const snippet of node.metadata.snippets) {
+ if (seen.has(snippet)) continue;
+
+ seen.add(snippet);
+ walk_children(snippet.body);
+ }
+ } else {
+ context.next();
+ }
+ }
+ });
+ }
+
+ walk_children(node.type === 'RenderTag' ? node : node.fragment);
+
+ return descendants;
+}
+
/**
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
* @returns {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | null}
@@ -843,11 +811,12 @@ function get_element_parent(node) {
/**
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
+ * @param {Direction} direction
* @param {boolean} adjacent_only
* @param {Set} seen
* @returns {Map}
*/
-function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
+function get_possible_element_siblings(node, direction, adjacent_only, seen = new Set()) {
/** @type {Map} */
const result = new Map();
const path = node.metadata.path;
@@ -859,9 +828,9 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
while (i--) {
const fragment = /** @type {Compiler.AST.Fragment} */ (path[i--]);
- let j = fragment.nodes.indexOf(current);
+ let j = fragment.nodes.indexOf(current) + (direction === FORWARD ? 1 : -1);
- while (j--) {
+ while (j >= 0 && j < fragment.nodes.length) {
const node = fragment.nodes[j];
if (node.type === 'RegularElement') {
@@ -876,21 +845,28 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
return result;
}
}
+ // Special case: slots, render tags and svelte:element tags could resolve to no siblings,
+ // so we want to continue until we find a definite sibling even with the adjacent-only combinator
} else if (is_block(node)) {
if (node.type === 'SlotElement') {
result.set(node, NODE_PROBABLY_EXISTS);
}
- const possible_last_child = get_possible_last_child(node, adjacent_only);
+ const possible_last_child = get_possible_nested_siblings(node, direction, adjacent_only);
add_to_map(possible_last_child, result);
if (adjacent_only && has_definite_elements(possible_last_child)) {
return result;
}
- } else if (node.type === 'RenderTag' || node.type === 'SvelteElement') {
+ } else if (node.type === 'SvelteElement') {
result.set(node, NODE_PROBABLY_EXISTS);
- // Special case: slots, render tags and svelte:element tags could resolve to no siblings,
- // so we want to continue until we find a definite sibling even with the adjacent-only combinator
+ } else if (node.type === 'RenderTag') {
+ result.set(node, NODE_PROBABLY_EXISTS);
+ for (const snippet of node.metadata.snippets) {
+ add_to_map(get_possible_nested_siblings(snippet, direction, adjacent_only), result);
+ }
}
+
+ j = direction === FORWARD ? j + 1 : j - 1;
}
current = path[i];
@@ -910,7 +886,7 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
seen.add(current);
for (const site of current.metadata.sites) {
- const siblings = get_possible_element_siblings(site, adjacent_only, seen);
+ const siblings = get_possible_element_siblings(site, direction, adjacent_only, seen);
add_to_map(siblings, result);
if (adjacent_only && current.metadata.sites.size === 1 && has_definite_elements(siblings)) {
@@ -923,7 +899,7 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
if (current.type === 'EachBlock' && fragment === current.body) {
// `{#each ...} {/each}` — `` can be previous sibling of ` `
- add_to_map(get_possible_last_child(current, adjacent_only), result);
+ add_to_map(get_possible_nested_siblings(current, direction, adjacent_only), result);
}
}
@@ -931,11 +907,13 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
}
/**
- * @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement} node
+ * @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement | Compiler.AST.SnippetBlock} node
+ * @param {Direction} direction
* @param {boolean} adjacent_only
+ * @param {Set} seen
* @returns {Map}
*/
-function get_possible_last_child(node, adjacent_only) {
+function get_possible_nested_siblings(node, direction, adjacent_only, seen = new Set()) {
/** @type {Array} */
let fragments = [];
@@ -956,12 +934,20 @@ function get_possible_last_child(node, adjacent_only) {
case 'SlotElement':
fragments.push(node.fragment);
break;
+
+ case 'SnippetBlock':
+ if (seen.has(node)) {
+ return new Map();
+ }
+ seen.add(node);
+ fragments.push(node.body);
+ break;
}
/** @type {Map} NodeMap */
const result = new Map();
- let exhaustive = node.type !== 'SlotElement';
+ let exhaustive = node.type !== 'SlotElement' && node.type !== 'SnippetBlock';
for (const fragment of fragments) {
if (fragment == null) {
@@ -969,7 +955,7 @@ function get_possible_last_child(node, adjacent_only) {
continue;
}
- const map = loop_child(fragment.nodes, adjacent_only);
+ const map = loop_child(fragment.nodes, direction, adjacent_only, seen);
exhaustive &&= has_definite_elements(map);
add_to_map(map, result);
@@ -1012,27 +998,28 @@ function add_to_map(from, to) {
}
/**
- * @param {NodeExistsValue | undefined} exist1
+ * @param {NodeExistsValue} exist1
* @param {NodeExistsValue | undefined} exist2
* @returns {NodeExistsValue}
*/
function higher_existence(exist1, exist2) {
- // @ts-expect-error TODO figure out if this is a bug
- if (exist1 === undefined || exist2 === undefined) return exist1 || exist2;
+ if (exist2 === undefined) return exist1;
return exist1 > exist2 ? exist1 : exist2;
}
/**
* @param {Compiler.AST.SvelteNode[]} children
+ * @param {Direction} direction
* @param {boolean} adjacent_only
+ * @param {Set} seen
*/
-function loop_child(children, adjacent_only) {
+function loop_child(children, direction, adjacent_only, seen) {
/** @type {Map} */
const result = new Map();
- let i = children.length;
+ let i = direction === FORWARD ? 0 : children.length - 1;
- while (i--) {
+ while (i >= 0 && i < children.length) {
const child = children[i];
if (child.type === 'RegularElement') {
@@ -1042,13 +1029,19 @@ function loop_child(children, adjacent_only) {
}
} else if (child.type === 'SvelteElement') {
result.set(child, NODE_PROBABLY_EXISTS);
+ } else if (child.type === 'RenderTag') {
+ for (const snippet of child.metadata.snippets) {
+ add_to_map(get_possible_nested_siblings(snippet, direction, adjacent_only, seen), result);
+ }
} else if (is_block(child)) {
- const child_result = get_possible_last_child(child, adjacent_only);
+ const child_result = get_possible_nested_siblings(child, direction, adjacent_only, seen);
add_to_map(child_result, result);
if (adjacent_only && has_definite_elements(child_result)) {
break;
}
}
+
+ i = direction === FORWARD ? i + 1 : i - 1;
}
return result;
diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js
index 8d89d98cbdb0..5700a09b9627 100644
--- a/packages/svelte/tests/css/samples/has/_config.js
+++ b/packages/svelte/tests/css/samples/has/_config.js
@@ -6,210 +6,238 @@ export default test({
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(y)"',
start: {
- line: 33,
+ line: 41,
column: 1,
- character: 330
+ character: 378
},
end: {
- line: 33,
+ line: 41,
column: 15,
- character: 344
+ character: 392
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(:global(y))"',
start: {
- line: 36,
+ line: 44,
column: 1,
- character: 365
+ character: 413
},
end: {
- line: 36,
+ line: 44,
column: 24,
- character: 388
+ character: 436
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(.unused)"',
start: {
- line: 39,
+ line: 47,
column: 1,
- character: 409
+ character: 457
},
end: {
- line: 39,
+ line: 47,
column: 15,
- character: 423
+ character: 471
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ":global(.foo):has(.unused)"',
start: {
- line: 42,
+ line: 50,
column: 1,
- character: 444
+ character: 492
},
end: {
- line: 42,
+ line: 50,
column: 27,
- character: 470
+ character: 518
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(y):has(.unused)"',
start: {
- line: 52,
+ line: 60,
column: 1,
- character: 578
+ character: 626
},
end: {
- line: 52,
+ line: 60,
column: 22,
- character: 599
+ character: 647
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused"',
start: {
- line: 71,
+ line: 79,
column: 2,
- character: 804
+ character: 852
},
end: {
- line: 71,
+ line: 79,
column: 9,
- character: 811
+ character: 859
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused x:has(y)"',
start: {
- line: 87,
+ line: 95,
column: 1,
- character: 958
+ character: 1006
},
end: {
- line: 87,
+ line: 95,
column: 17,
- character: 974
+ character: 1022
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(.unused)"',
start: {
- line: 90,
+ line: 98,
column: 1,
- character: 995
+ character: 1043
},
end: {
- line: 90,
+ line: 98,
column: 21,
- character: 1015
+ character: 1063
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(> z)"',
start: {
- line: 100,
+ line: 108,
column: 1,
- character: 1115
+ character: 1163
},
end: {
- line: 100,
+ line: 108,
column: 11,
- character: 1125
+ character: 1173
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(> d)"',
start: {
- line: 103,
+ line: 111,
column: 1,
- character: 1146
+ character: 1194
},
end: {
- line: 103,
+ line: 111,
column: 11,
- character: 1156
+ character: 1204
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(~ y)"',
start: {
- line: 123,
+ line: 131,
column: 1,
- character: 1348
+ character: 1396
},
end: {
- line: 123,
+ line: 131,
column: 11,
- character: 1358
+ character: 1406
+ }
+ },
+ {
+ code: 'css_unused_selector',
+ message: 'Unused CSS selector "d:has(+ f)"',
+ start: {
+ line: 141,
+ column: 1,
+ character: 1494
+ },
+ end: {
+ line: 141,
+ column: 11,
+ character: 1504
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "f:has(~ d)"',
start: {
- line: 133,
+ line: 144,
column: 1,
- character: 1446
+ character: 1525
},
end: {
- line: 133,
+ line: 144,
column: 11,
- character: 1456
+ character: 1535
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ":has(.unused)"',
start: {
- line: 141,
+ line: 152,
column: 2,
- character: 1529
+ character: 1608
},
end: {
- line: 141,
+ line: 152,
column: 15,
- character: 1542
+ character: 1621
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "&:has(.unused)"',
start: {
- line: 147,
+ line: 158,
column: 2,
- character: 1600
+ character: 1679
},
end: {
- line: 147,
+ line: 158,
column: 16,
- character: 1614
+ character: 1693
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ":global(.foo):has(.unused)"',
start: {
- line: 155,
+ line: 166,
column: 1,
- character: 1684
+ character: 1763
},
end: {
- line: 155,
+ line: 166,
column: 27,
- character: 1710
+ character: 1789
+ }
+ },
+ {
+ code: 'css_unused_selector',
+ message: 'Unused CSS selector "h:has(> h > i)"',
+ start: {
+ line: 173,
+ column: 1,
+ character: 1848
+ },
+ end: {
+ line: 173,
+ column: 15,
+ character: 1862
}
}
]
diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css
index b257370d61f3..2ce4d2bec5cd 100644
--- a/packages/svelte/tests/css/samples/has/expected.css
+++ b/packages/svelte/tests/css/samples/has/expected.css
@@ -118,6 +118,9 @@
d.svelte-xyz:has(~ f:where(.svelte-xyz)) {
color: green;
}
+ /* (unused) d:has(+ f) {
+ color: red;
+ }*/
/* (unused) f:has(~ d) {
color: red;
}*/
@@ -143,3 +146,13 @@
/* (unused) :global(.foo):has(.unused) {
color: red;
}*/
+
+ g.svelte-xyz:has(> h:where(.svelte-xyz) > i:where(.svelte-xyz)) {
+ color: green;
+ }
+ /* (unused) h:has(> h > i) {
+ color: red;
+ }*/
+ g.svelte-xyz:has(+ j:where(.svelte-xyz) > k:where(.svelte-xyz)) {
+ color: green;
+ }
\ No newline at end of file
diff --git a/packages/svelte/tests/css/samples/has/input.svelte b/packages/svelte/tests/css/samples/has/input.svelte
index 9b254996bf30..033471bc1696 100644
--- a/packages/svelte/tests/css/samples/has/input.svelte
+++ b/packages/svelte/tests/css/samples/has/input.svelte
@@ -9,6 +9,14 @@
+
+
+
+
+
+
+
+
diff --git a/packages/svelte/tests/css/samples/render-tag-loop/_config.js b/packages/svelte/tests/css/samples/render-tag-loop/_config.js
index f623b92cc38b..292c6c49ac9d 100644
--- a/packages/svelte/tests/css/samples/render-tag-loop/_config.js
+++ b/packages/svelte/tests/css/samples/render-tag-loop/_config.js
@@ -1,20 +1,5 @@
import { test } from '../../test';
export default test({
- warnings: [
- {
- code: 'css_unused_selector',
- message: 'Unused CSS selector "div + div"',
- start: {
- line: 19,
- column: 1,
- character: 185
- },
- end: {
- line: 19,
- column: 10,
- character: 194
- }
- }
- ]
+ warnings: []
});
diff --git a/packages/svelte/tests/css/samples/render-tag-loop/expected.css b/packages/svelte/tests/css/samples/render-tag-loop/expected.css
index 9ced15e96407..3e449286c997 100644
--- a/packages/svelte/tests/css/samples/render-tag-loop/expected.css
+++ b/packages/svelte/tests/css/samples/render-tag-loop/expected.css
@@ -2,9 +2,12 @@
div.svelte-xyz div:where(.svelte-xyz) {
color: green;
}
- /* (unused) div + div {
- color: red; /* this is marked as unused, but only because we've written an infinite loop - worth fixing? *\/
- }*/
+ div.svelte-xyz + div:where(.svelte-xyz) {
+ color: green;
+ }
div.svelte-xyz:has(div:where(.svelte-xyz)) {
color: green;
}
+ span.svelte-xyz:has(~span:where(.svelte-xyz)) {
+ color: green;
+ }
diff --git a/packages/svelte/tests/css/samples/render-tag-loop/input.svelte b/packages/svelte/tests/css/samples/render-tag-loop/input.svelte
index ade8df574489..3c55261f1845 100644
--- a/packages/svelte/tests/css/samples/render-tag-loop/input.svelte
+++ b/packages/svelte/tests/css/samples/render-tag-loop/input.svelte
@@ -12,14 +12,22 @@
{/snippet}
+{#snippet c()}
+
+ {@render c()}
+{/snippet}
+
From aaeda65f2f31585a4e48b452b73874e10fd4ebfc Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Fri, 14 Mar 2025 22:46:52 +0100
Subject: [PATCH 31/97] docs: add docs on state_unsafe_mutation error (#14932)
closes #14752
---
.../98-reference/.generated/client-errors.md | 24 +++++++++++++++++++
.../svelte/messages/client-errors/errors.md | 24 +++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index 2c2e0707ea12..0beb3cb9a96c 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -133,3 +133,27 @@ Reading state that was created inside the same derived is forbidden. Consider us
```
Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
```
+
+This error is thrown in a situation like this:
+
+```svelte
+
+
+ count++}>{count} / {multiple}
+```
+
+Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable.
+
+To fix this:
+- See if it's possible to refactor your `$derived` such that the update becomes unnecessary
+- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update
+- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates
diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md
index ce1f222c63ea..ab4d1519c18c 100644
--- a/packages/svelte/messages/client-errors/errors.md
+++ b/packages/svelte/messages/client-errors/errors.md
@@ -87,3 +87,27 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
## state_unsafe_mutation
> Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
+
+This error is thrown in a situation like this:
+
+```svelte
+
+
+ count++}>{count} / {multiple}
+```
+
+Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable.
+
+To fix this:
+- See if it's possible to refactor your `$derived` such that the update becomes unnecessary
+- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update
+- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates
From 32ee6c1bc252023e79acb9c6c255079964dfb665 Mon Sep 17 00:00:00 2001
From: adiGuba
Date: Sat, 15 Mar 2025 18:46:44 +0100
Subject: [PATCH 32/97] rune_invalid_arguments_length (#15516)
---
.changeset/two-spies-lie.md | 5 +++++
.../compiler/phases/2-analyze/visitors/CallExpression.js | 2 +-
.../samples/runes-wrong-state-raw-args/_config.js | 8 ++++++++
.../samples/runes-wrong-state-raw-args/main.svelte | 3 +++
.../samples/runes-wrong-state-raw-args/main.svelte.js | 1 +
5 files changed, 18 insertions(+), 1 deletion(-)
create mode 100644 .changeset/two-spies-lie.md
create mode 100644 packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/_config.js
create mode 100644 packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte
create mode 100644 packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte.js
diff --git a/.changeset/two-spies-lie.md b/.changeset/two-spies-lie.md
new file mode 100644
index 000000000000..2ea7fd61364a
--- /dev/null
+++ b/.changeset/two-spies-lie.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: throw rune_invalid_arguments_length when $state.raw() is used with more than 1 arg
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
index 6c2171785244..6ef323725b3f 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
@@ -117,7 +117,7 @@ export function CallExpression(node, context) {
if ((rune === '$derived' || rune === '$derived.by') && node.arguments.length !== 1) {
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
- } else if (rune === '$state' && node.arguments.length > 1) {
+ } else if (node.arguments.length > 1) {
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
}
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/_config.js
new file mode 100644
index 000000000000..af226559d11b
--- /dev/null
+++ b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/_config.js
@@ -0,0 +1,8 @@
+import { test } from '../../test';
+
+export default test({
+ error: {
+ code: 'rune_invalid_arguments_length',
+ message: '`$state.raw` must be called with zero or one arguments'
+ }
+});
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte
new file mode 100644
index 000000000000..2b50b43b9a2b
--- /dev/null
+++ b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte
@@ -0,0 +1,3 @@
+
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte.js
new file mode 100644
index 000000000000..442aaad14289
--- /dev/null
+++ b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte.js
@@ -0,0 +1 @@
+const foo = $state.raw(1, 2, 3);
From f30d75ab7e289f741379de7d715c0cb2508a3596 Mon Sep 17 00:00:00 2001
From: Garik Asplund <111464359+garikAsplund@users.noreply.github.com>
Date: Sun, 16 Mar 2025 14:25:52 -0700
Subject: [PATCH 33/97] =?UTF-8?q?updated=20->=20to=20=20=E2=86=92=20in=20v?=
=?UTF-8?q?5-migration-guide=20(#15526)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../docs/07-misc/07-v5-migration-guide.md | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/documentation/docs/07-misc/07-v5-migration-guide.md b/documentation/docs/07-misc/07-v5-migration-guide.md
index 87ff40cf472e..36e97763643e 100644
--- a/documentation/docs/07-misc/07-v5-migration-guide.md
+++ b/documentation/docs/07-misc/07-v5-migration-guide.md
@@ -10,7 +10,7 @@ You don't have to migrate to the new syntax right away - Svelte 5 still supports
At the heart of Svelte 5 is the new runes API. Runes are basically compiler instructions that inform Svelte about reactivity. Syntactically, runes are functions starting with a dollar-sign.
-### let -> $state
+### let → $state
In Svelte 4, a `let` declaration at the top level of a component was implicitly reactive. In Svelte 5, things are more explicit: a variable is reactive when created using the `$state` rune. Let's migrate the counter to runes mode by wrapping the counter in `$state`:
@@ -25,7 +25,7 @@ Nothing else changes. `count` is still the number itself, and you read and write
> [!DETAILS] Why we did this
> `let` being implicitly reactive at the top level worked great, but it meant that reactivity was constrained - a `let` declaration anywhere else was not reactive. This forced you to resort to using stores when refactoring code out of the top level of components for reuse. This meant you had to learn an entirely separate reactivity model, and the result often wasn't as nice to work with. Because reactivity is more explicit in Svelte 5, you can keep using the same API outside the top level of components. Head to [the tutorial](/tutorial) to learn more.
-### $: -> $derived/$effect
+### $: → $derived/$effect
In Svelte 4, a `$:` statement at the top level of a component could be used to declare a derivation, i.e. state that is entirely defined through a computation of other state. In Svelte 5, this is achieved using the `$derived` rune:
@@ -73,7 +73,7 @@ Note that [when `$effect` runs is different]($effect#Understanding-dependencies)
> - executing dependencies as needed and therefore being immune to ordering problems
> - being TypeScript-friendly
-### export let -> $props
+### export let → $props
In Svelte 4, properties of a component were declared using `export let`. Each property was one declaration. In Svelte 5, all properties are declared through the `$props` rune, through destructuring:
@@ -466,11 +466,11 @@ By now you should have a pretty good understanding of the before/after and how t
We thought the same, which is why we provide a migration script to do most of the migration automatically. You can upgrade your project by using `npx sv migrate svelte-5`. This will do the following things:
- bump core dependencies in your `package.json`
-- migrate to runes (`let` -> `$state` etc)
-- migrate to event attributes for DOM elements (`on:click` -> `onclick`)
-- migrate slot creations to render tags (` ` -> `{@render children()}`)
-- migrate slot usages to snippets (`...
` -> `{#snippet x()}...
{/snippet}`)
-- migrate obvious component creations (`new Component(...)` -> `mount(Component, ...)`)
+- migrate to runes (`let` → `$state` etc)
+- migrate to event attributes for DOM elements (`on:click` → `onclick`)
+- migrate slot creations to render tags (` ` → `{@render children()}`)
+- migrate slot usages to snippets (`...
` → `{#snippet x()}...
{/snippet}`)
+- migrate obvious component creations (`new Component(...)` → `mount(Component, ...)`)
You can also migrate a single component in VS Code through the `Migrate Component to Svelte 5 Syntax` command, or in our Playground through the `Migrate` button.
From e5881eade3e53316ee4329349cc1297c79d8522d Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Sun, 16 Mar 2025 17:32:22 -0400
Subject: [PATCH 34/97] chore: tweak migration doc diff blocks (#15527)
---
.../docs/07-misc/07-v5-migration-guide.md | 23 ++++++++++---------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/documentation/docs/07-misc/07-v5-migration-guide.md b/documentation/docs/07-misc/07-v5-migration-guide.md
index 36e97763643e..e502b7921a1e 100644
--- a/documentation/docs/07-misc/07-v5-migration-guide.md
+++ b/documentation/docs/07-misc/07-v5-migration-guide.md
@@ -16,7 +16,7 @@ In Svelte 4, a `let` declaration at the top level of a component was implicitly
```svelte
```
@@ -31,8 +31,8 @@ In Svelte 4, a `$:` statement at the top level of a component could be used to d
```svelte
```
@@ -42,7 +42,8 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is
```svelte
```
@@ -105,8 +106,8 @@ In Svelte 5, the `$props` rune makes this straightforward without any additional
```svelte
click me
@@ -192,9 +193,9 @@ This function is deprecated in Svelte 5. Instead, components should accept _call
```svelte
From 74917ae7039e512d6bf26b2b04f433eea7da8cd3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 16 Mar 2025 20:25:35 -0400
Subject: [PATCH 35/97] Version Packages (#15501)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/brown-rockets-shake.md | 5 -----
.changeset/cuddly-chefs-refuse.md | 5 -----
.changeset/curvy-countries-flow.md | 5 -----
.changeset/gold-hairs-jog.md | 5 -----
.changeset/hungry-dancers-tap.md | 5 -----
.changeset/plenty-bats-lay.md | 5 -----
.changeset/two-spies-lie.md | 5 -----
packages/svelte/CHANGELOG.md | 18 ++++++++++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
10 files changed, 20 insertions(+), 37 deletions(-)
delete mode 100644 .changeset/brown-rockets-shake.md
delete mode 100644 .changeset/cuddly-chefs-refuse.md
delete mode 100644 .changeset/curvy-countries-flow.md
delete mode 100644 .changeset/gold-hairs-jog.md
delete mode 100644 .changeset/hungry-dancers-tap.md
delete mode 100644 .changeset/plenty-bats-lay.md
delete mode 100644 .changeset/two-spies-lie.md
diff --git a/.changeset/brown-rockets-shake.md b/.changeset/brown-rockets-shake.md
deleted file mode 100644
index 3772a88f6ebd..000000000000
--- a/.changeset/brown-rockets-shake.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: invalidate parent effects when child effects update parent dependencies
diff --git a/.changeset/cuddly-chefs-refuse.md b/.changeset/cuddly-chefs-refuse.md
deleted file mode 100644
index 6672ac4ab35d..000000000000
--- a/.changeset/cuddly-chefs-refuse.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: correctly match `:has()` selector during css pruning
diff --git a/.changeset/curvy-countries-flow.md b/.changeset/curvy-countries-flow.md
deleted file mode 100644
index 6ef85458043d..000000000000
--- a/.changeset/curvy-countries-flow.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: replace `undefined` with `void 0` to avoid edge case
diff --git a/.changeset/gold-hairs-jog.md b/.changeset/gold-hairs-jog.md
deleted file mode 100644
index eaafced31447..000000000000
--- a/.changeset/gold-hairs-jog.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: allow global-like pseudo-selectors refinement
diff --git a/.changeset/hungry-dancers-tap.md b/.changeset/hungry-dancers-tap.md
deleted file mode 100644
index 51b2f86019af..000000000000
--- a/.changeset/hungry-dancers-tap.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-chore: don't distribute unused types definitions
diff --git a/.changeset/plenty-bats-lay.md b/.changeset/plenty-bats-lay.md
deleted file mode 100644
index cd5ce66e424e..000000000000
--- a/.changeset/plenty-bats-lay.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: add `files` and `group` to HTMLInputAttributes in elements.d.ts
diff --git a/.changeset/two-spies-lie.md b/.changeset/two-spies-lie.md
deleted file mode 100644
index 2ea7fd61364a..000000000000
--- a/.changeset/two-spies-lie.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: throw rune_invalid_arguments_length when $state.raw() is used with more than 1 arg
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 65b3edd1fdad..e10a606fe48b 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,23 @@
# svelte
+## 5.23.1
+
+### Patch Changes
+
+- fix: invalidate parent effects when child effects update parent dependencies ([#15506](https://github.com/sveltejs/svelte/pull/15506))
+
+- fix: correctly match `:has()` selector during css pruning ([#15277](https://github.com/sveltejs/svelte/pull/15277))
+
+- fix: replace `undefined` with `void 0` to avoid edge case ([#15511](https://github.com/sveltejs/svelte/pull/15511))
+
+- fix: allow global-like pseudo-selectors refinement ([#15313](https://github.com/sveltejs/svelte/pull/15313))
+
+- chore: don't distribute unused types definitions ([#15473](https://github.com/sveltejs/svelte/pull/15473))
+
+- fix: add `files` and `group` to HTMLInputAttributes in elements.d.ts ([#15492](https://github.com/sveltejs/svelte/pull/15492))
+
+- fix: throw rune_invalid_arguments_length when $state.raw() is used with more than 1 arg ([#15516](https://github.com/sveltejs/svelte/pull/15516))
+
## 5.23.0
### Minor Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index c74c9d34ca58..6f10b2a9ea60 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.23.0",
+ "version": "5.23.1",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 5f06fd07536e..32a50f3bcec5 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.23.0';
+export const VERSION = '5.23.1';
export const PUBLIC_VERSION = '5';
From 5b9f0df8ee97ba43a3d3af18f99f2dd44bd86965 Mon Sep 17 00:00:00 2001
From: Paolo Ricciuti
Date: Tue, 18 Mar 2025 11:51:26 +0100
Subject: [PATCH 36/97] fix: don't hoist listeners that access non hoistable
snippets (#15534)
* fix: don't hoist listeners that access non hoistable snippets
* chore: add comment
* chore: fix auto import fumble
---
.changeset/thick-pans-fold.md | 5 +++++
.../compiler/phases/2-analyze/visitors/Attribute.js | 9 +++++++++
.../unhoist-function-accessing-snippet/_config.js | 12 ++++++++++++
.../unhoist-function-accessing-snippet/main.svelte | 12 ++++++++++++
4 files changed, 38 insertions(+)
create mode 100644 .changeset/thick-pans-fold.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte
diff --git a/.changeset/thick-pans-fold.md b/.changeset/thick-pans-fold.md
new file mode 100644
index 000000000000..b5b5cee53ec5
--- /dev/null
+++ b/.changeset/thick-pans-fold.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't hoist listeners that access non hoistable snippets
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js
index 9124a8822f58..3ba81767cce3 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js
@@ -183,6 +183,15 @@ function get_delegated_event(event_name, handler, context) {
const binding = scope.get(reference);
const local_binding = context.state.scope.get(reference);
+ // if the function access a snippet that can't be hoisted we bail out
+ if (
+ local_binding !== null &&
+ local_binding.initial?.type === 'SnippetBlock' &&
+ !local_binding.initial.metadata.can_hoist
+ ) {
+ return unhoisted;
+ }
+
// If we are referencing a binding that is shadowed in another scope then bail out.
if (local_binding !== null && binding !== null && local_binding.node !== binding.node) {
return unhoisted;
diff --git a/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js
new file mode 100644
index 000000000000..b1229f5a8aad
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js
@@ -0,0 +1,12 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target, errors }) {
+ const button = target.querySelector('button');
+ flushSync(() => {
+ button?.click();
+ });
+ assert.deepEqual(errors, []);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte
new file mode 100644
index 000000000000..e909d77fd670
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+{#snippet snip()}
+ snippet {x}
+{/snippet}
\ No newline at end of file
From 0af6f20c77c209a5ea5691f2d1c15e0e359fbed6 Mon Sep 17 00:00:00 2001
From: henrykrinkle01 <162001892+henrykrinkle01@users.noreply.github.com>
Date: Tue, 18 Mar 2025 17:54:32 +0700
Subject: [PATCH 37/97] Fix grammar (#15533)
---
documentation/docs/06-runtime/02-context.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md
index 30799215b6eb..87b93a92b269 100644
--- a/documentation/docs/06-runtime/02-context.md
+++ b/documentation/docs/06-runtime/02-context.md
@@ -30,7 +30,7 @@ export const myGlobalState = $state({
This has a few drawbacks though:
- it only safely works when your global state is only used client-side - for example, when you're building a single page application that does not render any of your components on the server. If your state ends up being managed and updated on the server, it could end up being shared between sessions and/or users, causing bugs
-- it may give the false impression that certain state is global when in reality it should only used in a certain part of your app
+- it may give the false impression that certain state is global when in reality it should only be used in a certain part of your app
To solve these drawbacks, Svelte provides a few `context` primitives which alleviate these problems.
From 190c0c7653435fd983d13a9594f5d70bdb4dd26f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 18 Mar 2025 13:28:29 -0400
Subject: [PATCH 38/97] Version Packages (#15536)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/thick-pans-fold.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/thick-pans-fold.md
diff --git a/.changeset/thick-pans-fold.md b/.changeset/thick-pans-fold.md
deleted file mode 100644
index b5b5cee53ec5..000000000000
--- a/.changeset/thick-pans-fold.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: don't hoist listeners that access non hoistable snippets
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index e10a606fe48b..6461df1d25df 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.23.2
+
+### Patch Changes
+
+- fix: don't hoist listeners that access non hoistable snippets ([#15534](https://github.com/sveltejs/svelte/pull/15534))
+
## 5.23.1
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 6f10b2a9ea60..d005eca0b9c8 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.23.1",
+ "version": "5.23.2",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 32a50f3bcec5..191b52ecef42 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.23.1';
+export const VERSION = '5.23.2';
export const PUBLIC_VERSION = '5';
From 8f940ee0ff12be2ae6b393b4e021507d3f3e2068 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 19 Mar 2025 10:41:13 -0400
Subject: [PATCH 39/97] docs: use function bindings in "when not to use effect"
(#15544)
---
documentation/docs/02-runes/04-$effect.md | 42 ++++-------------------
1 file changed, 7 insertions(+), 35 deletions(-)
diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md
index e346bceba81c..6a2b565aeaa2 100644
--- a/documentation/docs/02-runes/04-$effect.md
+++ b/documentation/docs/02-runes/04-$effect.md
@@ -280,7 +280,7 @@ You might be tempted to do something convoluted with effects to link one value t
```
-Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAACo1SMW6EMBD8imWluFMSIEUaDiKlvy5lSOHjlhOSMRZeTiDkv8deMEEJRcqdmZ1ZjzzxqpZgePo5cRw18JQA_sSVaPz0rnVk7iDRYxdhYA8vW4Wg0NnwzJRdrfGtUAVKQIYtCsly9pIkp4AZ7cQOezAoEA7JcWUkVBuCdol0dNWrEutWsV5fHfnhPQ5wZJMnCwyejxCh6G6A0V3IHk4zu_jOxzzPBxBld83PTr7xXrb3rUNw8PbiYJ3FP22oTIoLSComq5XuXTeu8LzgnVA3KDgj13wiQ8taRaJ82rzXskYM-URRlsXktejjgNLoo9e4fyf70_8EnwncySX1GuunX6kGRwnzR_BgaPNaGy3FmLJKwrCUeBM6ZUn0Cs2mOlp3vwthQJ5i14P9st9vZqQlsQIAAA==)):
+Instead, use `oninput` callbacks or — better still — [function bindings](bind#Function-bindings) where possible ([demo](/playground/untitled#H4sIAAAAAAAAE51SsW6DMBT8FcvqABINdOhCIFKXTt06lg4GHpElYyz8iECIf69tcIIipo6-u3f3fPZMJWuBpvRzkBXyTpKSy5rLq6YRbbgATdOfmeKkrMgCBt9GPpQ66RsItFjJNBzhVScRJBobmumq5wovhSxQABLskAmSk7ckOXtMKyM22ItGhhAk4Z0R0OwIN-tIQzd-90HVhvy2HsGNiQFCMltBgd7XoecV2xzXNV7XaEcth7ZfRv7kujnsTX2Qd7USb5rFjwZkJlgJwpWRcakG04cpOS9oz-QVCuoeInXW-RyEJL-sG0b7Wy6kZWM-u7CFxM5tdrIl9qg72vB74H-y7T2iXROHyVb0CLanp1yNk4D1A1jQ91hzrQSbUtIIGLcir0ylJDm9Q7urz42bX4UwIk2xH2D5Xf4A7SeMcMQCAAA=)):
```svelte
-
+ spent, updateSpent} max={total} />
{spent}/{total} spent
-
+ left, updateLeft} max={total} />
{left}/{total} left
```
-If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/playground/untitled#H4sIAAAAAAAACpWRwW6DMBBEf8WyekikFOihFwcq9TvqHkyyQUjGsfCCQMj_XnvBNKpy6Qn2DTOD1wu_tRocF18Lx9kCFwT4iRvVxenT2syNoDGyWjl4xi93g2AwxPDSXfrW4oc0EjUgwzsqzSr2VhTnxJwNHwf24lAhHIpjVDZNwy1KS5wlNoGMSg9wOCYksQccerMlv65p51X0p_Xpdt_4YEy9yTkmV3z4MJT579-bUqsaNB2kbI0dwlnCgirJe2UakJzVrbkKaqkWivasU1O1ULxnOVk3JU-Uxti0p_-vKO4no_enbQ_yXhnZn0aHs4b1jiJMK7q2zmo1C3bTMG3LaZQVrMjeoSPgaUtkDxePMCEX2Ie6b_8D4WyJJEwCAAA=)):
-
-```svelte
-
-
-
-
- {spent}/{total} spent
-
-
-
-
- {left.value}/{total} left
-
-```
-
If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](svelte#untrack).
From 701f085c82d11e6064433731d36b33d4894c706a Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 19 Mar 2025 10:43:45 -0400
Subject: [PATCH 40/97] docs: rewrite context docs (#15541)
---
documentation/docs/06-runtime/02-context.md | 164 ++++++++++----------
1 file changed, 86 insertions(+), 78 deletions(-)
diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md
index 87b93a92b269..b698323a04ee 100644
--- a/documentation/docs/06-runtime/02-context.md
+++ b/documentation/docs/06-runtime/02-context.md
@@ -2,129 +2,137 @@
title: Context
---
-
+Context allows components to access values owned by parent components without passing them down as props (potentially through many layers of intermediate components, known as 'prop-drilling'). The parent component sets context with `setContext(key, value)`...
-Most state is component-level state that lives as long as its component lives. There's also section-wide or app-wide state however, which also needs to be handled somehow.
-
-The easiest way to do that is to create global state and just import that.
+```svelte
+
+
```
+...and the child retrieves it with `getContext`:
+
```svelte
-
+
+
+{message}, inside Child.svelte
```
-This has a few drawbacks though:
+This is particularly useful when `Parent.svelte` is not directly aware of `Child.svelte`, but instead renders it as part of a `children` [snippet](snippet) ([demo](/playground/untitled#H4sIAAAAAAAAE42Q3W6DMAyFX8WyJgESK-oto6hTX2D3YxcM3IIUQpR40yqUd58CrCXsp7tL7HNsf2dAWXaEKR56yfTBGOOxFWQwfR6Qz8q1XAHjL-GjUhvzToJd7bU09FO9ctMkG0wxM5VuFeeFLLjtVK8ZnkpNkuGo-w6CTTJ9Z3PwsBAemlbUF934W8iy5DpaZtOUcU02-ZLcaS51jHEkTFm_kY1_wfOO8QnXrb8hBzDEc6pgZ4gFoyz4KgiD7nxfTe8ghqAhIfrJ46cTzVZBbkPlODVJsLCDO6V7ZcJoncyw1yRr0hd1GNn_ZbEM3I9i1bmVxOlWElUvDUNHxpQngt3C4CXzjS1rtvkw22wMrTRtTbC8Lkuabe7jvthPPe3DofYCAAA=)):
+
+```svelte
+
+
+
+```
-- it only safely works when your global state is only used client-side - for example, when you're building a single page application that does not render any of your components on the server. If your state ends up being managed and updated on the server, it could end up being shared between sessions and/or users, causing bugs
-- it may give the false impression that certain state is global when in reality it should only be used in a certain part of your app
+The key (`'my-context'`, in the example above) and the context itself can be any JavaScript value.
-To solve these drawbacks, Svelte provides a few `context` primitives which alleviate these problems.
+In addition to [`setContext`](svelte#setContext) and [`getContext`](svelte#getContext), Svelte exposes [`hasContext`](svelte#hasContext) and [`getAllContexts`](svelte#getAllContexts) functions.
-## Setting and getting context
+## Using context with state
-To associate an arbitrary object with the current component, use `setContext`.
+You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAAAE41R0W6DMAz8FSuaBNUQdK8MkKZ-wh7HHihzu6hgosRMm1D-fUpSVNq12x4iEvvOx_kmQU2PIhfP3DCCJGgHYvxkkYid7NCI_GUS_KUcxhVEMjOelErNB3bsatvG4LW6n0ZsRC4K02qpuKqpZtmrQTNMYJA3QRAs7PTQQxS40eMCt3mX3duxnWb-lS5h7nTI0A4jMWoo4c44P_Hku-zrOazdy64chWo-ScfRkRgl8wgHKrLTH1OxHZkHgoHaTraHcopXUFYzPPVfuC_hwQaD1GrskdiNCdQwJljJqlvXfyqVsA5CGg0uRUQifHw56xFtciO75QrP07vo_JXf_tf8yK2ezDKY_ZWt_1y2qqYzv7bI1IW1V_sN19m-07wCAAA=))...
```svelte
+
+ counter.count += 1}>
+ increment
+
+
+
+
+
```
-The context is then available to children of the component (including slotted content) with `getContext`.
+...though note that if you _reassign_ `counter` instead of updating it, you will 'break the link' — in other words instead of this...
```svelte
-
+ counter = { count: 0 }}>
+ reset
+
```
-`setContext` and `getContext` solve the above problems:
+...you must do this:
-- the state is not global, it's scoped to the component. That way it's safe to render your components on the server and not leak state
-- it's clear that the state is not global but rather scoped to a specific component tree and therefore can't be used in other parts of your app
+```svelte
+ +++counter.count = 0+++}>
+ reset
+
+```
-> [!NOTE] `setContext`/`getContext` must be called during component initialisation.
+Svelte will warn you if you get it wrong.
-Context is not inherently reactive. If you need reactive values in context then you can pass a `$state` object into context, whose properties _will_ be reactive.
+## Type-safe context
-```svelte
-
-
+```js
+/// file: context.js
+// @filename: ambient.d.ts
+interface User {}
- value.count++}>increment
-```
+// @filename: index.js
+// ---cut---
+import { getContext, setContext } from 'svelte';
-```svelte
-
-
+/** @param {User} user */
+export function setUserContext(user) {
+ setContext(key, user);
+}
-Count is {value.count}
+export function getUserContext() {
+ return /** @type {User} */ (getContext(key));
+}
```
-To check whether a given `key` has been set in the context of a parent component, use `hasContext`.
+## Replacing global state
-```svelte
-
+ // ...
+});
```
-You can also retrieve the whole context map that belongs to the closest parent component using `getAllContexts`. This is useful, for example, if you programmatically create a component and want to pass the existing context to it.
+In many cases this is perfectly fine, but there is a risk: if you mutate the state during server-side rendering (which is discouraged, but entirely possible!)...
```svelte
+
```
-## Encapsulating context interactions
-
-The above methods are very unopinionated about how to use them. When your app grows in scale, it's worthwhile to encapsulate setting and getting the context into functions and properly type them.
-
-```ts
-// @errors: 2304
-import { getContext, setContext } from 'svelte';
-
-let userKey = Symbol('user');
-
-export function setUserContext(user: User) {
- setContext(userKey, user);
-}
-
-export function getUserContext(): User {
- return getContext(userKey) as User;
-}
-```
+...then the data may be accessible by the _next_ user. Context solves this problem because it is not shared between requests.
From 99ca7a4d7f5948f94c2fa0137a481b57c4a6c17b Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 19 Mar 2025 14:06:20 -0400
Subject: [PATCH 41/97] chore: create stack lazily when proxying value (#15547)
---
packages/svelte/src/internal/client/proxy.js | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js
index 4c262880f1cd..29828a7c995d 100644
--- a/packages/svelte/src/internal/client/proxy.js
+++ b/packages/svelte/src/internal/client/proxy.js
@@ -25,11 +25,6 @@ import { tracing_mode_flag } from '../flags/index.js';
* @returns {T}
*/
export function proxy(value, parent = null, prev) {
- /** @type {Error | null} */
- var stack = null;
- if (DEV && tracing_mode_flag) {
- stack = get_stack('CreatedAt');
- }
// if non-proxyable, or is already a proxy, return `value`
if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) {
return value;
@@ -46,6 +41,8 @@ export function proxy(value, parent = null, prev) {
var is_proxied_array = is_array(value);
var version = source(0);
+ var stack = DEV && tracing_mode_flag ? get_stack('CreatedAt') : null;
+
if (is_proxied_array) {
// We need to create the length source eagerly to ensure that
// mutations to the array are properly synced with our proxy
From c436b6cdbe01577b219ddbfd09e23c5765515004 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 19 Mar 2025 17:21:26 -0400
Subject: [PATCH 42/97] fix: simplify set calls for proxyable values (#15548)
* chore: simplify set calls for proxyable values
* changeset
---
.changeset/nine-laws-rush.md | 5 ++
.../phases/3-transform/client/types.d.ts | 2 +-
.../phases/3-transform/client/utils.js | 8 ----
.../client/visitors/AssignmentExpression.js | 46 +++++++------------
.../3-transform/client/visitors/ClassBody.js | 15 ++----
.../client/visitors/shared/declarations.js | 4 +-
.../src/internal/client/reactivity/sources.js | 8 +++-
.../_expected/client/index.svelte.js | 2 +-
.../_expected/client/index.svelte.js | 4 +-
.../_expected/client/index.svelte.js | 4 +-
.../_expected/client/index.svelte.js | 2 +-
11 files changed, 40 insertions(+), 60 deletions(-)
create mode 100644 .changeset/nine-laws-rush.md
diff --git a/.changeset/nine-laws-rush.md b/.changeset/nine-laws-rush.md
new file mode 100644
index 000000000000..e0a0fc15a0a6
--- /dev/null
+++ b/.changeset/nine-laws-rush.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: simplify set calls for proxyable values
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
index 63fe3223cf7d..243e1c64a33c 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
+++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
@@ -30,7 +30,7 @@ export interface ClientTransformState extends TransformState {
/** turn `foo` into e.g. `$.get(foo)` */
read: (id: Identifier) => Expression;
/** turn `foo = bar` into e.g. `$.set(foo, bar)` */
- assign?: (node: Identifier, value: Expression) => Expression;
+ assign?: (node: Identifier, value: Expression, proxy?: boolean) => Expression;
/** turn `foo.bar = baz` into e.g. `$.mutate(foo, $.get(foo).bar = baz);` */
mutate?: (node: Identifier, mutation: AssignmentExpression | UpdateExpression) => Expression;
/** turn `foo++` into e.g. `$.update(foo)` */
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index 421118cf680b..28e3fabb1990 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -45,14 +45,6 @@ export function build_getter(node, state) {
return node;
}
-/**
- * @param {Expression} value
- * @param {Expression} previous
- */
-export function build_proxy_reassignment(value, previous) {
- return dev ? b.call('$.proxy', value, b.null, previous) : b.call('$.proxy', value);
-}
-
/**
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
* @param {ComponentContext} context
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 a8c615af936f..150c56e166c1 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
@@ -1,5 +1,4 @@
-/** @import { Location } from 'locate-character' */
-/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Literal, MemberExpression, Pattern } from 'estree' */
+/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Pattern } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { Context } from '../types.js' */
import * as b from '../../../../utils/builders.js';
@@ -8,8 +7,8 @@ import {
get_attribute_expression,
is_event_attribute
} from '../../../../utils/ast.js';
-import { dev, filename, is_ignored, locate_node, locator } from '../../../../state.js';
-import { build_proxy_reassignment, should_proxy } from '../utils.js';
+import { dev, is_ignored, locate_node } from '../../../../state.js';
+import { should_proxy } from '../utils.js';
import { visit_assignment_expression } from '../../shared/assignments.js';
/**
@@ -65,21 +64,12 @@ function build_assignment(operator, left, right, context) {
context.visit(build_assignment_value(operator, left, right))
);
- if (
+ const needs_proxy =
private_state.kind === 'state' &&
is_non_coercive_operator(operator) &&
- should_proxy(value, context.state.scope)
- ) {
- value = build_proxy_reassignment(value, b.member(b.this, private_state.id));
- }
-
- if (context.state.in_constructor) {
- // inside the constructor, we can assign to `this.#foo.v` rather than using `$.set`,
- // since nothing is tracking the signal at this point
- return b.assignment(operator, /** @type {Pattern} */ (context.visit(left)), value);
- }
+ should_proxy(value, context.state.scope);
- return b.call('$.set', left, value);
+ return b.call('$.set', left, value, needs_proxy && b.true);
}
}
@@ -113,20 +103,18 @@ function build_assignment(operator, left, right, context) {
context.visit(build_assignment_value(operator, left, right))
);
- if (
+ return transform.assign(
+ object,
+ value,
!is_primitive &&
- binding.kind !== 'prop' &&
- binding.kind !== 'bindable_prop' &&
- binding.kind !== 'raw_state' &&
- binding.kind !== 'store_sub' &&
- context.state.analysis.runes &&
- should_proxy(right, context.state.scope) &&
- is_non_coercive_operator(operator)
- ) {
- value = build_proxy_reassignment(value, object);
- }
-
- return transform.assign(object, value);
+ binding.kind !== 'prop' &&
+ binding.kind !== 'bindable_prop' &&
+ binding.kind !== 'raw_state' &&
+ binding.kind !== 'store_sub' &&
+ context.state.analysis.runes &&
+ should_proxy(right, context.state.scope) &&
+ is_non_coercive_operator(operator)
+ );
}
// mutation
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
index ed800e5226ce..5787b590a8f9 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
@@ -5,7 +5,7 @@ import { dev, is_ignored } from '../../../../state.js';
import * as b from '../../../../utils/builders.js';
import { regex_invalid_identifier_chars } from '../../../patterns.js';
import { get_rune } from '../../../scope.js';
-import { build_proxy_reassignment, should_proxy } from '../utils.js';
+import { should_proxy } from '../utils.js';
/**
* @param {ClassBody} node
@@ -142,29 +142,20 @@ export function ClassBody(node, context) {
// get foo() { return this.#foo; }
body.push(b.method('get', definition.key, [], [b.return(b.call('$.get', member))]));
- if (field.kind === 'state') {
+ if (field.kind === 'state' || field.kind === 'raw_state') {
// set foo(value) { this.#foo = value; }
const value = b.id('value');
- const prev = b.member(b.this, field.id);
body.push(
b.method(
'set',
definition.key,
[value],
- [b.stmt(b.call('$.set', member, build_proxy_reassignment(value, prev)))]
+ [b.stmt(b.call('$.set', member, value, field.kind === 'state' && b.true))]
)
);
}
- if (field.kind === 'raw_state') {
- // set foo(value) { this.#foo = value; }
- const value = b.id('value');
- body.push(
- b.method('set', definition.key, [value], [b.stmt(b.call('$.set', member, value))])
- );
- }
-
if (dev && (field.kind === 'derived' || field.kind === 'derived_by')) {
body.push(
b.method(
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js
index 0bd8c352f6a9..a13ecfed2ce5 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js
@@ -24,8 +24,8 @@ export function add_state_transformers(context) {
) {
context.state.transform[name] = {
read: binding.declaration_kind === 'var' ? (node) => b.call('$.safe_get', node) : get_value,
- assign: (node, value) => {
- let call = b.call('$.set', node, value);
+ assign: (node, value, proxy = false) => {
+ let call = b.call('$.set', node, value, proxy && b.true);
if (context.state.scope.get(`$${node.name}`)?.kind === 'store_sub') {
call = b.call('$.store_unsub', call, b.literal(`$${node.name}`), b.id('$$stores'));
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 49584e862624..92508945c96c 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -33,6 +33,7 @@ import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
import { get_stack } from '../dev/tracing.js';
import { component_context, is_runes } from '../context.js';
+import { proxy } from '../proxy.js';
export let inspect_effects = new Set();
export const old_values = new Map();
@@ -143,9 +144,10 @@ export function mutate(source, value) {
* @template V
* @param {Source} source
* @param {V} value
+ * @param {boolean} [should_proxy]
* @returns {V}
*/
-export function set(source, value) {
+export function set(source, value, should_proxy = false) {
if (
active_reaction !== null &&
!untracking &&
@@ -158,7 +160,9 @@ export function set(source, value) {
e.state_unsafe_mutation();
}
- return internal_set(source, value);
+ let new_value = should_proxy ? proxy(value, null, source) : value;
+
+ return internal_set(source, new_value);
}
/**
diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js
index fa990b33ee56..390e86a3510a 100644
--- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js
@@ -23,7 +23,7 @@ export default function Bind_component_snippet($$anchor) {
return $.get(value);
},
set value($$value) {
- $.set(value, $.proxy($$value));
+ $.set(value, $$value, true);
}
});
diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js
index 2898f31a6fb5..21339741761f 100644
--- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js
@@ -12,14 +12,14 @@ export default function Class_state_field_constructor_assignment($$anchor, $$pro
}
set a(value) {
- $.set(this.#a, $.proxy(value));
+ $.set(this.#a, value, true);
}
#b = $.state();
constructor() {
this.a = 1;
- this.#b.v = 2;
+ $.set(this.#b, 2);
}
}
diff --git a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js
index 9651713c52f5..47f297bce9c7 100644
--- a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js
@@ -8,8 +8,8 @@ let d = 4;
export function update(array) {
(
- $.set(a, $.proxy(array[0])),
- $.set(b, $.proxy(array[1]))
+ $.set(a, array[0], true),
+ $.set(b, array[1], true)
);
[c, d] = array;
diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js
index c545608bcacf..762a23754c9b 100644
--- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js
@@ -13,7 +13,7 @@ export default function Function_prop_no_getter($$anchor) {
Button($$anchor, {
onmousedown: () => $.set(count, $.get(count) + 1),
onmouseup,
- onmouseenter: () => $.set(count, $.proxy(plusOne($.get(count)))),
+ onmouseenter: () => $.set(count, plusOne($.get(count)), true),
children: ($$anchor, $$slotProps) => {
$.next();
From c7ce9fc004325d5e5c957943f4c5e342e8304905 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Thu, 20 Mar 2025 14:19:42 -0400
Subject: [PATCH 43/97] fix benchmarks (#15560)
---
benchmarking/compare/index.js | 1 -
benchmarking/compare/runner.js | 4 ++--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/benchmarking/compare/index.js b/benchmarking/compare/index.js
index a5fc6d10a9a1..9d8d279c353a 100644
--- a/benchmarking/compare/index.js
+++ b/benchmarking/compare/index.js
@@ -2,7 +2,6 @@ import fs from 'node:fs';
import path from 'node:path';
import { execSync, fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
-import { benchmarks } from '../benchmarks.js';
// if (execSync('git status --porcelain').toString().trim()) {
// console.error('Working directory is not clean');
diff --git a/benchmarking/compare/runner.js b/benchmarking/compare/runner.js
index 6fa58e2bacf3..a2e864637969 100644
--- a/benchmarking/compare/runner.js
+++ b/benchmarking/compare/runner.js
@@ -1,7 +1,7 @@
-import { benchmarks } from '../benchmarks.js';
+import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js';
const results = [];
-for (const benchmark of benchmarks) {
+for (const benchmark of reactivity_benchmarks) {
const result = await benchmark();
console.error(result.benchmark);
results.push(result);
From 6915c12b583f4d62c125161be07f5d09573918c9 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Thu, 20 Mar 2025 16:04:00 -0400
Subject: [PATCH 44/97] feat: allow state created in deriveds/effects to be
written/read locally without self-invalidation (#15553)
* move parent property onto Signal
* don't self-invalidate when updating a source create inside current reaction
* lazily create deep state with parent reaction
* no need to push_derived_source with mutable_state, as it never coexists with $.derived
* reduce indirection
* remove state_unsafe_local_read error
* changeset
* tests
* fix test
* inelegant fix
* remove arg
* tweak
* some progress
* more
* tidy up
* parent -> p
* tmp
* alternative approach
* tidy up
* reduce diff size
* more
* update comment
---
.changeset/dirty-pianos-sparkle.md | 5 ++
.../98-reference/.generated/client-errors.md | 6 --
.../svelte/messages/client-errors/errors.md | 4 --
.../3-transform/client/transform-client.js | 5 +-
.../client/visitors/VariableDeclaration.js | 4 +-
.../svelte/src/internal/client/constants.js | 1 +
packages/svelte/src/internal/client/errors.js | 15 -----
packages/svelte/src/internal/client/index.js | 9 ++-
packages/svelte/src/internal/client/proxy.js | 67 +++++++++++++++----
.../src/internal/client/reactivity/sources.js | 57 +++++-----------
.../svelte/src/internal/client/runtime.js | 30 +++++----
packages/svelte/tests/signals/test.ts | 49 ++++++++++++--
12 files changed, 151 insertions(+), 101 deletions(-)
create mode 100644 .changeset/dirty-pianos-sparkle.md
diff --git a/.changeset/dirty-pianos-sparkle.md b/.changeset/dirty-pianos-sparkle.md
new file mode 100644
index 000000000000..b3e4dd1d8c1b
--- /dev/null
+++ b/.changeset/dirty-pianos-sparkle.md
@@ -0,0 +1,5 @@
+---
+'svelte': minor
+---
+
+feat: allow state created in deriveds/effects to be written/read locally without self-invalidation
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index 0beb3cb9a96c..62d9c3302a3c 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -122,12 +122,6 @@ Property descriptors defined on `$state` objects must contain `value` and always
Cannot set prototype of `$state` object
```
-### state_unsafe_local_read
-
-```
-Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
-```
-
### state_unsafe_mutation
```
diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md
index ab4d1519c18c..bc8ec3625605 100644
--- a/packages/svelte/messages/client-errors/errors.md
+++ b/packages/svelte/messages/client-errors/errors.md
@@ -80,10 +80,6 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
> Cannot set prototype of `$state` object
-## state_unsafe_local_read
-
-> Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
-
## state_unsafe_mutation
> Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index ac8263b91669..0bdfbae746d0 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -219,7 +219,10 @@ export function client_component(analysis, options) {
for (const [name, binding] of analysis.instance.scope.declarations) {
if (binding.kind === 'legacy_reactive') {
legacy_reactive_declarations.push(
- b.const(name, b.call('$.mutable_state', undefined, analysis.immutable ? b.true : undefined))
+ b.const(
+ name,
+ b.call('$.mutable_source', undefined, analysis.immutable ? b.true : undefined)
+ )
);
}
if (binding.kind === 'store_sub') {
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
index baffc5dec374..3a914fb56099 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
@@ -299,7 +299,7 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
return [
b.declarator(
declarator.id,
- b.call('$.mutable_state', value, analysis.immutable ? b.true : undefined)
+ b.call('$.mutable_source', value, analysis.immutable ? b.true : undefined)
)
];
}
@@ -314,7 +314,7 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
return b.declarator(
path.node,
binding?.kind === 'state'
- ? b.call('$.mutable_state', value, analysis.immutable ? b.true : undefined)
+ ? b.call('$.mutable_source', value, analysis.immutable ? b.true : undefined)
: value
);
})
diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js
index a4840ce4ebd0..21377c1cc85f 100644
--- a/packages/svelte/src/internal/client/constants.js
+++ b/packages/svelte/src/internal/client/constants.js
@@ -20,6 +20,7 @@ export const LEGACY_DERIVED_PROP = 1 << 17;
export const INSPECT_EFFECT = 1 << 18;
export const HEAD_EFFECT = 1 << 19;
export const EFFECT_HAS_DERIVED = 1 << 20;
+export const EFFECT_IS_UPDATING = 1 << 21;
export const STATE_SYMBOL = Symbol('$state');
export const STATE_SYMBOL_METADATA = Symbol('$state metadata');
diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js
index 682816e1d64b..8a5b5033a78c 100644
--- a/packages/svelte/src/internal/client/errors.js
+++ b/packages/svelte/src/internal/client/errors.js
@@ -307,21 +307,6 @@ export function state_prototype_fixed() {
}
}
-/**
- * Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
- * @returns {never}
- */
-export function state_unsafe_local_read() {
- if (DEV) {
- const error = new Error(`state_unsafe_local_read\nReading state that was created inside the same derived is forbidden. Consider using \`untrack\` to read locally created state\nhttps://svelte.dev/e/state_unsafe_local_read`);
-
- error.name = 'Svelte error';
- throw error;
- } else {
- throw new Error(`https://svelte.dev/e/state_unsafe_local_read`);
- }
-}
-
/**
* Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
* @returns {never}
diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js
index 31da00dbb448..723ff57678b0 100644
--- a/packages/svelte/src/internal/client/index.js
+++ b/packages/svelte/src/internal/client/index.js
@@ -113,7 +113,14 @@ export {
user_effect,
user_pre_effect
} from './reactivity/effects.js';
-export { mutable_state, mutate, set, state, update, update_pre } from './reactivity/sources.js';
+export {
+ mutable_source,
+ mutate,
+ set,
+ source as state,
+ update,
+ update_pre
+} from './reactivity/sources.js';
export {
prop,
rest_props,
diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js
index 29828a7c995d..9c3c0cf29f29 100644
--- a/packages/svelte/src/internal/client/proxy.js
+++ b/packages/svelte/src/internal/client/proxy.js
@@ -1,6 +1,6 @@
/** @import { ProxyMetadata, Source } from '#client' */
import { DEV } from 'esm-env';
-import { get, active_effect } from './runtime.js';
+import { get, active_effect, active_reaction, set_active_reaction } from './runtime.js';
import { component_context } from './context.js';
import {
array_prototype,
@@ -17,14 +17,16 @@ import * as e from './errors.js';
import { get_stack } from './dev/tracing.js';
import { tracing_mode_flag } from '../flags/index.js';
+/** @type {ProxyMetadata | null} */
+var parent_metadata = null;
+
/**
* @template T
* @param {T} value
- * @param {ProxyMetadata | null} [parent]
* @param {Source} [prev] dev mode only
* @returns {T}
*/
-export function proxy(value, parent = null, prev) {
+export function proxy(value, prev) {
// if non-proxyable, or is already a proxy, return `value`
if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) {
return value;
@@ -42,6 +44,31 @@ export function proxy(value, parent = null, prev) {
var version = source(0);
var stack = DEV && tracing_mode_flag ? get_stack('CreatedAt') : null;
+ var reaction = active_reaction;
+
+ /**
+ * @template T
+ * @param {() => T} fn
+ */
+ var with_parent = (fn) => {
+ var previous_reaction = active_reaction;
+ set_active_reaction(reaction);
+
+ /** @type {T} */
+ var result;
+
+ if (DEV) {
+ var previous_metadata = parent_metadata;
+ parent_metadata = metadata;
+ result = fn();
+ parent_metadata = previous_metadata;
+ } else {
+ result = fn();
+ }
+
+ set_active_reaction(previous_reaction);
+ return result;
+ };
if (is_proxied_array) {
// We need to create the length source eagerly to ensure that
@@ -54,7 +81,7 @@ export function proxy(value, parent = null, prev) {
if (DEV) {
metadata = {
- parent,
+ parent: parent_metadata,
owners: null
};
@@ -66,7 +93,7 @@ export function proxy(value, parent = null, prev) {
metadata.owners = prev_owners ? new Set(prev_owners) : null;
} else {
metadata.owners =
- parent === null
+ parent_metadata === null
? component_context !== null
? new Set([component_context.function])
: null
@@ -92,10 +119,13 @@ export function proxy(value, parent = null, prev) {
var s = sources.get(prop);
if (s === undefined) {
- s = source(descriptor.value, stack);
+ s = with_parent(() => source(descriptor.value, stack));
sources.set(prop, s);
} else {
- set(s, proxy(descriptor.value, metadata));
+ set(
+ s,
+ with_parent(() => proxy(descriptor.value))
+ );
}
return true;
@@ -106,7 +136,10 @@ export function proxy(value, parent = null, prev) {
if (s === undefined) {
if (prop in target) {
- sources.set(prop, source(UNINITIALIZED, stack));
+ sources.set(
+ prop,
+ with_parent(() => source(UNINITIALIZED, stack))
+ );
}
} else {
// When working with arrays, we need to also ensure we update the length when removing
@@ -140,7 +173,7 @@ export function proxy(value, parent = null, prev) {
// create a source, but only if it's an own property and not a prototype property
if (s === undefined && (!exists || get_descriptor(target, prop)?.writable)) {
- s = source(proxy(exists ? target[prop] : UNINITIALIZED, metadata), stack);
+ s = with_parent(() => source(proxy(exists ? target[prop] : UNINITIALIZED), stack));
sources.set(prop, s);
}
@@ -208,7 +241,7 @@ export function proxy(value, parent = null, prev) {
(active_effect !== null && (!has || get_descriptor(target, prop)?.writable))
) {
if (s === undefined) {
- s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED, stack);
+ s = with_parent(() => source(has ? proxy(target[prop]) : UNINITIALIZED, stack));
sources.set(prop, s);
}
@@ -235,7 +268,7 @@ export function proxy(value, parent = null, prev) {
// If the item exists in the original, we need to create a uninitialized source,
// else a later read of the property would result in a source being created with
// the value of the original item at that index.
- other_s = source(UNINITIALIZED, stack);
+ other_s = with_parent(() => source(UNINITIALIZED, stack));
sources.set(i + '', other_s);
}
}
@@ -247,13 +280,19 @@ export function proxy(value, parent = null, prev) {
// object property before writing to that property.
if (s === undefined) {
if (!has || get_descriptor(target, prop)?.writable) {
- s = source(undefined, stack);
- set(s, proxy(value, metadata));
+ s = with_parent(() => source(undefined, stack));
+ set(
+ s,
+ with_parent(() => proxy(value))
+ );
sources.set(prop, s);
}
} else {
has = s.v !== UNINITIALIZED;
- set(s, proxy(value, metadata));
+ set(
+ s,
+ with_parent(() => proxy(value))
+ );
}
if (DEV) {
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 92508945c96c..cac8431b4e60 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -11,8 +11,8 @@ import {
untrack,
increment_write_version,
update_effect,
- derived_sources,
- set_derived_sources,
+ reaction_sources,
+ set_reaction_sources,
check_dirtiness,
untracking,
is_destroying_effect
@@ -27,7 +27,8 @@ import {
UNOWNED,
MAYBE_DIRTY,
BLOCK_EFFECT,
- ROOT_EFFECT
+ ROOT_EFFECT,
+ EFFECT_IS_UPDATING
} from '../constants.js';
import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
@@ -51,6 +52,7 @@ export function set_inspect_effects(v) {
* @param {Error | null} [stack]
* @returns {Source}
*/
+// TODO rename this to `state` throughout the codebase
export function source(v, stack) {
/** @type {Value} */
var signal = {
@@ -62,6 +64,14 @@ export function source(v, stack) {
wv: 0
};
+ if (active_reaction !== null && active_reaction.f & EFFECT_IS_UPDATING) {
+ if (reaction_sources === null) {
+ set_reaction_sources([signal]);
+ } else {
+ reaction_sources.push(signal);
+ }
+ }
+
if (DEV && tracing_mode_flag) {
signal.created = stack ?? get_stack('CreatedAt');
signal.debug = null;
@@ -70,14 +80,6 @@ export function source(v, stack) {
return signal;
}
-/**
- * @template V
- * @param {V} v
- */
-export function state(v) {
- return push_derived_source(source(v));
-}
-
/**
* @template V
* @param {V} initial_value
@@ -100,33 +102,6 @@ export function mutable_source(initial_value, immutable = false) {
return s;
}
-/**
- * @template V
- * @param {V} v
- * @param {boolean} [immutable]
- * @returns {Source}
- */
-export function mutable_state(v, immutable = false) {
- return push_derived_source(mutable_source(v, immutable));
-}
-
-/**
- * @template V
- * @param {Source} source
- */
-/*#__NO_SIDE_EFFECTS__*/
-function push_derived_source(source) {
- if (active_reaction !== null && !untracking && (active_reaction.f & DERIVED) !== 0) {
- if (derived_sources === null) {
- set_derived_sources([source]);
- } else {
- derived_sources.push(source);
- }
- }
-
- return source;
-}
-
/**
* @template V
* @param {Value} source
@@ -153,14 +128,12 @@ export function set(source, value, should_proxy = false) {
!untracking &&
is_runes() &&
(active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 &&
- // If the source was created locally within the current derived, then
- // we allow the mutation.
- (derived_sources === null || !derived_sources.includes(source))
+ !reaction_sources?.includes(source)
) {
e.state_unsafe_mutation();
}
- let new_value = should_proxy ? proxy(value, null, source) : value;
+ let new_value = should_proxy ? proxy(value, source) : value;
return internal_set(source, new_value);
}
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 0a65c6e45a13..74b58ee1a935 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -22,7 +22,8 @@ import {
ROOT_EFFECT,
LEGACY_DERIVED_PROP,
DISCONNECTED,
- BOUNDARY_EFFECT
+ BOUNDARY_EFFECT,
+ EFFECT_IS_UPDATING
} from './constants.js';
import { flush_tasks } from './dom/task.js';
import { internal_set, old_values } from './reactivity/sources.js';
@@ -87,17 +88,17 @@ export function set_active_effect(effect) {
}
/**
- * When sources are created within a derived, we record them so that we can safely allow
- * local mutations to these sources without the side-effect error being invoked unnecessarily.
+ * When sources are created within a reaction, reading and writing
+ * them should not cause a re-run
* @type {null | Source[]}
*/
-export let derived_sources = null;
+export let reaction_sources = null;
/**
* @param {Source[] | null} sources
*/
-export function set_derived_sources(sources) {
- derived_sources = sources;
+export function set_reaction_sources(sources) {
+ reaction_sources = sources;
}
/**
@@ -367,6 +368,9 @@ function schedule_possible_effect_self_invalidation(signal, effect, root = true)
for (var i = 0; i < reactions.length; i++) {
var reaction = reactions[i];
+
+ if (reaction_sources?.includes(signal)) continue;
+
if ((reaction.f & DERIVED) !== 0) {
schedule_possible_effect_self_invalidation(/** @type {Derived} */ (reaction), effect, false);
} else if (effect === reaction) {
@@ -391,9 +395,10 @@ export function update_reaction(reaction) {
var previous_untracked_writes = untracked_writes;
var previous_reaction = active_reaction;
var previous_skip_reaction = skip_reaction;
- var prev_derived_sources = derived_sources;
+ var previous_reaction_sources = reaction_sources;
var previous_component_context = component_context;
var previous_untracking = untracking;
+
var flags = reaction.f;
new_deps = /** @type {null | Value[]} */ (null);
@@ -403,11 +408,13 @@ export function update_reaction(reaction) {
(flags & UNOWNED) !== 0 && (untracking || !is_updating_effect || active_reaction === null);
active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null;
- derived_sources = null;
+ reaction_sources = null;
set_component_context(reaction.ctx);
untracking = false;
read_version++;
+ reaction.f |= EFFECT_IS_UPDATING;
+
try {
var result = /** @type {Function} */ (0, reaction.fn)();
var deps = reaction.deps;
@@ -477,9 +484,11 @@ export function update_reaction(reaction) {
untracked_writes = previous_untracked_writes;
active_reaction = previous_reaction;
skip_reaction = previous_skip_reaction;
- derived_sources = prev_derived_sources;
+ reaction_sources = previous_reaction_sources;
set_component_context(previous_component_context);
untracking = previous_untracking;
+
+ reaction.f ^= EFFECT_IS_UPDATING;
}
}
@@ -866,9 +875,6 @@ export function get(signal) {
// Register the dependency on the current reaction signal.
if (active_reaction !== null && !untracking) {
- if (derived_sources !== null && derived_sources.includes(signal)) {
- e.state_unsafe_local_read();
- }
var deps = active_reaction.deps;
if (signal.rv < read_version) {
signal.rv = read_version;
diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts
index ef4cf16d3b6c..72f99c90e55b 100644
--- a/packages/svelte/tests/signals/test.ts
+++ b/packages/svelte/tests/signals/test.ts
@@ -8,7 +8,12 @@ import {
render_effect,
user_effect
} from '../../src/internal/client/reactivity/effects';
-import { state, set, update, update_pre } from '../../src/internal/client/reactivity/sources';
+import {
+ source as state,
+ set,
+ update,
+ update_pre
+} from '../../src/internal/client/reactivity/sources';
import type { Derived, Effect, Value } from '../../src/internal/client/types';
import { proxy } from '../../src/internal/client/proxy';
import { derived } from '../../src/internal/client/reactivity/deriveds';
@@ -487,6 +492,26 @@ describe('signals', () => {
};
});
+ test('schedules rerun when updating deeply nested value', (runes) => {
+ if (!runes) return () => {};
+
+ const value = proxy({ a: { b: { c: 0 } } });
+ user_effect(() => {
+ value.a.b.c += 1;
+ });
+
+ return () => {
+ let errored = false;
+ try {
+ flushSync();
+ } catch (e: any) {
+ assert.include(e.message, 'effect_update_depth_exceeded');
+ errored = true;
+ }
+ assert.equal(errored, true);
+ };
+ });
+
test('schedules rerun when writing to signal before reading it', (runes) => {
if (!runes) return () => {};
@@ -958,14 +983,30 @@ describe('signals', () => {
};
});
- test('deriveds cannot depend on state they own', () => {
+ test('deriveds do not depend on state they own', () => {
return () => {
+ let s;
+
const d = derived(() => {
- const s = state(0);
+ s = state(0);
return $.get(s);
});
- assert.throws(() => $.get(d), 'state_unsafe_local_read');
+ assert.equal($.get(d), 0);
+
+ set(s!, 1);
+ assert.equal($.get(d), 0);
+ };
+ });
+
+ test('effects do not depend on state they own', () => {
+ user_effect(() => {
+ const value = state(0);
+ set(value, $.get(value) + 1);
+ });
+
+ return () => {
+ flushSync();
};
});
From 1a5fb8fd51cdec1a72df9ec3100317bea83698aa Mon Sep 17 00:00:00 2001
From: Robert Gieseke
Date: Fri, 21 Mar 2025 14:28:44 +0100
Subject: [PATCH 45/97] fix: Keep inlined JSDoc comments in property conversion
of svelte-migrate (#15567)
* Add failing JSDoc property svelte-migrate conversion tests
* Add further test case and remove default value in JSDoc output
* Look for inlined JSDoc comments after a hyphen
* Add changeset
---
.changeset/happy-cameras-bow.md | 5 +++++
packages/svelte/src/compiler/migrate/index.js | 7 +++++--
.../samples/jsdoc-with-comments/input.svelte | 9 +++++++++
.../samples/jsdoc-with-comments/output.svelte | 14 +++++++++++++-
4 files changed, 32 insertions(+), 3 deletions(-)
create mode 100644 .changeset/happy-cameras-bow.md
diff --git a/.changeset/happy-cameras-bow.md b/.changeset/happy-cameras-bow.md
new file mode 100644
index 000000000000..47188f4f6d00
--- /dev/null
+++ b/.changeset/happy-cameras-bow.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+Keep inlined trailing JSDoc comments of properties when running svelte-migrate
diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js
index 1bb7a69a20f9..02bb5b144385 100644
--- a/packages/svelte/src/compiler/migrate/index.js
+++ b/packages/svelte/src/compiler/migrate/index.js
@@ -1592,7 +1592,6 @@ function extract_type_and_comment(declarator, state, path) {
const comment_start = /** @type {any} */ (comment_node)?.start;
const comment_end = /** @type {any} */ (comment_node)?.end;
let comment = comment_node && str.original.substring(comment_start, comment_end);
-
if (comment_node) {
str.update(comment_start, comment_end, '');
}
@@ -1673,6 +1672,11 @@ function extract_type_and_comment(declarator, state, path) {
state.has_type_or_fallback = true;
const match = /@type {(.+)}/.exec(comment_node.value);
if (match) {
+ // try to find JSDoc comments after a hyphen `-`
+ const jsdocComment = /@type {.+} (?:\w+|\[.*?\]) - (.+)/.exec(comment_node.value);
+ if (jsdocComment) {
+ cleaned_comment += jsdocComment[1]?.trim();
+ }
return {
type: match[1],
comment: cleaned_comment,
@@ -1693,7 +1697,6 @@ function extract_type_and_comment(declarator, state, path) {
};
}
}
-
return {
type: 'any',
comment: state.uses_ts ? comment : cleaned_comment,
diff --git a/packages/svelte/tests/migrate/samples/jsdoc-with-comments/input.svelte b/packages/svelte/tests/migrate/samples/jsdoc-with-comments/input.svelte
index f2efb1db804c..f138c3a0707d 100644
--- a/packages/svelte/tests/migrate/samples/jsdoc-with-comments/input.svelte
+++ b/packages/svelte/tests/migrate/samples/jsdoc-with-comments/input.svelte
@@ -21,6 +21,9 @@
*/
export let type_no_comment;
+ /** @type {boolean} type_with_comment - One-line declaration with comment */
+ export let type_with_comment;
+
/**
* This is optional
*/
@@ -40,4 +43,10 @@
export let inline_multiline_trailing_comment = 'world'; /*
* this is a same-line trailing multiline comment
**/
+
+ /** @type {number} [default_value=1] */
+ export let default_value = 1;
+
+ /** @type {number} [comment_default_value=1] - This has a comment and an optional value. */
+ export let comment_default_value = 1;
\ No newline at end of file
diff --git a/packages/svelte/tests/migrate/samples/jsdoc-with-comments/output.svelte b/packages/svelte/tests/migrate/samples/jsdoc-with-comments/output.svelte
index 19fbe38b5093..32133ccd4c85 100644
--- a/packages/svelte/tests/migrate/samples/jsdoc-with-comments/output.svelte
+++ b/packages/svelte/tests/migrate/samples/jsdoc-with-comments/output.svelte
@@ -9,12 +9,18 @@
+
+
+
+
+
+
/**
* @typedef {Object} Props
* @property {string} comment - My wonderful comment
@@ -22,11 +28,14 @@
* @property {any} one_line - one line comment
* @property {any} no_comment
* @property {boolean} type_no_comment
+ * @property {boolean} type_with_comment - One-line declaration with comment
* @property {any} [optional] - This is optional
* @property {any} inline_commented - this should stay a comment
* @property {any} inline_commented_merged - This comment should be merged - with this inline comment
* @property {string} [inline_multiline_leading_comment] - this is a same-line leading multiline comment
* @property {string} [inline_multiline_trailing_comment] - this is a same-line trailing multiline comment
+ * @property {number} [default_value]
+ * @property {number} [comment_default_value] - This has a comment and an optional value.
*/
/** @type {Props} */
@@ -36,10 +45,13 @@
one_line,
no_comment,
type_no_comment,
+ type_with_comment,
optional = {stuff: true},
inline_commented,
inline_commented_merged,
inline_multiline_leading_comment = 'world',
- inline_multiline_trailing_comment = 'world'
+ inline_multiline_trailing_comment = 'world',
+ default_value = 1,
+ comment_default_value = 1
} = $props();
\ No newline at end of file
From 1d10a65b7858ca4da8d7ade113ec5b6f9c1afb43 Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Fri, 21 Mar 2025 13:30:17 +0000
Subject: [PATCH 46/97] fix: check if DOM prototypes are extensible (#15569)
---
.changeset/dry-ducks-roll.md | 5 +++
.../src/internal/client/dom/operations.js | 35 +++++++++++--------
packages/svelte/src/internal/shared/utils.js | 1 +
3 files changed, 26 insertions(+), 15 deletions(-)
create mode 100644 .changeset/dry-ducks-roll.md
diff --git a/.changeset/dry-ducks-roll.md b/.changeset/dry-ducks-roll.md
new file mode 100644
index 000000000000..2dea8174dd0d
--- /dev/null
+++ b/.changeset/dry-ducks-roll.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: check if DOM prototypes are extensible
diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js
index 0ad9045b2062..aae44d4b3989 100644
--- a/packages/svelte/src/internal/client/dom/operations.js
+++ b/packages/svelte/src/internal/client/dom/operations.js
@@ -2,7 +2,7 @@
import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
import { DEV } from 'esm-env';
import { init_array_prototype_warnings } from '../dev/equality.js';
-import { get_descriptor } from '../../shared/utils.js';
+import { get_descriptor, is_extensible } from '../../shared/utils.js';
// export these for reference in the compiled code, making global name deduplication unnecessary
/** @type {Window} */
@@ -34,26 +34,31 @@ export function init_operations() {
var element_prototype = Element.prototype;
var node_prototype = Node.prototype;
+ var text_prototype = Text.prototype;
// @ts-ignore
first_child_getter = get_descriptor(node_prototype, 'firstChild').get;
// @ts-ignore
next_sibling_getter = get_descriptor(node_prototype, 'nextSibling').get;
- // the following assignments improve perf of lookups on DOM nodes
- // @ts-expect-error
- element_prototype.__click = undefined;
- // @ts-expect-error
- element_prototype.__className = undefined;
- // @ts-expect-error
- element_prototype.__attributes = null;
- // @ts-expect-error
- element_prototype.__style = undefined;
- // @ts-expect-error
- element_prototype.__e = undefined;
-
- // @ts-expect-error
- Text.prototype.__t = undefined;
+ if (is_extensible(element_prototype)) {
+ // the following assignments improve perf of lookups on DOM nodes
+ // @ts-expect-error
+ element_prototype.__click = undefined;
+ // @ts-expect-error
+ element_prototype.__className = undefined;
+ // @ts-expect-error
+ element_prototype.__attributes = null;
+ // @ts-expect-error
+ element_prototype.__style = undefined;
+ // @ts-expect-error
+ element_prototype.__e = undefined;
+ }
+
+ if (is_extensible(text_prototype)) {
+ // @ts-expect-error
+ text_prototype.__t = undefined;
+ }
if (DEV) {
// @ts-expect-error
diff --git a/packages/svelte/src/internal/shared/utils.js b/packages/svelte/src/internal/shared/utils.js
index f9d52cb065ac..5e7f3152d80b 100644
--- a/packages/svelte/src/internal/shared/utils.js
+++ b/packages/svelte/src/internal/shared/utils.js
@@ -10,6 +10,7 @@ export var get_descriptors = Object.getOwnPropertyDescriptors;
export var object_prototype = Object.prototype;
export var array_prototype = Array.prototype;
export var get_prototype_of = Object.getPrototypeOf;
+export var is_extensible = Object.isExtensible;
/**
* @param {any} thing
From d2e79326c7d3810cd4ec657660d4aeec464cd689 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Mar 2025 09:31:03 -0400
Subject: [PATCH 47/97] fix: don't depend on deriveds created inside the
current reaction (#15564)
* WIP
* WIP
* add test
* update test
* changeset
* oops
* lint
---
.changeset/young-poets-wait.md | 5 +++
packages/svelte/src/internal/client/index.js | 11 +----
packages/svelte/src/internal/client/proxy.js | 2 +-
.../internal/client/reactivity/deriveds.js | 16 ++++++-
.../src/internal/client/reactivity/sources.js | 24 +++++++----
.../svelte/src/internal/client/runtime.js | 43 ++++++++++++-------
.../samples/effect-cleanup/_config.js | 2 +-
.../samples/untrack-own-deriveds/_config.js | 20 +++++++++
.../samples/untrack-own-deriveds/main.svelte | 26 +++++++++++
packages/svelte/tests/signals/test.ts | 7 +--
10 files changed, 114 insertions(+), 42 deletions(-)
create mode 100644 .changeset/young-poets-wait.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/main.svelte
diff --git a/.changeset/young-poets-wait.md b/.changeset/young-poets-wait.md
new file mode 100644
index 000000000000..479f5027efd1
--- /dev/null
+++ b/.changeset/young-poets-wait.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't depend on deriveds created inside the current reaction
diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js
index 723ff57678b0..a5f93e8b171b 100644
--- a/packages/svelte/src/internal/client/index.js
+++ b/packages/svelte/src/internal/client/index.js
@@ -101,7 +101,7 @@ export {
text,
props_id
} from './dom/template.js';
-export { derived, derived_safe_equal } from './reactivity/deriveds.js';
+export { user_derived as derived, derived_safe_equal } from './reactivity/deriveds.js';
export {
effect_tracking,
effect_root,
@@ -113,14 +113,7 @@ export {
user_effect,
user_pre_effect
} from './reactivity/effects.js';
-export {
- mutable_source,
- mutate,
- set,
- source as state,
- update,
- update_pre
-} from './reactivity/sources.js';
+export { mutable_source, mutate, set, state, update, update_pre } from './reactivity/sources.js';
export {
prop,
rest_props,
diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js
index 9c3c0cf29f29..ffe63f4b77a8 100644
--- a/packages/svelte/src/internal/client/proxy.js
+++ b/packages/svelte/src/internal/client/proxy.js
@@ -10,7 +10,7 @@ import {
object_prototype
} from '../shared/utils.js';
import { check_ownership, widen_ownership } from './dev/ownership.js';
-import { source, set } from './reactivity/sources.js';
+import { state as source, set } from './reactivity/sources.js';
import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js';
import { UNINITIALIZED } from '../../constants.js';
import * as e from './errors.js';
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index 795417cc0fdb..cd7bbba02f91 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -8,7 +8,8 @@ import {
skip_reaction,
update_reaction,
increment_write_version,
- set_active_effect
+ set_active_effect,
+ push_reaction_value
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
@@ -61,6 +62,19 @@ export function derived(fn) {
return signal;
}
+/**
+ * @template V
+ * @param {() => V} fn
+ * @returns {Derived}
+ */
+export function user_derived(fn) {
+ const d = derived(fn);
+
+ push_reaction_value(d);
+
+ return d;
+}
+
/**
* @template V
* @param {() => V} fn
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index cac8431b4e60..e4834902fe3f 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -15,7 +15,8 @@ import {
set_reaction_sources,
check_dirtiness,
untracking,
- is_destroying_effect
+ is_destroying_effect,
+ push_reaction_value
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import {
@@ -64,14 +65,6 @@ export function source(v, stack) {
wv: 0
};
- if (active_reaction !== null && active_reaction.f & EFFECT_IS_UPDATING) {
- if (reaction_sources === null) {
- set_reaction_sources([signal]);
- } else {
- reaction_sources.push(signal);
- }
- }
-
if (DEV && tracing_mode_flag) {
signal.created = stack ?? get_stack('CreatedAt');
signal.debug = null;
@@ -80,6 +73,19 @@ export function source(v, stack) {
return signal;
}
+/**
+ * @template V
+ * @param {V} v
+ * @param {Error | null} [stack]
+ */
+export function state(v, stack) {
+ const s = source(v, stack);
+
+ push_reaction_value(s);
+
+ return s;
+}
+
/**
* @template V
* @param {V} initial_value
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 74b58ee1a935..a5d26412a4e6 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -101,6 +101,17 @@ export function set_reaction_sources(sources) {
reaction_sources = sources;
}
+/** @param {Value} value */
+export function push_reaction_value(value) {
+ if (active_reaction !== null && active_reaction.f & EFFECT_IS_UPDATING) {
+ if (reaction_sources === null) {
+ set_reaction_sources([value]);
+ } else {
+ reaction_sources.push(value);
+ }
+ }
+}
+
/**
* The dependencies of the reaction that is currently being executed. In many cases,
* the dependencies are unchanged between runs, and so this will be `null` unless
@@ -875,21 +886,23 @@ export function get(signal) {
// Register the dependency on the current reaction signal.
if (active_reaction !== null && !untracking) {
- var deps = active_reaction.deps;
- if (signal.rv < read_version) {
- signal.rv = read_version;
- // If the signal is accessing the same dependencies in the same
- // order as it did last time, increment `skipped_deps`
- // rather than updating `new_deps`, which creates GC cost
- if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
- skipped_deps++;
- } else if (new_deps === null) {
- new_deps = [signal];
- } else if (!skip_reaction || !new_deps.includes(signal)) {
- // Normally we can push duplicated dependencies to `new_deps`, but if we're inside
- // an unowned derived because skip_reaction is true, then we need to ensure that
- // we don't have duplicates
- new_deps.push(signal);
+ if (!reaction_sources?.includes(signal)) {
+ var deps = active_reaction.deps;
+ if (signal.rv < read_version) {
+ signal.rv = read_version;
+ // If the signal is accessing the same dependencies in the same
+ // order as it did last time, increment `skipped_deps`
+ // rather than updating `new_deps`, which creates GC cost
+ if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
+ skipped_deps++;
+ } else if (new_deps === null) {
+ new_deps = [signal];
+ } else if (!skip_reaction || !new_deps.includes(signal)) {
+ // Normally we can push duplicated dependencies to `new_deps`, but if we're inside
+ // an unowned derived because skip_reaction is true, then we need to ensure that
+ // we don't have duplicates
+ new_deps.push(signal);
+ }
}
}
} else if (
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js
index 6a3d9eef7702..e55733c14810 100644
--- a/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js
@@ -10,6 +10,6 @@ export default test({
flushSync(() => {
b1.click();
});
- assert.deepEqual(logs, ['init 0', 'cleanup 2', null, 'init 2', 'cleanup 4', null, 'init 4']);
+ assert.deepEqual(logs, ['init 0']);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/_config.js b/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/_config.js
new file mode 100644
index 000000000000..18062b86fb43
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/_config.js
@@ -0,0 +1,20 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ test({ assert, target, logs }) {
+ const button = target.querySelector('button');
+
+ flushSync(() => button?.click());
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ increment
+ 1/2
+ class Foo {
+ value = $state(0);
+ double = $derived(this.value * 2);
+
+ constructor() {
+ console.log(this.value, this.double);
+ }
+
+ increment() {
+ this.value++;
+ }
+ }
+
+ let foo = $state();
+
+ $effect(() => {
+ foo = new Foo();
+ });
+
+
+ foo.increment()}>increment
+
+{#if foo}
+ {foo.value}/{foo.double}
+{/if}
diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts
index 72f99c90e55b..3977caae36ad 100644
--- a/packages/svelte/tests/signals/test.ts
+++ b/packages/svelte/tests/signals/test.ts
@@ -8,12 +8,7 @@ import {
render_effect,
user_effect
} from '../../src/internal/client/reactivity/effects';
-import {
- source as state,
- set,
- update,
- update_pre
-} from '../../src/internal/client/reactivity/sources';
+import { state, set, update, update_pre } from '../../src/internal/client/reactivity/sources';
import type { Derived, Effect, Value } from '../../src/internal/client/types';
import { proxy } from '../../src/internal/client/proxy';
import { derived } from '../../src/internal/client/reactivity/deriveds';
From e25c2812961f9bb74ab50f1b034d8e5a5d8ae412 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 21 Mar 2025 09:40:37 -0400
Subject: [PATCH 48/97] Version Packages (#15551)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/dirty-pianos-sparkle.md | 5 -----
.changeset/dry-ducks-roll.md | 5 -----
.changeset/happy-cameras-bow.md | 5 -----
.changeset/nine-laws-rush.md | 5 -----
.changeset/young-poets-wait.md | 5 -----
packages/svelte/CHANGELOG.md | 16 ++++++++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
8 files changed, 18 insertions(+), 27 deletions(-)
delete mode 100644 .changeset/dirty-pianos-sparkle.md
delete mode 100644 .changeset/dry-ducks-roll.md
delete mode 100644 .changeset/happy-cameras-bow.md
delete mode 100644 .changeset/nine-laws-rush.md
delete mode 100644 .changeset/young-poets-wait.md
diff --git a/.changeset/dirty-pianos-sparkle.md b/.changeset/dirty-pianos-sparkle.md
deleted file mode 100644
index b3e4dd1d8c1b..000000000000
--- a/.changeset/dirty-pianos-sparkle.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': minor
----
-
-feat: allow state created in deriveds/effects to be written/read locally without self-invalidation
diff --git a/.changeset/dry-ducks-roll.md b/.changeset/dry-ducks-roll.md
deleted file mode 100644
index 2dea8174dd0d..000000000000
--- a/.changeset/dry-ducks-roll.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: check if DOM prototypes are extensible
diff --git a/.changeset/happy-cameras-bow.md b/.changeset/happy-cameras-bow.md
deleted file mode 100644
index 47188f4f6d00..000000000000
--- a/.changeset/happy-cameras-bow.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-Keep inlined trailing JSDoc comments of properties when running svelte-migrate
diff --git a/.changeset/nine-laws-rush.md b/.changeset/nine-laws-rush.md
deleted file mode 100644
index e0a0fc15a0a6..000000000000
--- a/.changeset/nine-laws-rush.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: simplify set calls for proxyable values
diff --git a/.changeset/young-poets-wait.md b/.changeset/young-poets-wait.md
deleted file mode 100644
index 479f5027efd1..000000000000
--- a/.changeset/young-poets-wait.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: don't depend on deriveds created inside the current reaction
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 6461df1d25df..8cb7efd4ef14 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,21 @@
# svelte
+## 5.24.0
+
+### Minor Changes
+
+- feat: allow state created in deriveds/effects to be written/read locally without self-invalidation ([#15553](https://github.com/sveltejs/svelte/pull/15553))
+
+### Patch Changes
+
+- fix: check if DOM prototypes are extensible ([#15569](https://github.com/sveltejs/svelte/pull/15569))
+
+- Keep inlined trailing JSDoc comments of properties when running svelte-migrate ([#15567](https://github.com/sveltejs/svelte/pull/15567))
+
+- fix: simplify set calls for proxyable values ([#15548](https://github.com/sveltejs/svelte/pull/15548))
+
+- fix: don't depend on deriveds created inside the current reaction ([#15564](https://github.com/sveltejs/svelte/pull/15564))
+
## 5.23.2
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index d005eca0b9c8..0aa6b2984123 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.23.2",
+ "version": "5.24.0",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 191b52ecef42..7cd43e74cb7d 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.23.2';
+export const VERSION = '5.24.0';
export const PUBLIC_VERSION = '5';
From 6b23a7c4777a123dc1ea4db6cb87e03268fbb45b Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Mar 2025 09:52:30 -0400
Subject: [PATCH 49/97] chore: camelCase -> snake_case (#15573)
---
packages/svelte/src/compiler/migrate/index.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js
index 02bb5b144385..7f26d0d0103a 100644
--- a/packages/svelte/src/compiler/migrate/index.js
+++ b/packages/svelte/src/compiler/migrate/index.js
@@ -1673,9 +1673,9 @@ function extract_type_and_comment(declarator, state, path) {
const match = /@type {(.+)}/.exec(comment_node.value);
if (match) {
// try to find JSDoc comments after a hyphen `-`
- const jsdocComment = /@type {.+} (?:\w+|\[.*?\]) - (.+)/.exec(comment_node.value);
- if (jsdocComment) {
- cleaned_comment += jsdocComment[1]?.trim();
+ const jsdoc_comment = /@type {.+} (?:\w+|\[.*?\]) - (.+)/.exec(comment_node.value);
+ if (jsdoc_comment) {
+ cleaned_comment += jsdoc_comment[1]?.trim();
}
return {
type: match[1],
From 83d0c5894dc26c92274f162c9f9495038cabe37d Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Mar 2025 09:52:51 -0400
Subject: [PATCH 50/97] docs: add note on effect-local state (#15572)
---
documentation/docs/02-runes/04-$effect.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md
index 6a2b565aeaa2..75f59102f96b 100644
--- a/documentation/docs/02-runes/04-$effect.md
+++ b/documentation/docs/02-runes/04-$effect.md
@@ -74,6 +74,8 @@ Teardown functions also run when the effect is destroyed, which happens when its
`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the `$effect` schedules a re-run.
+If `$state` and `$derived` are used directly inside the `$effect` (for example, during creation of a [reactive class](https://svelte.dev/docs/svelte/$state#Classes)), those values will _not_ be treated as dependencies.
+
Values that are read _asynchronously_ — after an `await` or inside a `setTimeout`, for example — will not be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/playground/untitled#H4sIAAAAAAAAE31T246bMBD9lZF3pWSlBEirfaEQqdo_2PatVIpjBrDkGGQPJGnEv1e2IZfVal-wfHzmzJyZ4cIqqdCy9M-F0blDlnqArZjmB3f72XWRHVCRw_bc4me4aDWhJstSlllhZEfbQhekkMDKfwg5PFvihMvX5OXH_CJa1Zrb0-Kpqr5jkiwC48rieuDWQbqgZ6wqFLRcvkC-hYvnkWi1dWqa8ESQTxFRjfQWsOXiWzmr0sSLhEJu3p1YsoJkNUcdZUnN9dagrBu6FVRQHAM10sJRKgUG16bXcGxQ44AGdt7SDkTDdY02iqLHnJVU6hedlWuIp94JW6Tf8oBt_8GdTxlF0b4n0C35ZLBzXb3mmYn3ae6cOW74zj0YVzDNYXRHFt9mprNgHfZSl6mzml8CMoLvTV6wTZIUDEJv5us2iwMtiJRyAKG4tXnhl8O0yhbML0Wm-B7VNlSSSd31BG7z8oIZZ6dgIffAVY_5xdU9Qrz1Bnx8fCfwtZ7v8Qc9j3nB8PqgmMWlHIID6-bkVaPZwDySfWtKNGtquxQ23Qlsq2QJT0KIqb8dL0up6xQ2eIBkAg_c1FI_YqW0neLnFCqFpwmreedJYT7XX8FVOBfwWRhXstZrSXiwKQjUhOZeMIleb5JZfHWn2Yq5pWEpmR7Hv-N_wEqT8hEEAAA=)):
```ts
From ade66c6feade92cfd932dcb4be2812305e518d2b Mon Sep 17 00:00:00 2001
From: Paolo Ricciuti
Date: Fri, 21 Mar 2025 15:21:40 +0100
Subject: [PATCH 51/97] fix: use `get` in constructor for deriveds (#15300)
Co-authored-by: Rich Harris
---
.changeset/new-cherries-leave.md | 5 +++++
.../client/visitors/MemberExpression.js | 4 +++-
.../samples/deriveds-in-constructor/_config.js | 5 +++++
.../deriveds-in-constructor/main.svelte | 18 ++++++++++++++++++
4 files changed, 31 insertions(+), 1 deletion(-)
create mode 100644 .changeset/new-cherries-leave.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/main.svelte
diff --git a/.changeset/new-cherries-leave.md b/.changeset/new-cherries-leave.md
new file mode 100644
index 000000000000..738a78b4a34a
--- /dev/null
+++ b/.changeset/new-cherries-leave.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: use `get` in constructor for deriveds
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js
index 501ecda5557d..3f2aada1f575 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js
@@ -11,7 +11,9 @@ export function MemberExpression(node, context) {
if (node.property.type === 'PrivateIdentifier') {
const field = context.state.private_state.get(node.property.name);
if (field) {
- return context.state.in_constructor ? b.member(node, 'v') : b.call('$.get', node);
+ return context.state.in_constructor && (field.kind === 'raw_state' || field.kind === 'state')
+ ? b.member(node, 'v')
+ : b.call('$.get', node);
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/_config.js b/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/_config.js
new file mode 100644
index 000000000000..b364a989f480
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+ html: `state,derived state,derived.by derived state
`
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/main.svelte b/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/main.svelte
new file mode 100644
index 000000000000..bc8efba7e7c2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/main.svelte
@@ -0,0 +1,18 @@
+
+
+{foo.initial}
\ No newline at end of file
From 1f37c02f918d6fa4d8a14de5d6868228e61dd05a Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Fri, 21 Mar 2025 14:25:46 +0000
Subject: [PATCH 52/97] fix: ensure toStore root effect is connected to correct
parent effect (#15574)
* fix: ensure toStore root effect is connected to correct parent effect
* prettier
---------
Co-authored-by: Rich Harris
---
.changeset/twelve-bananas-destroy.md | 5 +++
packages/svelte/src/store/index-client.js | 35 +++++++++++++++----
.../samples/toStore-subscribe2/_config.js | 16 +++++++++
.../samples/toStore-subscribe2/main.svelte | 11 ++++++
4 files changed, 60 insertions(+), 7 deletions(-)
create mode 100644 .changeset/twelve-bananas-destroy.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/main.svelte
diff --git a/.changeset/twelve-bananas-destroy.md b/.changeset/twelve-bananas-destroy.md
new file mode 100644
index 000000000000..873ee21877b7
--- /dev/null
+++ b/.changeset/twelve-bananas-destroy.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure toStore root effect is connected to correct parent effect
diff --git a/packages/svelte/src/store/index-client.js b/packages/svelte/src/store/index-client.js
index ae6806ec763f..2f0a1a831a0c 100644
--- a/packages/svelte/src/store/index-client.js
+++ b/packages/svelte/src/store/index-client.js
@@ -6,6 +6,12 @@ import {
} from '../internal/client/reactivity/effects.js';
import { get, writable } from './shared/index.js';
import { createSubscriber } from '../reactivity/create-subscriber.js';
+import {
+ active_effect,
+ active_reaction,
+ set_active_effect,
+ set_active_reaction
+} from '../internal/client/runtime.js';
export { derived, get, readable, readonly, writable } from './shared/index.js';
@@ -39,19 +45,34 @@ export { derived, get, readable, readonly, writable } from './shared/index.js';
* @returns {Writable | Readable}
*/
export function toStore(get, set) {
- let init_value = get();
+ var effect = active_effect;
+ var reaction = active_reaction;
+ var init_value = get();
+
const store = writable(init_value, (set) => {
// If the value has changed before we call subscribe, then
// we need to treat the value as already having run
- let ran = init_value !== get();
+ var ran = init_value !== get();
// TODO do we need a different implementation on the server?
- const teardown = effect_root(() => {
- render_effect(() => {
- const value = get();
- if (ran) set(value);
+ var teardown;
+ // Apply the reaction and effect at the time of toStore being called
+ var previous_reaction = active_reaction;
+ var previous_effect = active_effect;
+ set_active_reaction(reaction);
+ set_active_effect(effect);
+
+ try {
+ teardown = effect_root(() => {
+ render_effect(() => {
+ const value = get();
+ if (ran) set(value);
+ });
});
- });
+ } finally {
+ set_active_reaction(previous_reaction);
+ set_active_effect(previous_effect);
+ }
ran = true;
diff --git a/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/_config.js b/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/_config.js
new file mode 100644
index 000000000000..bc1793e7a461
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/_config.js
@@ -0,0 +1,16 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ let btn = target.querySelector('button');
+
+ btn?.click();
+ flushSync();
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `Count 1!
Count from store 1!
Add 1 `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/main.svelte b/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/main.svelte
new file mode 100644
index 000000000000..82d20105b8eb
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/main.svelte
@@ -0,0 +1,11 @@
+
+
+Count {counter}!
+Count from store {$count}!
+
+ counter+=1}>Add 1
From 842a7c6995f94f46b1839fcac91042fd541e52ca Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Mar 2025 10:26:15 -0400
Subject: [PATCH 53/97] docs: update state_unsafe_mutation message (#15539)
* docs: update state_unsafe_mutation message
* regenerate
* fix example
---
.../98-reference/.generated/client-errors.md | 35 +++++++++++--------
.../svelte/messages/client-errors/errors.md | 35 +++++++++++--------
2 files changed, 40 insertions(+), 30 deletions(-)
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index 62d9c3302a3c..901c49822c00 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -128,26 +128,31 @@ Cannot set prototype of `$state` object
Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
```
-This error is thrown in a situation like this:
+This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go:
```svelte
- count++}>{count} / {multiple}
+ count++}>{count}
+
+{count} is even: {even}
+{count} is odd: {odd}
```
-Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable.
+This is forbidden because it introduces instability: if `{count} is even: {even}
` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
+
+```js
+let even = $derived(count % 2 === 0);
+let odd = $derived(!even);
+```
-To fix this:
-- See if it's possible to refactor your `$derived` such that the update becomes unnecessary
-- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update
-- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates
+If side-effects are unavoidable, use [`$effect`]($effect) instead.
diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md
index bc8ec3625605..572930843e78 100644
--- a/packages/svelte/messages/client-errors/errors.md
+++ b/packages/svelte/messages/client-errors/errors.md
@@ -84,26 +84,31 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
> Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
-This error is thrown in a situation like this:
+This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go:
```svelte
- count++}>{count} / {multiple}
+ count++}>{count}
+
+{count} is even: {even}
+{count} is odd: {odd}
```
-Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable.
+This is forbidden because it introduces instability: if `{count} is even: {even}
` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
+
+```js
+let even = $derived(count % 2 === 0);
+let odd = $derived(!even);
+```
-To fix this:
-- See if it's possible to refactor your `$derived` such that the update becomes unnecessary
-- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update
-- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates
+If side-effects are unavoidable, use [`$effect`]($effect) instead.
From 2d3b65dfbd589416d94661bd34ed7a99896bcde2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 21 Mar 2025 10:28:23 -0400
Subject: [PATCH 54/97] Version Packages (#15575)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/new-cherries-leave.md | 5 -----
.changeset/twelve-bananas-destroy.md | 5 -----
packages/svelte/CHANGELOG.md | 8 ++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
5 files changed, 10 insertions(+), 12 deletions(-)
delete mode 100644 .changeset/new-cherries-leave.md
delete mode 100644 .changeset/twelve-bananas-destroy.md
diff --git a/.changeset/new-cherries-leave.md b/.changeset/new-cherries-leave.md
deleted file mode 100644
index 738a78b4a34a..000000000000
--- a/.changeset/new-cherries-leave.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: use `get` in constructor for deriveds
diff --git a/.changeset/twelve-bananas-destroy.md b/.changeset/twelve-bananas-destroy.md
deleted file mode 100644
index 873ee21877b7..000000000000
--- a/.changeset/twelve-bananas-destroy.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: ensure toStore root effect is connected to correct parent effect
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 8cb7efd4ef14..04ddfcadbd30 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,13 @@
# svelte
+## 5.24.1
+
+### Patch Changes
+
+- fix: use `get` in constructor for deriveds ([#15300](https://github.com/sveltejs/svelte/pull/15300))
+
+- fix: ensure toStore root effect is connected to correct parent effect ([#15574](https://github.com/sveltejs/svelte/pull/15574))
+
## 5.24.0
### Minor Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 0aa6b2984123..f321571e7abb 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.24.0",
+ "version": "5.24.1",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 7cd43e74cb7d..565c19071396 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.24.0';
+export const VERSION = '5.24.1';
export const PUBLIC_VERSION = '5';
From 5a8fa69dbf46e99beed812157ed78609f8054331 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Mar 2025 12:54:53 -0400
Subject: [PATCH 55/97] feat: make deriveds writable (#15570)
* feat: make deriveds writable
* add optimistic UI example
* add note to when-not-to-use-effect
* add section on deep reactivity
* root-relative URL
* use hash URL
* mention const
* make handler async, move into script block
---
.changeset/clever-terms-tell.md | 5 ++
documentation/docs/02-runes/03-$derived.md | 42 +++++++++++++++++
documentation/docs/02-runes/04-$effect.md | 2 +
.../phases/2-analyze/visitors/shared/utils.js | 31 +------------
.../runes-no-derived-assignment/_config.js | 8 ----
.../runes-no-derived-assignment/main.svelte | 5 --
.../runes-no-derived-binding/_config.js | 8 ----
.../runes-no-derived-binding/main.svelte | 6 ---
.../_config.js | 8 ----
.../main.svelte | 10 ----
.../_config.js | 8 ----
.../main.svelte | 10 ----
.../runes-no-derived-update/_config.js | 8 ----
.../runes-no-derived-update/main.svelte | 5 --
.../samples/writable-derived/_config.js | 46 +++++++++++++++++++
.../samples/writable-derived/main.svelte | 9 ++++
.../reassign-derived-literal/errors.json | 14 ------
.../reassign-derived-literal/input.svelte | 9 ----
.../errors.json | 14 ------
.../input.svelte | 9 ----
.../reassign-derived-public-field/errors.json | 14 ------
.../input.svelte | 9 ----
22 files changed, 105 insertions(+), 175 deletions(-)
create mode 100644 .changeset/clever-terms-tell.md
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/_config.js
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/_config.js
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/main.svelte
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-assignment/_config.js
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-assignment/main.svelte
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-update/_config.js
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-update/main.svelte
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/_config.js
delete mode 100644 packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/writable-derived/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/writable-derived/main.svelte
delete mode 100644 packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json
delete mode 100644 packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte
delete mode 100644 packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json
delete mode 100644 packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte
delete mode 100644 packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json
delete mode 100644 packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte
diff --git a/.changeset/clever-terms-tell.md b/.changeset/clever-terms-tell.md
new file mode 100644
index 000000000000..606868bce39c
--- /dev/null
+++ b/.changeset/clever-terms-tell.md
@@ -0,0 +1,5 @@
+---
+'svelte': minor
+---
+
+feat: make deriveds writable
diff --git a/documentation/docs/02-runes/03-$derived.md b/documentation/docs/02-runes/03-$derived.md
index 24ab643b68f6..2464aa929550 100644
--- a/documentation/docs/02-runes/03-$derived.md
+++ b/documentation/docs/02-runes/03-$derived.md
@@ -52,6 +52,48 @@ Anything read synchronously inside the `$derived` expression (or `$derived.by` f
To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
+## Overriding derived values
+
+Derived expressions are recalculated when their dependencies change, but you can temporarily override their values by reassigning them (unless they are declared with `const`). This can be useful for things like _optimistic UI_, where a value is derived from the 'source of truth' (such as data from your server) but you'd like to show immediate feedback to the user:
+
+```svelte
+
+
+🧡 {likes}
+```
+
+> [!NOTE] Prior to Svelte 5.25, deriveds were read-only.
+
+## Deriveds and reactivity
+
+Unlike `$state`, which converts objects and arrays to [deeply reactive proxies]($state#Deep-state), `$derived` values are left as-is. For example, [in a case like this](/playground/untitled#H4sIAAAAAAAAE4VU22rjMBD9lUHd3aaQi9PdstS1A3t5XvpQ2Ic4D7I1iUUV2UjjNMX431eS7TRdSosxgjMzZ45mjt0yzffIYibvy0ojFJWqDKCQVBk2ZVup0LJ43TJ6rn2aBxw-FP2o67k9oCKP5dziW3hRaUJNjoYltjCyplWmM1JIIAn3FlL4ZIkTTtYez6jtj4w8WwyXv9GiIXiQxLVs9pfTMR7EuoSLIuLFbX7Z4930bZo_nBrD1bs834tlfvsBz9_SyX6PZXu9XaL4gOWn4sXjeyzftv4ZWfyxubpzxzg6LfD4MrooxELEosKCUPigQCMPKCZh0OtQE1iSxcsmdHuBvCiHZXALLXiN08EL3RRkaJ_kDVGle0HcSD5TPEeVtj67O4Nrg9aiSNtBY5oODJkrL5QsHtN2cgXp6nSJMWzpWWGasdlsGEMbzi5jPr5KFr0Ep7pdeM2-TCelCddIhDxAobi1jqF3cMaC1RKp64bAW9iFAmXGIHfd4wNXDabtOLN53w8W53VvJoZLh7xk4Rr3CoL-UNoLhWHrT1JQGcM17u96oES5K-kc2XOzkzqGCKL5De79OUTyyrg1zgwXsrEx3ESfx4Bz0M5UjVMHB24mw9SuXtXFoN13fYKOM1tyUT3FbvbWmSWCZX2Er-41u5xPoml45svRahl9Wb9aasbINJixDZwcPTbyTLZSUsAvrg_cPuCR7s782_WU8343Y72Qtlb8OYatwuOQvuN13M_hJKNfxann1v1U_B1KZ_D_mzhzhz24fw85CSz2irtN9w9HshBK7AQAAA==)...
+
+```svelte
+let items = $state([...]);
+
+let index = $state(0);
+let selected = $derived(items[index]);
+```
+
+...you can change (or `bind:` to) properties of `selected` and it will affect the underlying `items` array. If `items` was _not_ deeply reactive, mutating `selected` would have no effect.
+
## Update propagation
Svelte uses something called _push-pull reactivity_ — when state is updated, everything that depends on the state (whether directly or indirectly) is immediately notified of the change (the 'push'), but derived values are not re-evaluated until they are actually read (the 'pull').
diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md
index 75f59102f96b..ae1a2146c9d4 100644
--- a/documentation/docs/02-runes/04-$effect.md
+++ b/documentation/docs/02-runes/04-$effect.md
@@ -254,6 +254,8 @@ In general, `$effect` is best considered something of an escape hatch — useful
> [!NOTE] For things that are more complicated than a simple expression like `count * 2`, you can also use `$derived.by`.
+If you're using an effect because you want to be able to reassign the derived value (to build an optimistic UI, for example) note that [deriveds can be directly overridden]($derived#Overriding-derived-values) as of Svelte 5.25.
+
You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAACpVRy26DMBD8FcvKgUhtoIdeHBwp31F6MGSJkBbHwksEQvx77aWQqooq9bgzOzP7mGTdIHipPiZJowOpGJAv0po2VmfnDv4OSBErjYdneHWzBJaCjcx91TWOToUtCIEE3cig0OIty44r5l1oDtjOkyFIsv3GINQ_CNYyGegd1DVUlCR7oU9iilDUcP8S8roYs9n8p2wdYNVFm4csTx872BxNCcjr5I11fdgonEkXsjP2CoUUZWMv6m6wBz2x7yxaM-iJvWeRsvSbSVeUy5i0uf8vKA78NIeJLSZWv1I8jQjLdyK4XuTSeIdmVKJGGI4LdjVOiezwDu1yG74My8PLCQaSiroe5s_5C2PHrkVGAgAA)):
```svelte
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js
index 04f4347a40bb..d6c74eddb6f0 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js
@@ -21,10 +21,6 @@ export function validate_assignment(node, argument, state) {
const binding = state.scope.get(argument.name);
if (state.analysis.runes) {
- if (binding?.kind === 'derived') {
- e.constant_assignment(node, 'derived state');
- }
-
if (binding?.node === state.analysis.props_id) {
e.constant_assignment(node, '$props.id()');
}
@@ -38,25 +34,6 @@ export function validate_assignment(node, argument, state) {
e.snippet_parameter_assignment(node);
}
}
- if (
- argument.type === 'MemberExpression' &&
- argument.object.type === 'ThisExpression' &&
- (((argument.property.type === 'PrivateIdentifier' || argument.property.type === 'Identifier') &&
- state.derived_state.some(
- (derived) =>
- derived.name === /** @type {PrivateIdentifier | Identifier} */ (argument.property).name &&
- derived.private === (argument.property.type === 'PrivateIdentifier')
- )) ||
- (argument.property.type === 'Literal' &&
- argument.property.value &&
- typeof argument.property.value === 'string' &&
- state.derived_state.some(
- (derived) =>
- derived.name === /** @type {Literal} */ (argument.property).value && !derived.private
- )))
- ) {
- e.constant_assignment(node, 'derived state');
- }
}
/**
@@ -81,7 +58,6 @@ export function validate_no_const_assignment(node, argument, scope, is_binding)
} else if (argument.type === 'Identifier') {
const binding = scope.get(argument.name);
if (
- binding?.kind === 'derived' ||
binding?.declaration_kind === 'import' ||
(binding?.declaration_kind === 'const' && binding.kind !== 'each')
) {
@@ -96,12 +72,7 @@ export function validate_no_const_assignment(node, argument, scope, is_binding)
// );
// TODO have a more specific error message for assignments to things like `{:then foo}`
- const thing =
- binding.declaration_kind === 'import'
- ? 'import'
- : binding.kind === 'derived'
- ? 'derived state'
- : 'constant';
+ const thing = binding.declaration_kind === 'import' ? 'import' : 'constant';
if (is_binding) {
e.constant_binding(node, thing);
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/_config.js
deleted file mode 100644
index 94985a99397a..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/_config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { test } from '../../test';
-
-export default test({
- error: {
- code: 'constant_assignment',
- message: 'Cannot assign to derived state'
- }
-});
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte
deleted file mode 100644
index 3bf836f6c586..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/_config.js
deleted file mode 100644
index 87b88d79cc26..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/_config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { test } from '../../test';
-
-export default test({
- error: {
- code: 'constant_binding',
- message: 'Cannot bind to derived state'
- }
-});
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/main.svelte
deleted file mode 100644
index 6c198dc068fe..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/main.svelte
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-assignment/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-assignment/_config.js
deleted file mode 100644
index 94985a99397a..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-assignment/_config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { test } from '../../test';
-
-export default test({
- error: {
- code: 'constant_assignment',
- message: 'Cannot assign to derived state'
- }
-});
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-assignment/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-assignment/main.svelte
deleted file mode 100644
index d44806757e6c..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-assignment/main.svelte
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-update/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-update/_config.js
deleted file mode 100644
index 94985a99397a..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-update/_config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { test } from '../../test';
-
-export default test({
- error: {
- code: 'constant_assignment',
- message: 'Cannot assign to derived state'
- }
-});
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-update/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-update/main.svelte
deleted file mode 100644
index e4ee2e86356d..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-state-field-update/main.svelte
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/_config.js
deleted file mode 100644
index 94985a99397a..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/_config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { test } from '../../test';
-
-export default test({
- error: {
- code: 'constant_assignment',
- message: 'Cannot assign to derived state'
- }
-});
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte
deleted file mode 100644
index d266c95bb872..000000000000
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/writable-derived/_config.js
new file mode 100644
index 000000000000..b48ccbdfd004
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/writable-derived/_config.js
@@ -0,0 +1,46 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ html: `
+ 0 * 2 = 0
+ `,
+
+ ssrHtml: `
+ 0 * 2 = 0
+ `,
+
+ test({ assert, target, window }) {
+ const [input1, input2] = target.querySelectorAll('input');
+
+ flushSync(() => {
+ input1.value = '10';
+ input1.dispatchEvent(new window.Event('input', { bubbles: true }));
+ });
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `10 * 2 = 20
`
+ );
+
+ flushSync(() => {
+ input2.value = '99';
+ input2.dispatchEvent(new window.Event('input', { bubbles: true }));
+ });
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `10 * 2 = 99
`
+ );
+
+ flushSync(() => {
+ input1.value = '20';
+ input1.dispatchEvent(new window.Event('input', { bubbles: true }));
+ });
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `20 * 2 = 40
`
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/writable-derived/main.svelte
new file mode 100644
index 000000000000..ab1dde0b9bba
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/writable-derived/main.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+{count} * 2 = {double}
diff --git a/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json
deleted file mode 100644
index 8681d84ab227..000000000000
--- a/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json
+++ /dev/null
@@ -1,14 +0,0 @@
-[
- {
- "code": "constant_assignment",
- "message": "Cannot assign to derived state",
- "start": {
- "column": 3,
- "line": 6
- },
- "end": {
- "column": 29,
- "line": 6
- }
- }
-]
diff --git a/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte
deleted file mode 100644
index 8f109c9e1fe8..000000000000
--- a/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte
+++ /dev/null
@@ -1,9 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json
deleted file mode 100644
index c211aa460895..000000000000
--- a/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json
+++ /dev/null
@@ -1,14 +0,0 @@
-[
- {
- "code": "constant_assignment",
- "message": "Cannot assign to derived state",
- "start": {
- "column": 3,
- "line": 6
- },
- "end": {
- "column": 27,
- "line": 6
- }
- }
-]
diff --git a/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte
deleted file mode 100644
index 62e2317e033f..000000000000
--- a/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte
+++ /dev/null
@@ -1,9 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json
deleted file mode 100644
index 98837589ac80..000000000000
--- a/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json
+++ /dev/null
@@ -1,14 +0,0 @@
-[
- {
- "code": "constant_assignment",
- "message": "Cannot assign to derived state",
- "start": {
- "column": 3,
- "line": 6
- },
- "end": {
- "column": 26,
- "line": 6
- }
- }
-]
diff --git a/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte
deleted file mode 100644
index e2c4693e86b7..000000000000
--- a/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte
+++ /dev/null
@@ -1,9 +0,0 @@
-
\ No newline at end of file
From 6e343b9ad7bca473947cbee0c7ea9455d9485599 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 21 Mar 2025 13:01:58 -0400
Subject: [PATCH 56/97] Version Packages (#15578)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/clever-terms-tell.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/clever-terms-tell.md
diff --git a/.changeset/clever-terms-tell.md b/.changeset/clever-terms-tell.md
deleted file mode 100644
index 606868bce39c..000000000000
--- a/.changeset/clever-terms-tell.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': minor
----
-
-feat: make deriveds writable
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 04ddfcadbd30..4bac12916931 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.25.0
+
+### Minor Changes
+
+- feat: make deriveds writable ([#15570](https://github.com/sveltejs/svelte/pull/15570))
+
## 5.24.1
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index f321571e7abb..e3824b89fb9f 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.24.1",
+ "version": "5.25.0",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 565c19071396..a62190bb2e07 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.24.1';
+export const VERSION = '5.25.0';
export const PUBLIC_VERSION = '5';
From 441108b8ff28a6c1aa8e38f5c041a2583446167e Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Mar 2025 13:06:46 -0400
Subject: [PATCH 57/97] fix docs
---
documentation/docs/98-reference/.generated/client-errors.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index 901c49822c00..fd9419176d81 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -151,6 +151,8 @@ This error occurs when state is updated while evaluating a `$derived`. You might
This is forbidden because it introduces instability: if `{count} is even: {even}
` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
```js
+let count = 0;
+// ---cut---
let even = $derived(count % 2 === 0);
let odd = $derived(!even);
```
From ef98ccae8b27dbac393623c166ea890b515d5e1d Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Mar 2025 15:44:23 -0400
Subject: [PATCH 58/97] doh
---
documentation/docs/98-reference/.generated/client-errors.md | 2 --
1 file changed, 2 deletions(-)
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index fd9419176d81..901c49822c00 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -151,8 +151,6 @@ This error occurs when state is updated while evaluating a `$derived`. You might
This is forbidden because it introduces instability: if `{count} is even: {even}
` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
```js
-let count = 0;
-// ---cut---
let even = $derived(count % 2 === 0);
let odd = $derived(!even);
```
From d1bd32ec9ec06e6505740a0de5f5b2281546787c Mon Sep 17 00:00:00 2001
From: Blade Barringer
Date: Fri, 21 Mar 2025 14:46:20 -0500
Subject: [PATCH 59/97] fix: allow get_proxied_value to return original value
when error (#15577)
* fix: allow get_proxied_value to return original value when error
closes #15546
* Update packages/svelte/src/internal/client/proxy.js
---------
Co-authored-by: Rich Harris
---
.changeset/afraid-penguins-battle.md | 5 +++++
packages/svelte/src/internal/client/proxy.js | 14 ++++++++++++--
2 files changed, 17 insertions(+), 2 deletions(-)
create mode 100644 .changeset/afraid-penguins-battle.md
diff --git a/.changeset/afraid-penguins-battle.md b/.changeset/afraid-penguins-battle.md
new file mode 100644
index 000000000000..2cc5059b9a43
--- /dev/null
+++ b/.changeset/afraid-penguins-battle.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: prevent dev server from throwing errors when attempting to retrieve the proxied value of an iframe's contentWindow
diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js
index ffe63f4b77a8..fab271c91652 100644
--- a/packages/svelte/src/internal/client/proxy.js
+++ b/packages/svelte/src/internal/client/proxy.js
@@ -366,8 +366,18 @@ function update_version(signal, d = 1) {
* @param {any} value
*/
export function get_proxied_value(value) {
- if (value !== null && typeof value === 'object' && STATE_SYMBOL in value) {
- return value[STATE_SYMBOL];
+ try {
+ if (value !== null && typeof value === 'object' && STATE_SYMBOL in value) {
+ return value[STATE_SYMBOL];
+ }
+ } catch {
+ // the above if check can throw an error if the value in question
+ // is the contentWindow of an iframe on another domain, in which
+ // case we want to just return the value (because it's definitely
+ // not a proxied value) so we don't break any JavaScript interacting
+ // with that iframe (such as various payment companies client side
+ // JavaScript libraries interacting with their iframes on the same
+ // domain)
}
return value;
From c1ae8953aaa81b9191d8d944c4bf0df7fdf4f2ee Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 21 Mar 2025 17:04:59 -0400
Subject: [PATCH 60/97] Version Packages (#15580)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/afraid-penguins-battle.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/afraid-penguins-battle.md
diff --git a/.changeset/afraid-penguins-battle.md b/.changeset/afraid-penguins-battle.md
deleted file mode 100644
index 2cc5059b9a43..000000000000
--- a/.changeset/afraid-penguins-battle.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: prevent dev server from throwing errors when attempting to retrieve the proxied value of an iframe's contentWindow
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 4bac12916931..9e99e91b8e73 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.25.1
+
+### Patch Changes
+
+- fix: prevent dev server from throwing errors when attempting to retrieve the proxied value of an iframe's contentWindow ([#15577](https://github.com/sveltejs/svelte/pull/15577))
+
## 5.25.0
### Minor Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index e3824b89fb9f..9d3902696d25 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.25.0",
+ "version": "5.25.1",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index a62190bb2e07..a4f5a15c8f7b 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.25.0';
+export const VERSION = '5.25.1';
export const PUBLIC_VERSION = '5';
From 33d118f8a29a376e4490f2d31b0b444bf8fa0c7c Mon Sep 17 00:00:00 2001
From: Paolo Ricciuti
Date: Fri, 21 Mar 2025 23:47:26 +0100
Subject: [PATCH 61/97] feat: migrate reassigned deriveds to `$derived`
(#15581)
---
.changeset/forty-snakes-lay.md | 5 +++++
packages/svelte/src/compiler/migrate/index.js | 17 ++++++++++++++++-
.../samples/reassigned-deriveds/input.svelte | 10 ++++++++++
.../samples/reassigned-deriveds/output.svelte | 10 ++++++++++
4 files changed, 41 insertions(+), 1 deletion(-)
create mode 100644 .changeset/forty-snakes-lay.md
create mode 100644 packages/svelte/tests/migrate/samples/reassigned-deriveds/input.svelte
create mode 100644 packages/svelte/tests/migrate/samples/reassigned-deriveds/output.svelte
diff --git a/.changeset/forty-snakes-lay.md b/.changeset/forty-snakes-lay.md
new file mode 100644
index 000000000000..6cb4c2d76122
--- /dev/null
+++ b/.changeset/forty-snakes-lay.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+feat: migrate reassigned deriveds to `$derived`
diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js
index 7f26d0d0103a..b336ebb2b885 100644
--- a/packages/svelte/src/compiler/migrate/index.js
+++ b/packages/svelte/src/compiler/migrate/index.js
@@ -19,6 +19,7 @@ import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js';
import { validate_component_options } from '../validate-options.js';
import { is_reserved, is_svg, is_void } from '../../utils.js';
import { regex_is_valid_identifier } from '../phases/patterns.js';
+import { VERSION } from 'svelte/compiler';
const regex_style_tags = /(
-Foo
\ No newline at end of file
+Foo
+Bar
From d6d99ca2fd198b67a536d8ee30c0df51c5ac788a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 31 Mar 2025 14:40:31 -0400
Subject: [PATCH 70/97] Version Packages (#15647)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/lovely-windows-hang.md | 5 -----
.changeset/weak-knives-battle.md | 5 -----
packages/svelte/CHANGELOG.md | 8 ++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
5 files changed, 10 insertions(+), 12 deletions(-)
delete mode 100644 .changeset/lovely-windows-hang.md
delete mode 100644 .changeset/weak-knives-battle.md
diff --git a/.changeset/lovely-windows-hang.md b/.changeset/lovely-windows-hang.md
deleted file mode 100644
index 406e6c4961d1..000000000000
--- a/.changeset/lovely-windows-hang.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: support TS type assertions
diff --git a/.changeset/weak-knives-battle.md b/.changeset/weak-knives-battle.md
deleted file mode 100644
index 702053ee9ea3..000000000000
--- a/.changeset/weak-knives-battle.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: ensure `undefined` class still applies scoping class, if necessary
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index e6b5de7e5f29..699a094aa2ac 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,13 @@
# svelte
+## 5.25.4
+
+### Patch Changes
+
+- fix: support TS type assertions ([#15642](https://github.com/sveltejs/svelte/pull/15642))
+
+- fix: ensure `undefined` class still applies scoping class, if necessary ([#15643](https://github.com/sveltejs/svelte/pull/15643))
+
## 5.25.3
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 04105206345c..9c79dfc8bc2e 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.25.3",
+ "version": "5.25.4",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index e4ae33a9b120..b2cfbd6469aa 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.25.3';
+export const VERSION = '5.25.4';
export const PUBLIC_VERSION = '5';
From 046145fdd7a0bf245770f7866530ae263b927708 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Mon, 31 Mar 2025 20:41:20 +0200
Subject: [PATCH 71/97] fix: make sure CSS is preserved during SSR with
bindings (#15645)
fixes #15543
No test because this is a one-liner in a stable part of the code-base, also it can only happen within legacy mode when using bindings which is not getting any new updates at this point
---
.changeset/smooth-mugs-grin.md | 5 +++++
packages/svelte/src/internal/server/index.js | 1 +
2 files changed, 6 insertions(+)
create mode 100644 .changeset/smooth-mugs-grin.md
diff --git a/.changeset/smooth-mugs-grin.md b/.changeset/smooth-mugs-grin.md
new file mode 100644
index 000000000000..3302a63012d4
--- /dev/null
+++ b/.changeset/smooth-mugs-grin.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: make sure CSS is preserved during SSR with bindings
diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js
index 6098b496c5ac..bf36a595d8a9 100644
--- a/packages/svelte/src/internal/server/index.js
+++ b/packages/svelte/src/internal/server/index.js
@@ -49,6 +49,7 @@ export function copy_payload({ out, css, head, uid }) {
*/
export function assign_payload(p1, p2) {
p1.out = p2.out;
+ p1.css = p2.css;
p1.head = p2.head;
p1.uid = p2.uid;
}
From 97542048dfc5f40d0ce31ccad29509613da07f21 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Mon, 31 Mar 2025 21:15:06 +0200
Subject: [PATCH 72/97] fix: silence assignment warning on more function
bindings (#15644)
* fix: silence assignment warning on more function bindings
fixes #15550
* Update packages/svelte/tests/html_equal.js
---
.changeset/rare-falcons-admire.md | 5 +++++
.../3-transform/client/visitors/AssignmentExpression.js | 4 +++-
packages/svelte/tests/html_equal.js | 2 +-
.../samples/proxy-coercive-assignment-warning/_config.js | 8 ++++----
.../samples/proxy-coercive-assignment-warning/main.svelte | 7 +++++++
5 files changed, 20 insertions(+), 6 deletions(-)
create mode 100644 .changeset/rare-falcons-admire.md
diff --git a/.changeset/rare-falcons-admire.md b/.changeset/rare-falcons-admire.md
new file mode 100644
index 000000000000..ad13dd1cbca7
--- /dev/null
+++ b/.changeset/rare-falcons-admire.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: silence assignment warning on more function bindings
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 150c56e166c1..850cd83b15b6 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
@@ -165,7 +165,9 @@ function build_assignment(operator, left, right, context) {
path.at(-1) === 'SvelteComponent' ||
(path.at(-1) === 'ArrowFunctionExpression' &&
path.at(-2) === 'SequenceExpression' &&
- (path.at(-3) === 'Component' || path.at(-3) === 'SvelteComponent'))
+ (path.at(-3) === 'Component' ||
+ path.at(-3) === 'SvelteComponent' ||
+ path.at(-3) === 'BindDirective'))
) {
should_transform = false;
}
diff --git a/packages/svelte/tests/html_equal.js b/packages/svelte/tests/html_equal.js
index 0ebf1fa6bd53..4c9e2a725332 100644
--- a/packages/svelte/tests/html_equal.js
+++ b/packages/svelte/tests/html_equal.js
@@ -86,7 +86,7 @@ export function normalize_html(
clean_children(node);
return node.innerHTML;
} catch (err) {
- throw new Error(`Failed to normalize HTML:\n${html}`);
+ throw new Error(`Failed to normalize HTML:\n${html}\nCause: ${err}`);
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js
index f0292274725e..4462f492fac9 100644
--- a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js
@@ -6,7 +6,7 @@ export default test({
dev: true
},
- html: `items: null x
`,
+ html: `items: null x
`,
test({ assert, target, warnings }) {
const btn = target.querySelector('button');
@@ -15,13 +15,13 @@ export default test({
flushSync(() => btn.click());
assert.htmlEqual(
target.innerHTML,
- `items: [] x
`
+ `items: [] x
`
);
flushSync(() => btn.click());
assert.htmlEqual(
target.innerHTML,
- `items: [0] x
`
+ `items: [0] x
`
);
const input = target.querySelector('input');
@@ -30,7 +30,7 @@ export default test({
flushSync(() => input.dispatchEvent(new Event('change', { bubbles: true })));
assert.deepEqual(warnings, [
- 'Assignment to `items` property (main.svelte:8:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
+ 'Assignment to `items` property (main.svelte:9:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
]);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte
index ad94c4e56e03..a79fe873b7e9 100644
--- a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte
@@ -3,6 +3,7 @@
let entries = $state([]);
let object = $state({ items: null, group: [] });
+ let elementFunBind = $state();
(object.items ??= []).push(object.items.length)}>
@@ -13,6 +14,12 @@
x
+
entries[2], (v) => (entries[2] = v)}>
+
+{#snippet funBind(context)}
+ {}, (e) => (context.element = e)} />
+{/snippet}
+{@render funBind({ set element(e) { elementFunBind = e } })}
From 5d0eb27526caf457ef01c374cefd8740be7fc335 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 31 Mar 2025 15:21:34 -0400
Subject: [PATCH 73/97] chore(deps-dev): bump vite from 5.4.14 to 5.4.16
(#15648)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.14 to 5.4.16.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.16/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.16/packages/vite)
---
updated-dependencies:
- dependency-name: vite
dependency-type: direct:development
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
playgrounds/sandbox/package.json | 2 +-
pnpm-lock.yaml | 478 +++++++++++++++++++++++++++----
2 files changed, 420 insertions(+), 60 deletions(-)
diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json
index 9e2e5f673815..cb4490530949 100644
--- a/playgrounds/sandbox/package.json
+++ b/playgrounds/sandbox/package.json
@@ -18,7 +18,7 @@
"polka": "^1.0.0-next.25",
"svelte": "workspace:*",
"tinyglobby": "^0.2.12",
- "vite": "^5.4.14",
+ "vite": "^5.4.16",
"vite-plugin-inspect": "^0.8.4"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c687db12d4a9..eaf407e30475 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -152,7 +152,7 @@ importers:
devDependencies:
'@sveltejs/vite-plugin-svelte':
specifier: ^4.0.0-next.6
- version: 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ version: 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
polka:
specifier: ^1.0.0-next.25
version: 1.0.0-next.25
@@ -163,11 +163,11 @@ importers:
specifier: ^0.2.12
version: 0.2.12
vite:
- specifier: ^5.4.14
- version: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ specifier: ^5.4.16
+ version: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
vite-plugin-inspect:
specifier: ^0.8.4
- version: 0.8.4(rollup@4.22.4)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ version: 0.8.4(rollup@4.38.0)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
packages:
@@ -408,6 +408,12 @@ packages:
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+ '@eslint-community/eslint-utils@4.5.1':
+ resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
'@eslint-community/regexpp@4.12.1':
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -545,81 +551,181 @@ packages:
cpu: [arm]
os: [android]
+ '@rollup/rollup-android-arm-eabi@4.38.0':
+ resolution: {integrity: sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==}
+ cpu: [arm]
+ os: [android]
+
'@rollup/rollup-android-arm64@4.22.4':
resolution: {integrity: sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==}
cpu: [arm64]
os: [android]
+ '@rollup/rollup-android-arm64@4.38.0':
+ resolution: {integrity: sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==}
+ cpu: [arm64]
+ os: [android]
+
'@rollup/rollup-darwin-arm64@4.22.4':
resolution: {integrity: sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==}
cpu: [arm64]
os: [darwin]
+ '@rollup/rollup-darwin-arm64@4.38.0':
+ resolution: {integrity: sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==}
+ cpu: [arm64]
+ os: [darwin]
+
'@rollup/rollup-darwin-x64@4.22.4':
resolution: {integrity: sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==}
cpu: [x64]
os: [darwin]
+ '@rollup/rollup-darwin-x64@4.38.0':
+ resolution: {integrity: sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.38.0':
+ resolution: {integrity: sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.38.0':
+ resolution: {integrity: sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==}
+ cpu: [x64]
+ os: [freebsd]
+
'@rollup/rollup-linux-arm-gnueabihf@4.22.4':
resolution: {integrity: sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==}
cpu: [arm]
os: [linux]
+ '@rollup/rollup-linux-arm-gnueabihf@4.38.0':
+ resolution: {integrity: sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==}
+ cpu: [arm]
+ os: [linux]
+
'@rollup/rollup-linux-arm-musleabihf@4.22.4':
resolution: {integrity: sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==}
cpu: [arm]
os: [linux]
+ '@rollup/rollup-linux-arm-musleabihf@4.38.0':
+ resolution: {integrity: sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==}
+ cpu: [arm]
+ os: [linux]
+
'@rollup/rollup-linux-arm64-gnu@4.22.4':
resolution: {integrity: sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==}
cpu: [arm64]
os: [linux]
+ '@rollup/rollup-linux-arm64-gnu@4.38.0':
+ resolution: {integrity: sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==}
+ cpu: [arm64]
+ os: [linux]
+
'@rollup/rollup-linux-arm64-musl@4.22.4':
resolution: {integrity: sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==}
cpu: [arm64]
os: [linux]
+ '@rollup/rollup-linux-arm64-musl@4.38.0':
+ resolution: {integrity: sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.38.0':
+ resolution: {integrity: sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==}
+ cpu: [loong64]
+ os: [linux]
+
'@rollup/rollup-linux-powerpc64le-gnu@4.22.4':
resolution: {integrity: sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==}
cpu: [ppc64]
os: [linux]
+ '@rollup/rollup-linux-powerpc64le-gnu@4.38.0':
+ resolution: {integrity: sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==}
+ cpu: [ppc64]
+ os: [linux]
+
'@rollup/rollup-linux-riscv64-gnu@4.22.4':
resolution: {integrity: sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==}
cpu: [riscv64]
os: [linux]
+ '@rollup/rollup-linux-riscv64-gnu@4.38.0':
+ resolution: {integrity: sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.38.0':
+ resolution: {integrity: sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==}
+ cpu: [riscv64]
+ os: [linux]
+
'@rollup/rollup-linux-s390x-gnu@4.22.4':
resolution: {integrity: sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==}
cpu: [s390x]
os: [linux]
+ '@rollup/rollup-linux-s390x-gnu@4.38.0':
+ resolution: {integrity: sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==}
+ cpu: [s390x]
+ os: [linux]
+
'@rollup/rollup-linux-x64-gnu@4.22.4':
resolution: {integrity: sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==}
cpu: [x64]
os: [linux]
+ '@rollup/rollup-linux-x64-gnu@4.38.0':
+ resolution: {integrity: sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==}
+ cpu: [x64]
+ os: [linux]
+
'@rollup/rollup-linux-x64-musl@4.22.4':
resolution: {integrity: sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==}
cpu: [x64]
os: [linux]
+ '@rollup/rollup-linux-x64-musl@4.38.0':
+ resolution: {integrity: sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==}
+ cpu: [x64]
+ os: [linux]
+
'@rollup/rollup-win32-arm64-msvc@4.22.4':
resolution: {integrity: sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==}
cpu: [arm64]
os: [win32]
+ '@rollup/rollup-win32-arm64-msvc@4.38.0':
+ resolution: {integrity: sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==}
+ cpu: [arm64]
+ os: [win32]
+
'@rollup/rollup-win32-ia32-msvc@4.22.4':
resolution: {integrity: sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==}
cpu: [ia32]
os: [win32]
+ '@rollup/rollup-win32-ia32-msvc@4.38.0':
+ resolution: {integrity: sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==}
+ cpu: [ia32]
+ os: [win32]
+
'@rollup/rollup-win32-x64-msvc@4.22.4':
resolution: {integrity: sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==}
cpu: [x64]
os: [win32]
+ '@rollup/rollup-win32-x64-msvc@4.38.0':
+ resolution: {integrity: sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==}
+ cpu: [x64]
+ os: [win32]
+
'@stylistic/eslint-plugin-js@1.8.0':
resolution: {integrity: sha512-jdvnzt+pZPg8TfclZlTZPiUbbima93ylvQ+wNgHLNmup3obY6heQvgewSu9i2CfS61BnRByv+F9fxQLPoNeHag==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -673,6 +779,9 @@ packages:
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+ '@types/estree@1.0.7':
+ resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
+
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -707,6 +816,10 @@ packages:
resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@typescript-eslint/scope-manager@8.29.0':
+ resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@typescript-eslint/type-utils@8.26.0':
resolution: {integrity: sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -718,12 +831,22 @@ packages:
resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@typescript-eslint/types@8.29.0':
+ resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@typescript-eslint/typescript-estree@8.26.0':
resolution: {integrity: sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <5.9.0'
+ '@typescript-eslint/typescript-estree@8.29.0':
+ resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <5.9.0'
+
'@typescript-eslint/utils@8.26.0':
resolution: {integrity: sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -731,10 +854,21 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
+ '@typescript-eslint/utils@8.29.0':
+ resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
+
'@typescript-eslint/visitor-keys@8.26.0':
resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@typescript-eslint/visitor-keys@8.29.0':
+ resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@vitest/coverage-v8@2.0.5':
resolution: {integrity: sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==}
peerDependencies:
@@ -779,6 +913,11 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ acorn@8.14.1:
+ resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
agent-base@7.1.1:
resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
engines: {node: '>= 14'}
@@ -916,7 +1055,7 @@ packages:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
concat-map@0.0.1:
- resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
@@ -1161,6 +1300,10 @@ packages:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
@@ -1257,6 +1400,10 @@ packages:
resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==}
engines: {node: '>=18'}
+ globals@15.15.0:
+ resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
+ engines: {node: '>=18'}
+
globby@11.1.0:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
engines: {node: '>=10'}
@@ -1551,6 +1698,10 @@ packages:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
engines: {node: '>=8.6'}
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -1559,10 +1710,6 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
- minimatch@10.0.1:
- resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
- engines: {node: 20 || >=22}
-
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1585,8 +1732,8 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
- nanoid@3.3.8:
- resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -1751,8 +1898,8 @@ packages:
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
engines: {node: '>=4'}
- postcss@8.5.1:
- resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
+ postcss@8.5.3:
+ resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
@@ -1827,6 +1974,11 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ rollup@4.38.0:
+ resolution: {integrity: sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
rrweb-cssom@0.7.1:
resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
@@ -2064,8 +2216,14 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
- ts-declaration-location@1.0.5:
- resolution: {integrity: sha512-WqmlO9IoeYwCqJ2E9kHMcY9GZhhfLYItC3VnHDlPOrg6nNdUWS4wn4hhDZUPt60m1EvtjPIZyprTjpI992Bgzw==}
+ ts-api-utils@2.1.0:
+ resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ ts-declaration-location@1.0.7:
+ resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==}
peerDependencies:
typescript: '>=4.0.0'
@@ -2152,6 +2310,37 @@ packages:
terser:
optional: true
+ vite@5.4.16:
+ resolution: {integrity: sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+
vitefu@0.2.5:
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==}
peerDependencies:
@@ -2522,6 +2711,11 @@ snapshots:
eslint: 9.9.1
eslint-visitor-keys: 3.4.3
+ '@eslint-community/eslint-utils@4.5.1(eslint@9.9.1)':
+ dependencies:
+ eslint: 9.9.1
+ eslint-visitor-keys: 3.4.3
+
'@eslint-community/regexpp@4.12.1': {}
'@eslint/config-array@0.18.0':
@@ -2666,58 +2860,126 @@ snapshots:
optionalDependencies:
rollup: 4.22.4
+ '@rollup/pluginutils@5.1.0(rollup@4.38.0)':
+ dependencies:
+ '@types/estree': 1.0.6
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ optionalDependencies:
+ rollup: 4.38.0
+
'@rollup/rollup-android-arm-eabi@4.22.4':
optional: true
+ '@rollup/rollup-android-arm-eabi@4.38.0':
+ optional: true
+
'@rollup/rollup-android-arm64@4.22.4':
optional: true
+ '@rollup/rollup-android-arm64@4.38.0':
+ optional: true
+
'@rollup/rollup-darwin-arm64@4.22.4':
optional: true
+ '@rollup/rollup-darwin-arm64@4.38.0':
+ optional: true
+
'@rollup/rollup-darwin-x64@4.22.4':
optional: true
+ '@rollup/rollup-darwin-x64@4.38.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.38.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-arm-gnueabihf@4.22.4':
optional: true
+ '@rollup/rollup-linux-arm-gnueabihf@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-arm-musleabihf@4.22.4':
optional: true
+ '@rollup/rollup-linux-arm-musleabihf@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-arm64-gnu@4.22.4':
optional: true
+ '@rollup/rollup-linux-arm64-gnu@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-arm64-musl@4.22.4':
optional: true
+ '@rollup/rollup-linux-arm64-musl@4.38.0':
+ optional: true
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-powerpc64le-gnu@4.22.4':
optional: true
+ '@rollup/rollup-linux-powerpc64le-gnu@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-riscv64-gnu@4.22.4':
optional: true
+ '@rollup/rollup-linux-riscv64-gnu@4.38.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-s390x-gnu@4.22.4':
optional: true
+ '@rollup/rollup-linux-s390x-gnu@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-x64-gnu@4.22.4':
optional: true
+ '@rollup/rollup-linux-x64-gnu@4.38.0':
+ optional: true
+
'@rollup/rollup-linux-x64-musl@4.22.4':
optional: true
+ '@rollup/rollup-linux-x64-musl@4.38.0':
+ optional: true
+
'@rollup/rollup-win32-arm64-msvc@4.22.4':
optional: true
+ '@rollup/rollup-win32-arm64-msvc@4.38.0':
+ optional: true
+
'@rollup/rollup-win32-ia32-msvc@4.22.4':
optional: true
+ '@rollup/rollup-win32-ia32-msvc@4.38.0':
+ optional: true
+
'@rollup/rollup-win32-x64-msvc@4.22.4':
optional: true
+ '@rollup/rollup-win32-x64-msvc@4.38.0':
+ optional: true
+
'@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1)':
dependencies:
'@types/eslint': 8.56.12
- acorn: 8.14.0
+ acorn: 8.14.1
escape-string-regexp: 4.0.0
eslint: 9.9.1
eslint-visitor-keys: 3.4.3
@@ -2738,25 +3000,25 @@ snapshots:
typescript: 5.5.4
typescript-eslint: 8.26.0(eslint@9.9.1)(typescript@5.5.4)
- '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
+ '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
dependencies:
- '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
debug: 4.4.0
svelte: link:packages/svelte
- vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
transitivePeerDependencies:
- supports-color
- '@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
+ '@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
dependencies:
- '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
debug: 4.4.0
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.17
svelte: link:packages/svelte
- vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
- vitefu: 0.2.5(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vitefu: 0.2.5(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
transitivePeerDependencies:
- supports-color
@@ -2771,13 +3033,15 @@ snapshots:
'@types/eslint@8.56.12':
dependencies:
- '@types/estree': 1.0.6
+ '@types/estree': 1.0.7
'@types/json-schema': 7.0.15
'@types/estree@1.0.5': {}
'@types/estree@1.0.6': {}
+ '@types/estree@1.0.7': {}
+
'@types/json-schema@7.0.15': {}
'@types/node@12.20.55': {}
@@ -2824,6 +3088,11 @@ snapshots:
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/visitor-keys': 8.26.0
+ '@typescript-eslint/scope-manager@8.29.0':
+ dependencies:
+ '@typescript-eslint/types': 8.29.0
+ '@typescript-eslint/visitor-keys': 8.29.0
+
'@typescript-eslint/type-utils@8.26.0(eslint@9.9.1)(typescript@5.5.4)':
dependencies:
'@typescript-eslint/typescript-estree': 8.26.0(typescript@5.5.4)
@@ -2837,6 +3106,8 @@ snapshots:
'@typescript-eslint/types@8.26.0': {}
+ '@typescript-eslint/types@8.29.0': {}
+
'@typescript-eslint/typescript-estree@8.26.0(typescript@5.5.4)':
dependencies:
'@typescript-eslint/types': 8.26.0
@@ -2851,6 +3122,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/typescript-estree@8.29.0(typescript@5.5.4)':
+ dependencies:
+ '@typescript-eslint/types': 8.29.0
+ '@typescript-eslint/visitor-keys': 8.29.0
+ debug: 4.4.0
+ fast-glob: 3.3.3
+ is-glob: 4.0.3
+ minimatch: 9.0.5
+ semver: 7.7.1
+ ts-api-utils: 2.1.0(typescript@5.5.4)
+ typescript: 5.5.4
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/utils@8.26.0(eslint@9.9.1)(typescript@5.5.4)':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.9.1)
@@ -2862,11 +3147,27 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/utils@8.29.0(eslint@9.9.1)(typescript@5.5.4)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.5.1(eslint@9.9.1)
+ '@typescript-eslint/scope-manager': 8.29.0
+ '@typescript-eslint/types': 8.29.0
+ '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.5.4)
+ eslint: 9.9.1
+ typescript: 5.5.4
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/visitor-keys@8.26.0':
dependencies:
'@typescript-eslint/types': 8.26.0
eslint-visitor-keys: 4.2.0
+ '@typescript-eslint/visitor-keys@8.29.0':
+ dependencies:
+ '@typescript-eslint/types': 8.29.0
+ eslint-visitor-keys: 4.2.0
+
'@vitest/coverage-v8@2.0.5(vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
dependencies:
'@ampproject/remapping': 2.3.0
@@ -2929,8 +3230,14 @@ snapshots:
dependencies:
acorn: 8.14.0
+ acorn-jsx@5.3.2(acorn@8.14.1):
+ dependencies:
+ acorn: 8.14.1
+
acorn@8.14.0: {}
+ acorn@8.14.1: {}
+
agent-base@7.1.1:
dependencies:
debug: 4.4.0
@@ -3192,7 +3499,7 @@ snapshots:
eslint-plugin-es-x@7.8.0(eslint@9.9.1):
dependencies:
- '@eslint-community/eslint-utils': 4.4.1(eslint@9.9.1)
+ '@eslint-community/eslint-utils': 4.5.1(eslint@9.9.1)
'@eslint-community/regexpp': 4.12.1
eslint: 9.9.1
eslint-compat-utils: 0.5.1(eslint@9.9.1)
@@ -3201,33 +3508,33 @@ snapshots:
eslint-plugin-n@17.16.1(eslint@9.9.1)(typescript@5.5.4):
dependencies:
- '@eslint-community/eslint-utils': 4.4.1(eslint@9.9.1)
- '@typescript-eslint/utils': 8.26.0(eslint@9.9.1)(typescript@5.5.4)
+ '@eslint-community/eslint-utils': 4.5.1(eslint@9.9.1)
+ '@typescript-eslint/utils': 8.29.0(eslint@9.9.1)(typescript@5.5.4)
enhanced-resolve: 5.18.1
eslint: 9.9.1
eslint-plugin-es-x: 7.8.0(eslint@9.9.1)
get-tsconfig: 4.10.0
- globals: 15.14.0
+ globals: 15.15.0
ignore: 5.3.2
minimatch: 9.0.5
semver: 7.7.1
- ts-declaration-location: 1.0.5(typescript@5.5.4)
+ ts-declaration-location: 1.0.7(typescript@5.5.4)
transitivePeerDependencies:
- supports-color
- typescript
eslint-plugin-svelte@2.38.0(eslint@9.9.1)(svelte@packages+svelte):
dependencies:
- '@eslint-community/eslint-utils': 4.4.1(eslint@9.9.1)
+ '@eslint-community/eslint-utils': 4.5.1(eslint@9.9.1)
'@jridgewell/sourcemap-codec': 1.5.0
debug: 4.4.0
eslint: 9.9.1
eslint-compat-utils: 0.5.1(eslint@9.9.1)
esutils: 2.0.3
known-css-properties: 0.30.0
- postcss: 8.5.1
- postcss-load-config: 3.1.4(postcss@8.5.1)
- postcss-safe-parser: 6.0.0(postcss@8.5.1)
+ postcss: 8.5.3
+ postcss-load-config: 3.1.4(postcss@8.5.3)
+ postcss-safe-parser: 6.0.0(postcss@8.5.3)
postcss-selector-parser: 6.1.2
semver: 7.7.1
svelte-eslint-parser: 0.43.0(svelte@packages+svelte)
@@ -3300,8 +3607,8 @@ snapshots:
espree@9.6.1:
dependencies:
- acorn: 8.14.0
- acorn-jsx: 5.3.2(acorn@8.14.0)
+ acorn: 8.14.1
+ acorn-jsx: 5.3.2(acorn@8.14.1)
eslint-visitor-keys: 3.4.3
esprima@4.0.1: {}
@@ -3348,6 +3655,14 @@ snapshots:
merge2: 1.4.1
micromatch: 4.0.5
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
fast-json-stable-stringify@2.1.0: {}
fast-levenshtein@2.0.6: {}
@@ -3447,6 +3762,8 @@ snapshots:
globals@15.14.0: {}
+ globals@15.15.0: {}
+
globby@11.1.0:
dependencies:
array-union: 2.1.0
@@ -3740,16 +4057,17 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
- minimatch@10.0.1:
- dependencies:
- brace-expansion: 2.0.1
-
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -3766,7 +4084,7 @@ snapshots:
ms@2.1.3: {}
- nanoid@3.3.8: {}
+ nanoid@3.3.11: {}
natural-compare@1.4.0: {}
@@ -3875,29 +4193,29 @@ snapshots:
'@polka/url': 1.0.0-next.25
trouter: 4.0.0
- postcss-load-config@3.1.4(postcss@8.5.1):
+ postcss-load-config@3.1.4(postcss@8.5.3):
dependencies:
lilconfig: 2.1.0
yaml: 1.10.2
optionalDependencies:
- postcss: 8.5.1
+ postcss: 8.5.3
- postcss-safe-parser@6.0.0(postcss@8.5.1):
+ postcss-safe-parser@6.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.5.1
+ postcss: 8.5.3
- postcss-scss@4.0.9(postcss@8.5.1):
+ postcss-scss@4.0.9(postcss@8.5.3):
dependencies:
- postcss: 8.5.1
+ postcss: 8.5.3
postcss-selector-parser@6.1.2:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
- postcss@8.5.1:
+ postcss@8.5.3:
dependencies:
- nanoid: 3.3.8
+ nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
@@ -3974,6 +4292,32 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.22.4
fsevents: 2.3.3
+ rollup@4.38.0:
+ dependencies:
+ '@types/estree': 1.0.7
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.38.0
+ '@rollup/rollup-android-arm64': 4.38.0
+ '@rollup/rollup-darwin-arm64': 4.38.0
+ '@rollup/rollup-darwin-x64': 4.38.0
+ '@rollup/rollup-freebsd-arm64': 4.38.0
+ '@rollup/rollup-freebsd-x64': 4.38.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.38.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.38.0
+ '@rollup/rollup-linux-arm64-gnu': 4.38.0
+ '@rollup/rollup-linux-arm64-musl': 4.38.0
+ '@rollup/rollup-linux-loongarch64-gnu': 4.38.0
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.38.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.38.0
+ '@rollup/rollup-linux-riscv64-musl': 4.38.0
+ '@rollup/rollup-linux-s390x-gnu': 4.38.0
+ '@rollup/rollup-linux-x64-gnu': 4.38.0
+ '@rollup/rollup-linux-x64-musl': 4.38.0
+ '@rollup/rollup-win32-arm64-msvc': 4.38.0
+ '@rollup/rollup-win32-ia32-msvc': 4.38.0
+ '@rollup/rollup-win32-x64-msvc': 4.38.0
+ fsevents: 2.3.3
+
rrweb-cssom@0.7.1: {}
run-applescript@7.0.0: {}
@@ -4092,8 +4436,8 @@ snapshots:
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
- postcss: 8.5.1
- postcss-scss: 4.0.9(postcss@8.5.1)
+ postcss: 8.5.3
+ postcss-scss: 4.0.9(postcss@8.5.3)
optionalDependencies:
svelte: link:packages/svelte
@@ -4173,9 +4517,13 @@ snapshots:
dependencies:
typescript: 5.5.4
- ts-declaration-location@1.0.5(typescript@5.5.4):
+ ts-api-utils@2.1.0(typescript@5.5.4):
+ dependencies:
+ typescript: 5.5.4
+
+ ts-declaration-location@1.0.7(typescript@5.5.4):
dependencies:
- minimatch: 10.0.1
+ picomatch: 4.0.2
typescript: 5.5.4
type-check@0.4.0:
@@ -4214,7 +4562,7 @@ snapshots:
debug: 4.4.0
es-module-lexer: 1.6.0
pathe: 1.1.2
- vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -4226,10 +4574,10 @@ snapshots:
- supports-color
- terser
- vite-plugin-inspect@0.8.4(rollup@4.22.4)(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)):
+ vite-plugin-inspect@0.8.4(rollup@4.38.0)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)):
dependencies:
'@antfu/utils': 0.7.8
- '@rollup/pluginutils': 5.1.0(rollup@4.22.4)
+ '@rollup/pluginutils': 5.1.0(rollup@4.38.0)
debug: 4.4.0
error-stack-parser-es: 0.1.1
fs-extra: 11.2.0
@@ -4237,7 +4585,7 @@ snapshots:
perfect-debounce: 1.0.0
picocolors: 1.1.1
sirv: 2.0.4
- vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
transitivePeerDependencies:
- rollup
- supports-color
@@ -4245,7 +4593,7 @@ snapshots:
vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0):
dependencies:
esbuild: 0.21.5
- postcss: 8.5.1
+ postcss: 8.5.3
rollup: 4.22.4
optionalDependencies:
'@types/node': 20.12.7
@@ -4254,9 +4602,21 @@ snapshots:
sass: 1.70.0
terser: 5.27.0
- vitefu@0.2.5(vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)):
+ vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0):
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.5.3
+ rollup: 4.38.0
optionalDependencies:
- vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ '@types/node': 20.12.7
+ fsevents: 2.3.3
+ lightningcss: 1.23.0
+ sass: 1.70.0
+ terser: 5.27.0
+
+ vitefu@0.2.5(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)):
+ optionalDependencies:
+ vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0):
dependencies:
From ec91cacab2f10529a55ce8b6e8a2cdd21794a57e Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Mon, 31 Mar 2025 12:25:56 -0700
Subject: [PATCH 74/97] init (#15628)
---
.changeset/pink-lamps-leave.md | 5 +++
.../3-transform/client/visitors/ClassBody.js | 35 ++++++-------------
2 files changed, 16 insertions(+), 24 deletions(-)
create mode 100644 .changeset/pink-lamps-leave.md
diff --git a/.changeset/pink-lamps-leave.md b/.changeset/pink-lamps-leave.md
new file mode 100644
index 000000000000..7ca9d8736229
--- /dev/null
+++ b/.changeset/pink-lamps-leave.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: add setters to `$derived` class properties
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
index 5787b590a8f9..24c20d7f947f 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
@@ -142,30 +142,17 @@ export function ClassBody(node, context) {
// get foo() { return this.#foo; }
body.push(b.method('get', definition.key, [], [b.return(b.call('$.get', member))]));
- if (field.kind === 'state' || field.kind === 'raw_state') {
- // set foo(value) { this.#foo = value; }
- const value = b.id('value');
-
- body.push(
- b.method(
- 'set',
- definition.key,
- [value],
- [b.stmt(b.call('$.set', member, value, field.kind === 'state' && b.true))]
- )
- );
- }
-
- if (dev && (field.kind === 'derived' || field.kind === 'derived_by')) {
- body.push(
- b.method(
- 'set',
- definition.key,
- [b.id('_')],
- [b.throw_error(`Cannot update a derived property ('${name}')`)]
- )
- );
- }
+ // set foo(value) { this.#foo = value; }
+ const val = b.id('value');
+
+ body.push(
+ b.method(
+ 'set',
+ definition.key,
+ [val],
+ [b.stmt(b.call('$.set', member, val, field.kind === 'state' && b.true))]
+ )
+ );
}
continue;
}
From c822f9b0bf77abe105123a8809bb183312aa9b74 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 31 Mar 2025 15:35:56 -0400
Subject: [PATCH 75/97] Version Packages (#15649)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/pink-lamps-leave.md | 5 -----
.changeset/rare-falcons-admire.md | 5 -----
.changeset/smooth-mugs-grin.md | 5 -----
packages/svelte/CHANGELOG.md | 10 ++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
6 files changed, 12 insertions(+), 17 deletions(-)
delete mode 100644 .changeset/pink-lamps-leave.md
delete mode 100644 .changeset/rare-falcons-admire.md
delete mode 100644 .changeset/smooth-mugs-grin.md
diff --git a/.changeset/pink-lamps-leave.md b/.changeset/pink-lamps-leave.md
deleted file mode 100644
index 7ca9d8736229..000000000000
--- a/.changeset/pink-lamps-leave.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: add setters to `$derived` class properties
diff --git a/.changeset/rare-falcons-admire.md b/.changeset/rare-falcons-admire.md
deleted file mode 100644
index ad13dd1cbca7..000000000000
--- a/.changeset/rare-falcons-admire.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: silence assignment warning on more function bindings
diff --git a/.changeset/smooth-mugs-grin.md b/.changeset/smooth-mugs-grin.md
deleted file mode 100644
index 3302a63012d4..000000000000
--- a/.changeset/smooth-mugs-grin.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: make sure CSS is preserved during SSR with bindings
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 699a094aa2ac..e3fb18e85dd7 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,15 @@
# svelte
+## 5.25.5
+
+### Patch Changes
+
+- fix: add setters to `$derived` class properties ([#15628](https://github.com/sveltejs/svelte/pull/15628))
+
+- fix: silence assignment warning on more function bindings ([#15644](https://github.com/sveltejs/svelte/pull/15644))
+
+- fix: make sure CSS is preserved during SSR with bindings ([#15645](https://github.com/sveltejs/svelte/pull/15645))
+
## 5.25.4
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 9c79dfc8bc2e..f70ae5cac6c5 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.25.4",
+ "version": "5.25.5",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index b2cfbd6469aa..b07135b6fa20 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.25.4';
+export const VERSION = '5.25.5';
export const PUBLIC_VERSION = '5';
From e035e2838faf26da476e73831fb0e555464bf1d1 Mon Sep 17 00:00:00 2001
From: 7nik
Date: Mon, 31 Mar 2025 22:37:27 +0300
Subject: [PATCH 76/97] Fix: consider component and its snippets during css
pruning (#15630)
* fix: consider component and its snippets during css pruning
* add changeset
* fix: avoid iterator.map
---------
Co-authored-by: 7nik
---
.changeset/nervous-humans-flash.md | 5 +++
.../phases/2-analyze/css/css-prune.js | 26 +++++++++----
.../_config.js | 24 +++---------
.../expected.css | 4 +-
.../input.svelte | 4 +-
.../Child.svelte | 5 +++
.../siblings-combinator-component/_config.js | 20 ++++++++++
.../expected.css | 8 ++++
.../input.svelte | 37 +++++++++++++++++++
.../siblings-combinator-slot/_config.js | 10 +----
.../siblings-combinator-slot/expected.css | 2 +-
.../siblings-combinator-slot/input.svelte | 2 +-
12 files changed, 108 insertions(+), 39 deletions(-)
create mode 100644 .changeset/nervous-humans-flash.md
create mode 100644 packages/svelte/tests/css/samples/siblings-combinator-component/Child.svelte
create mode 100644 packages/svelte/tests/css/samples/siblings-combinator-component/_config.js
create mode 100644 packages/svelte/tests/css/samples/siblings-combinator-component/expected.css
create mode 100644 packages/svelte/tests/css/samples/siblings-combinator-component/input.svelte
diff --git a/.changeset/nervous-humans-flash.md b/.changeset/nervous-humans-flash.md
new file mode 100644
index 000000000000..753bb91e3f52
--- /dev/null
+++ b/.changeset/nervous-humans-flash.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: better consider component and its snippets during css pruning
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
index 070ec7cd347e..0646c6341a5f 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
@@ -251,7 +251,11 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
let sibling_matched = false;
for (const possible_sibling of siblings.keys()) {
- if (possible_sibling.type === 'RenderTag' || possible_sibling.type === 'SlotElement') {
+ if (
+ possible_sibling.type === 'RenderTag' ||
+ possible_sibling.type === 'SlotElement' ||
+ possible_sibling.type === 'Component'
+ ) {
// `{@render foo()}foo
` with `:global(.x) + p` is a match
if (rest_selectors.length === 1 && rest_selectors[0].metadata.is_global) {
sibling_matched = true;
@@ -814,10 +818,10 @@ function get_element_parent(node) {
* @param {Direction} direction
* @param {boolean} adjacent_only
* @param {Set} seen
- * @returns {Map}
+ * @returns {Map}
*/
function get_possible_element_siblings(node, direction, adjacent_only, seen = new Set()) {
- /** @type {Map} */
+ /** @type {Map} */
const result = new Map();
const path = node.metadata.path;
@@ -847,14 +851,18 @@ function get_possible_element_siblings(node, direction, adjacent_only, seen = ne
}
// Special case: slots, render tags and svelte:element tags could resolve to no siblings,
// so we want to continue until we find a definite sibling even with the adjacent-only combinator
- } else if (is_block(node)) {
- if (node.type === 'SlotElement') {
+ } else if (is_block(node) || node.type === 'Component') {
+ if (node.type === 'SlotElement' || node.type === 'Component') {
result.set(node, NODE_PROBABLY_EXISTS);
}
const possible_last_child = get_possible_nested_siblings(node, direction, adjacent_only);
add_to_map(possible_last_child, result);
- if (adjacent_only && has_definite_elements(possible_last_child)) {
+ if (
+ adjacent_only &&
+ node.type !== 'Component' &&
+ has_definite_elements(possible_last_child)
+ ) {
return result;
}
} else if (node.type === 'SvelteElement') {
@@ -907,7 +915,7 @@ function get_possible_element_siblings(node, direction, adjacent_only, seen = ne
}
/**
- * @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement | Compiler.AST.SnippetBlock} node
+ * @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement | Compiler.AST.SnippetBlock | Compiler.AST.Component} node
* @param {Direction} direction
* @param {boolean} adjacent_only
* @param {Set} seen
@@ -942,6 +950,10 @@ function get_possible_nested_siblings(node, direction, adjacent_only, seen = new
seen.add(node);
fragments.push(node.body);
break;
+
+ case 'Component':
+ fragments.push(node.fragment, ...[...node.metadata.snippets].map((s) => s.body));
+ break;
}
/** @type {Map} NodeMap */
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-slot/_config.js b/packages/svelte/tests/css/samples/general-siblings-combinator-slot/_config.js
index 97e470d1c325..76448654957d 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-slot/_config.js
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-slot/_config.js
@@ -5,32 +5,20 @@ export default test({
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b ~ .c"',
- start: { character: 137, column: 1, line: 11 },
- end: { character: 144, column: 8, line: 11 }
+ start: { character: 191, column: 1, line: 13 },
+ end: { character: 198, column: 8, line: 13 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".c ~ .f"',
- start: { character: 162, column: 1, line: 12 },
- end: { character: 169, column: 8, line: 12 }
- },
- {
- code: 'css_unused_selector',
- message: 'Unused CSS selector ".f ~ .g"',
- start: { character: 187, column: 1, line: 13 },
- end: { character: 194, column: 8, line: 13 }
+ start: { character: 216, column: 1, line: 14 },
+ end: { character: 223, column: 8, line: 14 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b ~ .f"',
- start: { character: 212, column: 1, line: 14 },
- end: { character: 219, column: 8, line: 14 }
- },
- {
- code: 'css_unused_selector',
- message: 'Unused CSS selector ".b ~ .g"',
- start: { character: 237, column: 1, line: 15 },
- end: { character: 244, column: 8, line: 15 }
+ start: { character: 241, column: 1, line: 15 },
+ end: { character: 248, column: 8, line: 15 }
}
]
});
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-slot/expected.css b/packages/svelte/tests/css/samples/general-siblings-combinator-slot/expected.css
index 67a19d10c996..53fca3ae9e04 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-slot/expected.css
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-slot/expected.css
@@ -2,10 +2,10 @@
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
+ .f.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
+ .b.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b ~ .c { color: red; }*/
/* (unused) .c ~ .f { color: red; }*/
- /* (unused) .f ~ .g { color: red; }*/
/* (unused) .b ~ .f { color: red; }*/
- /* (unused) .b ~ .g { color: red; }*/
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-slot/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-slot/input.svelte
index 2e2846fa87a6..52264d3a5a25 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-slot/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-slot/input.svelte
@@ -6,13 +6,13 @@
.d ~ .e { color: green; }
.a ~ .g { color: green; }
.a ~ .b { color: green; }
+ .f ~ .g { color: green; }
+ .b ~ .g { color: green; }
/* no match */
.b ~ .c { color: red; }
.c ~ .f { color: red; }
- .f ~ .g { color: red; }
.b ~ .f { color: red; }
- .b ~ .g { color: red; }
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-component/Child.svelte b/packages/svelte/tests/css/samples/siblings-combinator-component/Child.svelte
new file mode 100644
index 000000000000..1df9f35e50be
--- /dev/null
+++ b/packages/svelte/tests/css/samples/siblings-combinator-component/Child.svelte
@@ -0,0 +1,5 @@
+
+
+{@render foo()}
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-component/_config.js b/packages/svelte/tests/css/samples/siblings-combinator-component/_config.js
new file mode 100644
index 000000000000..837fa20ae104
--- /dev/null
+++ b/packages/svelte/tests/css/samples/siblings-combinator-component/_config.js
@@ -0,0 +1,20 @@
+import { test } from '../../test';
+
+export default test({
+ warnings: [
+ {
+ code: 'css_unused_selector',
+ message: 'Unused CSS selector "n + m"',
+ end: {
+ character: 468,
+ column: 6,
+ line: 36
+ },
+ start: {
+ character: 463,
+ column: 1,
+ line: 36
+ }
+ }
+ ]
+});
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-component/expected.css b/packages/svelte/tests/css/samples/siblings-combinator-component/expected.css
new file mode 100644
index 000000000000..d2657ccd21df
--- /dev/null
+++ b/packages/svelte/tests/css/samples/siblings-combinator-component/expected.css
@@ -0,0 +1,8 @@
+ x.svelte-xyz + y:where(.svelte-xyz) { color: green; }
+ x.svelte-xyz + v:where(.svelte-xyz) { color: green; }
+ x.svelte-xyz + z:where(.svelte-xyz) { color: green; }
+ y.svelte-xyz + z:where(.svelte-xyz) { color: green; }
+ v.svelte-xyz + z:where(.svelte-xyz) { color: green; }
+ .component + z.svelte-xyz { color: green; }
+
+ /* (unused) n + m { color: red; }*/
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-component/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-component/input.svelte
new file mode 100644
index 000000000000..8d80acffb364
--- /dev/null
+++ b/packages/svelte/tests/css/samples/siblings-combinator-component/input.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ {#snippet foo()}
+
+ {/snippet}
+
+
+
+
+
+
+
+ {#snippet foo()}
+
+
+
+ {/snippet}
+
+
+
+
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-slot/_config.js b/packages/svelte/tests/css/samples/siblings-combinator-slot/_config.js
index 2786baeff80b..8a8f561d014b 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-slot/_config.js
+++ b/packages/svelte/tests/css/samples/siblings-combinator-slot/_config.js
@@ -5,14 +5,8 @@ export default test({
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b + .c"',
- start: { character: 110, column: 1, line: 10 },
- end: { character: 117, column: 8, line: 10 }
- },
- {
- code: 'css_unused_selector',
- message: 'Unused CSS selector ".c + .f"',
- start: { character: 135, column: 1, line: 11 },
- end: { character: 142, column: 8, line: 11 }
+ start: { character: 137, column: 1, line: 11 },
+ end: { character: 144, column: 8, line: 11 }
}
]
});
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-slot/expected.css b/packages/svelte/tests/css/samples/siblings-combinator-slot/expected.css
index 643f6cf13f77..85cbb77e6514 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-slot/expected.css
+++ b/packages/svelte/tests/css/samples/siblings-combinator-slot/expected.css
@@ -1,7 +1,7 @@
.d.svelte-xyz + .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz + .b:where(.svelte-xyz) { color: green; }
+ .c.svelte-xyz + .f:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b + .c { color: red; }*/
- /* (unused) .c + .f { color: red; }*/
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-slot/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-slot/input.svelte
index 1b543f97b713..57e1df1507be 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-slot/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-slot/input.svelte
@@ -5,10 +5,10 @@
From 3682371fb602badc48dc4c006288177d398033c7 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 31 Mar 2025 16:07:19 -0400
Subject: [PATCH 77/97] chore: doc fix (#15651)
---
documentation/docs/98-reference/.generated/client-errors.md | 2 ++
packages/svelte/messages/client-errors/errors.md | 2 ++
2 files changed, 4 insertions(+)
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index 901c49822c00..fd9419176d81 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -151,6 +151,8 @@ This error occurs when state is updated while evaluating a `$derived`. You might
This is forbidden because it introduces instability: if `{count} is even: {even}
` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
```js
+let count = 0;
+// ---cut---
let even = $derived(count % 2 === 0);
let odd = $derived(!even);
```
diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md
index 572930843e78..ca06122cb581 100644
--- a/packages/svelte/messages/client-errors/errors.md
+++ b/packages/svelte/messages/client-errors/errors.md
@@ -107,6 +107,8 @@ This error occurs when state is updated while evaluating a `$derived`. You might
This is forbidden because it introduces instability: if `{count} is even: {even}
` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
```js
+let count = 0;
+// ---cut---
let even = $derived(count % 2 === 0);
let odd = $derived(!even);
```
From 959f593487d749f92fc924964bbe87190b3da4f0 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 1 Apr 2025 14:16:13 +0200
Subject: [PATCH 78/97] fix: ignore generic type arguments while creating AST
(#15659)
* fix: ignore generic type arguments while creating AST
bumps esrap and fixes a resulting error: because esrap now uses a more compliant TSESTree AST, it has `typeArguments` instead of `typeParameters` in some places - those were not deleted while stripping types, which caused an error.
* fix $$render_inner declaration
---
.changeset/happy-snakes-pump.md | 5 +++++
packages/svelte/package.json | 2 +-
.../compiler/phases/1-parse/remove_typescript_nodes.js | 1 +
.../phases/3-transform/server/transform-server.js | 10 ++++------
.../tests/runtime-runes/samples/typescript/main.svelte | 3 ++-
.../_expected/server/index.svelte.js | 2 +-
pnpm-lock.yaml | 10 +++++-----
7 files changed, 19 insertions(+), 14 deletions(-)
create mode 100644 .changeset/happy-snakes-pump.md
diff --git a/.changeset/happy-snakes-pump.md b/.changeset/happy-snakes-pump.md
new file mode 100644
index 000000000000..a5cb3dc30334
--- /dev/null
+++ b/.changeset/happy-snakes-pump.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ignore generic type arguments while creating AST
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index f70ae5cac6c5..1434bb9f069a 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -156,7 +156,7 @@
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"esm-env": "^1.2.1",
- "esrap": "^1.4.3",
+ "esrap": "^1.4.6",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",
diff --git a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js
index 37dc0e17a1ee..4ff6a782b4fb 100644
--- a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js
+++ b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js
@@ -24,6 +24,7 @@ const visitors = {
// until that day comes, we just delete them so they don't confuse esrap
delete n.typeAnnotation;
delete n.typeParameters;
+ delete n.typeArguments;
delete n.returnType;
delete n.accessibility;
},
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
index df3d831d3cc3..f746e90fe24f 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
@@ -186,12 +186,10 @@ export function server_component(analysis, options) {
...snippets,
b.let('$$settled', b.true),
b.let('$$inner_payload'),
- b.stmt(
- b.function(
- b.id('$$render_inner'),
- [b.id('$$payload')],
- b.block(/** @type {Statement[]} */ (rest))
- )
+ b.function_declaration(
+ b.id('$$render_inner'),
+ [b.id('$$payload')],
+ b.block(/** @type {Statement[]} */ (rest))
),
b.do_while(
b.unary('!', b.id('$$settled')),
diff --git a/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte
index d2a9da5439a4..d1b6452df465 100644
--- a/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte
@@ -8,7 +8,7 @@
console.log(this);
}
- function foo(): string {
+ function foo(): string {
return ""!;
}
@@ -46,6 +46,7 @@
const TypedFoo = Foo;
const typeAssertion = true;
+ const x = foo();
diff --git a/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/_config.js b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/_config.js
new file mode 100644
index 000000000000..0d24e265d3ab
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/_config.js
@@ -0,0 +1,7 @@
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, logs }) {
+ assert.deepEqual(logs, [1]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/main.svelte b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/main.svelte
new file mode 100644
index 000000000000..92746760a484
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/main.svelte
@@ -0,0 +1,14 @@
+
+
+{#key key}
+
+{/key}
From c544a5935131817123ffa6b405260f53b21ba6b3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 5 Apr 2025 16:56:35 +0100
Subject: [PATCH 81/97] Version Packages (#15665)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/kind-elephants-behave.md | 5 -----
packages/svelte/CHANGELOG.md | 6 ++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
4 files changed, 8 insertions(+), 7 deletions(-)
delete mode 100644 .changeset/kind-elephants-behave.md
diff --git a/.changeset/kind-elephants-behave.md b/.changeset/kind-elephants-behave.md
deleted file mode 100644
index f52dc1fd36a0..000000000000
--- a/.changeset/kind-elephants-behave.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: ensure clearing of old values happens independent of root flushes
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 15dc5e2234bd..3ac52f5dc759 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.25.7
+
+### Patch Changes
+
+- fix: ensure clearing of old values happens independent of root flushes ([#15664](https://github.com/sveltejs/svelte/pull/15664))
+
## 5.25.6
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index eeb221115156..8abad8a39b92 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.25.6",
+ "version": "5.25.7",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 8bcf91ce395c..2c39c2357fc4 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.25.6';
+export const VERSION = '5.25.7';
export const PUBLIC_VERSION = '5';
From 3d0bc3414920811a7e3fcf065d48b8e15b51bcdf Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Sun, 6 Apr 2025 12:32:03 +0100
Subject: [PATCH 82/97] fix: address untracked_writes memory leak (#15694)
* fix-untracked-writes-leak
* fix
* fix
* Update packages/svelte/src/internal/client/runtime.js
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
---
.changeset/pretty-dingos-unite.md | 5 +++++
packages/svelte/src/internal/client/runtime.js | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
create mode 100644 .changeset/pretty-dingos-unite.md
diff --git a/.changeset/pretty-dingos-unite.md b/.changeset/pretty-dingos-unite.md
new file mode 100644
index 000000000000..57032446707c
--- /dev/null
+++ b/.changeset/pretty-dingos-unite.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: address untracked_writes memory leak
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index a69181da986a..c1c91f35515d 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -476,7 +476,7 @@ export function update_reaction(reaction) {
// we need to increment the read version to ensure that
// any dependencies in this reaction aren't marked with
// the same version
- if (previous_reaction !== null) {
+ if (previous_reaction !== reaction) {
read_version++;
if (untracked_writes !== null) {
From 7b850d3fba8a3edb1f7c028cb80efeae7932c7c2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 7 Apr 2025 15:30:11 +0100
Subject: [PATCH 83/97] Version Packages (#15696)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/pretty-dingos-unite.md | 5 -----
packages/svelte/CHANGELOG.md | 6 ++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
4 files changed, 8 insertions(+), 7 deletions(-)
delete mode 100644 .changeset/pretty-dingos-unite.md
diff --git a/.changeset/pretty-dingos-unite.md b/.changeset/pretty-dingos-unite.md
deleted file mode 100644
index 57032446707c..000000000000
--- a/.changeset/pretty-dingos-unite.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: address untracked_writes memory leak
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 3ac52f5dc759..361de202b11d 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.25.8
+
+### Patch Changes
+
+- fix: address untracked_writes memory leak ([#15694](https://github.com/sveltejs/svelte/pull/15694))
+
## 5.25.7
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 8abad8a39b92..8a225a798d9a 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.25.7",
+ "version": "5.25.8",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 2c39c2357fc4..2c8140e36540 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.25.7';
+export const VERSION = '5.25.8';
export const PUBLIC_VERSION = '5';
From ab4e71a8099d1b7fb699c6ee5027e12313526812 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 7 Apr 2025 15:47:27 -0400
Subject: [PATCH 84/97] chore(deps-dev): bump vite from 5.4.16 to 5.4.17
(#15692)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.16 to 5.4.17.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.17/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.17/packages/vite)
---
updated-dependencies:
- dependency-name: vite
dependency-version: 5.4.17
dependency-type: direct:development
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
playgrounds/sandbox/package.json | 2 +-
pnpm-lock.yaml | 212 +++++++++++++++----------------
2 files changed, 107 insertions(+), 107 deletions(-)
diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json
index cb4490530949..d392349b6057 100644
--- a/playgrounds/sandbox/package.json
+++ b/playgrounds/sandbox/package.json
@@ -18,7 +18,7 @@
"polka": "^1.0.0-next.25",
"svelte": "workspace:*",
"tinyglobby": "^0.2.12",
- "vite": "^5.4.16",
+ "vite": "^5.4.17",
"vite-plugin-inspect": "^0.8.4"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e1f276d23f74..f0e66d168111 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -152,7 +152,7 @@ importers:
devDependencies:
'@sveltejs/vite-plugin-svelte':
specifier: ^4.0.0-next.6
- version: 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ version: 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
polka:
specifier: ^1.0.0-next.25
version: 1.0.0-next.25
@@ -163,11 +163,11 @@ importers:
specifier: ^0.2.12
version: 0.2.12
vite:
- specifier: ^5.4.16
- version: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ specifier: ^5.4.17
+ version: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
vite-plugin-inspect:
specifier: ^0.8.4
- version: 0.8.4(rollup@4.38.0)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ version: 0.8.4(rollup@4.39.0)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
packages:
@@ -551,8 +551,8 @@ packages:
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm-eabi@4.38.0':
- resolution: {integrity: sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==}
+ '@rollup/rollup-android-arm-eabi@4.39.0':
+ resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==}
cpu: [arm]
os: [android]
@@ -561,8 +561,8 @@ packages:
cpu: [arm64]
os: [android]
- '@rollup/rollup-android-arm64@4.38.0':
- resolution: {integrity: sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==}
+ '@rollup/rollup-android-arm64@4.39.0':
+ resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==}
cpu: [arm64]
os: [android]
@@ -571,8 +571,8 @@ packages:
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-arm64@4.38.0':
- resolution: {integrity: sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==}
+ '@rollup/rollup-darwin-arm64@4.39.0':
+ resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==}
cpu: [arm64]
os: [darwin]
@@ -581,18 +581,18 @@ packages:
cpu: [x64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.38.0':
- resolution: {integrity: sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==}
+ '@rollup/rollup-darwin-x64@4.39.0':
+ resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-freebsd-arm64@4.38.0':
- resolution: {integrity: sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==}
+ '@rollup/rollup-freebsd-arm64@4.39.0':
+ resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==}
cpu: [arm64]
os: [freebsd]
- '@rollup/rollup-freebsd-x64@4.38.0':
- resolution: {integrity: sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==}
+ '@rollup/rollup-freebsd-x64@4.39.0':
+ resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==}
cpu: [x64]
os: [freebsd]
@@ -601,8 +601,8 @@ packages:
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-gnueabihf@4.38.0':
- resolution: {integrity: sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.39.0':
+ resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==}
cpu: [arm]
os: [linux]
@@ -611,8 +611,8 @@ packages:
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.38.0':
- resolution: {integrity: sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==}
+ '@rollup/rollup-linux-arm-musleabihf@4.39.0':
+ resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==}
cpu: [arm]
os: [linux]
@@ -621,8 +621,8 @@ packages:
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.38.0':
- resolution: {integrity: sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==}
+ '@rollup/rollup-linux-arm64-gnu@4.39.0':
+ resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==}
cpu: [arm64]
os: [linux]
@@ -631,13 +631,13 @@ packages:
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.38.0':
- resolution: {integrity: sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==}
+ '@rollup/rollup-linux-arm64-musl@4.39.0':
+ resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-loongarch64-gnu@4.38.0':
- resolution: {integrity: sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==}
+ '@rollup/rollup-linux-loongarch64-gnu@4.39.0':
+ resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==}
cpu: [loong64]
os: [linux]
@@ -646,8 +646,8 @@ packages:
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-powerpc64le-gnu@4.38.0':
- resolution: {integrity: sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==}
+ '@rollup/rollup-linux-powerpc64le-gnu@4.39.0':
+ resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==}
cpu: [ppc64]
os: [linux]
@@ -656,13 +656,13 @@ packages:
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.38.0':
- resolution: {integrity: sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==}
+ '@rollup/rollup-linux-riscv64-gnu@4.39.0':
+ resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-riscv64-musl@4.38.0':
- resolution: {integrity: sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==}
+ '@rollup/rollup-linux-riscv64-musl@4.39.0':
+ resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==}
cpu: [riscv64]
os: [linux]
@@ -671,8 +671,8 @@ packages:
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.38.0':
- resolution: {integrity: sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==}
+ '@rollup/rollup-linux-s390x-gnu@4.39.0':
+ resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==}
cpu: [s390x]
os: [linux]
@@ -681,8 +681,8 @@ packages:
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.38.0':
- resolution: {integrity: sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==}
+ '@rollup/rollup-linux-x64-gnu@4.39.0':
+ resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==}
cpu: [x64]
os: [linux]
@@ -691,8 +691,8 @@ packages:
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.38.0':
- resolution: {integrity: sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==}
+ '@rollup/rollup-linux-x64-musl@4.39.0':
+ resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==}
cpu: [x64]
os: [linux]
@@ -701,8 +701,8 @@ packages:
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-arm64-msvc@4.38.0':
- resolution: {integrity: sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==}
+ '@rollup/rollup-win32-arm64-msvc@4.39.0':
+ resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==}
cpu: [arm64]
os: [win32]
@@ -711,8 +711,8 @@ packages:
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.38.0':
- resolution: {integrity: sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==}
+ '@rollup/rollup-win32-ia32-msvc@4.39.0':
+ resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==}
cpu: [ia32]
os: [win32]
@@ -721,8 +721,8 @@ packages:
cpu: [x64]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.38.0':
- resolution: {integrity: sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==}
+ '@rollup/rollup-win32-x64-msvc@4.39.0':
+ resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==}
cpu: [x64]
os: [win32]
@@ -1974,8 +1974,8 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
- rollup@4.38.0:
- resolution: {integrity: sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==}
+ rollup@4.39.0:
+ resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -2310,8 +2310,8 @@ packages:
terser:
optional: true
- vite@5.4.16:
- resolution: {integrity: sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==}
+ vite@5.4.17:
+ resolution: {integrity: sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -2860,120 +2860,120 @@ snapshots:
optionalDependencies:
rollup: 4.22.4
- '@rollup/pluginutils@5.1.0(rollup@4.38.0)':
+ '@rollup/pluginutils@5.1.0(rollup@4.39.0)':
dependencies:
'@types/estree': 1.0.6
estree-walker: 2.0.2
picomatch: 2.3.1
optionalDependencies:
- rollup: 4.38.0
+ rollup: 4.39.0
'@rollup/rollup-android-arm-eabi@4.22.4':
optional: true
- '@rollup/rollup-android-arm-eabi@4.38.0':
+ '@rollup/rollup-android-arm-eabi@4.39.0':
optional: true
'@rollup/rollup-android-arm64@4.22.4':
optional: true
- '@rollup/rollup-android-arm64@4.38.0':
+ '@rollup/rollup-android-arm64@4.39.0':
optional: true
'@rollup/rollup-darwin-arm64@4.22.4':
optional: true
- '@rollup/rollup-darwin-arm64@4.38.0':
+ '@rollup/rollup-darwin-arm64@4.39.0':
optional: true
'@rollup/rollup-darwin-x64@4.22.4':
optional: true
- '@rollup/rollup-darwin-x64@4.38.0':
+ '@rollup/rollup-darwin-x64@4.39.0':
optional: true
- '@rollup/rollup-freebsd-arm64@4.38.0':
+ '@rollup/rollup-freebsd-arm64@4.39.0':
optional: true
- '@rollup/rollup-freebsd-x64@4.38.0':
+ '@rollup/rollup-freebsd-x64@4.39.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.22.4':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.38.0':
+ '@rollup/rollup-linux-arm-gnueabihf@4.39.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.22.4':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.38.0':
+ '@rollup/rollup-linux-arm-musleabihf@4.39.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.22.4':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.38.0':
+ '@rollup/rollup-linux-arm64-gnu@4.39.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.22.4':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.38.0':
+ '@rollup/rollup-linux-arm64-musl@4.39.0':
optional: true
- '@rollup/rollup-linux-loongarch64-gnu@4.38.0':
+ '@rollup/rollup-linux-loongarch64-gnu@4.39.0':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.22.4':
optional: true
- '@rollup/rollup-linux-powerpc64le-gnu@4.38.0':
+ '@rollup/rollup-linux-powerpc64le-gnu@4.39.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.22.4':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.38.0':
+ '@rollup/rollup-linux-riscv64-gnu@4.39.0':
optional: true
- '@rollup/rollup-linux-riscv64-musl@4.38.0':
+ '@rollup/rollup-linux-riscv64-musl@4.39.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.22.4':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.38.0':
+ '@rollup/rollup-linux-s390x-gnu@4.39.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.22.4':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.38.0':
+ '@rollup/rollup-linux-x64-gnu@4.39.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.22.4':
optional: true
- '@rollup/rollup-linux-x64-musl@4.38.0':
+ '@rollup/rollup-linux-x64-musl@4.39.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.22.4':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.38.0':
+ '@rollup/rollup-win32-arm64-msvc@4.39.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.22.4':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.38.0':
+ '@rollup/rollup-win32-ia32-msvc@4.39.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.22.4':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.38.0':
+ '@rollup/rollup-win32-x64-msvc@4.39.0':
optional: true
'@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1)':
@@ -3000,25 +3000,25 @@ snapshots:
typescript: 5.5.4
typescript-eslint: 8.26.0(eslint@9.9.1)(typescript@5.5.4)
- '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
+ '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
dependencies:
- '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
debug: 4.4.0
svelte: link:packages/svelte
- vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
transitivePeerDependencies:
- supports-color
- '@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
+ '@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))':
dependencies:
- '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
debug: 4.4.0
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.17
svelte: link:packages/svelte
- vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
- vitefu: 0.2.5(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
+ vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vitefu: 0.2.5(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))
transitivePeerDependencies:
- supports-color
@@ -4292,30 +4292,30 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.22.4
fsevents: 2.3.3
- rollup@4.38.0:
+ rollup@4.39.0:
dependencies:
'@types/estree': 1.0.7
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.38.0
- '@rollup/rollup-android-arm64': 4.38.0
- '@rollup/rollup-darwin-arm64': 4.38.0
- '@rollup/rollup-darwin-x64': 4.38.0
- '@rollup/rollup-freebsd-arm64': 4.38.0
- '@rollup/rollup-freebsd-x64': 4.38.0
- '@rollup/rollup-linux-arm-gnueabihf': 4.38.0
- '@rollup/rollup-linux-arm-musleabihf': 4.38.0
- '@rollup/rollup-linux-arm64-gnu': 4.38.0
- '@rollup/rollup-linux-arm64-musl': 4.38.0
- '@rollup/rollup-linux-loongarch64-gnu': 4.38.0
- '@rollup/rollup-linux-powerpc64le-gnu': 4.38.0
- '@rollup/rollup-linux-riscv64-gnu': 4.38.0
- '@rollup/rollup-linux-riscv64-musl': 4.38.0
- '@rollup/rollup-linux-s390x-gnu': 4.38.0
- '@rollup/rollup-linux-x64-gnu': 4.38.0
- '@rollup/rollup-linux-x64-musl': 4.38.0
- '@rollup/rollup-win32-arm64-msvc': 4.38.0
- '@rollup/rollup-win32-ia32-msvc': 4.38.0
- '@rollup/rollup-win32-x64-msvc': 4.38.0
+ '@rollup/rollup-android-arm-eabi': 4.39.0
+ '@rollup/rollup-android-arm64': 4.39.0
+ '@rollup/rollup-darwin-arm64': 4.39.0
+ '@rollup/rollup-darwin-x64': 4.39.0
+ '@rollup/rollup-freebsd-arm64': 4.39.0
+ '@rollup/rollup-freebsd-x64': 4.39.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.39.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.39.0
+ '@rollup/rollup-linux-arm64-gnu': 4.39.0
+ '@rollup/rollup-linux-arm64-musl': 4.39.0
+ '@rollup/rollup-linux-loongarch64-gnu': 4.39.0
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.39.0
+ '@rollup/rollup-linux-riscv64-musl': 4.39.0
+ '@rollup/rollup-linux-s390x-gnu': 4.39.0
+ '@rollup/rollup-linux-x64-gnu': 4.39.0
+ '@rollup/rollup-linux-x64-musl': 4.39.0
+ '@rollup/rollup-win32-arm64-msvc': 4.39.0
+ '@rollup/rollup-win32-ia32-msvc': 4.39.0
+ '@rollup/rollup-win32-x64-msvc': 4.39.0
fsevents: 2.3.3
rrweb-cssom@0.7.1: {}
@@ -4562,7 +4562,7 @@ snapshots:
debug: 4.4.0
es-module-lexer: 1.6.0
pathe: 1.1.2
- vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -4574,10 +4574,10 @@ snapshots:
- supports-color
- terser
- vite-plugin-inspect@0.8.4(rollup@4.38.0)(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)):
+ vite-plugin-inspect@0.8.4(rollup@4.39.0)(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)):
dependencies:
'@antfu/utils': 0.7.8
- '@rollup/pluginutils': 5.1.0(rollup@4.38.0)
+ '@rollup/pluginutils': 5.1.0(rollup@4.39.0)
debug: 4.4.0
error-stack-parser-es: 0.1.1
fs-extra: 11.2.0
@@ -4585,7 +4585,7 @@ snapshots:
perfect-debounce: 1.0.0
picocolors: 1.1.1
sirv: 2.0.4
- vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
transitivePeerDependencies:
- rollup
- supports-color
@@ -4602,11 +4602,11 @@ snapshots:
sass: 1.70.0
terser: 5.27.0
- vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0):
+ vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0):
dependencies:
esbuild: 0.21.5
postcss: 8.5.3
- rollup: 4.38.0
+ rollup: 4.39.0
optionalDependencies:
'@types/node': 20.12.7
fsevents: 2.3.3
@@ -4614,9 +4614,9 @@ snapshots:
sass: 1.70.0
terser: 5.27.0
- vitefu@0.2.5(vite@5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)):
+ vitefu@0.2.5(vite@5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)):
optionalDependencies:
- vite: 5.4.16(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
+ vite: 5.4.17(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0):
dependencies:
From 8e21c41c27fd062fed2b9d53ab3f19ac1705142f Mon Sep 17 00:00:00 2001
From: James Scott-Brown
Date: Mon, 7 Apr 2025 21:01:48 +0100
Subject: [PATCH 85/97] Link from $host directive docs to custom elements docs
(#15686)
---
documentation/docs/02-runes/08-$host.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/documentation/docs/02-runes/08-$host.md b/documentation/docs/02-runes/08-$host.md
index 7b5e041e5ed9..ba6f0a5b5b40 100644
--- a/documentation/docs/02-runes/08-$host.md
+++ b/documentation/docs/02-runes/08-$host.md
@@ -2,7 +2,7 @@
title: $host
---
-When compiling a component as a custom element, the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)):
+When compiling a component as a [custom element](custom-elements), the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)):
```svelte
From 6a668c4daf553211a2315f6ef0bfd84c8ef344f0 Mon Sep 17 00:00:00 2001
From: Simon Lund <41208671+simon-lund@users.noreply.github.com>
Date: Mon, 7 Apr 2025 22:33:23 +0200
Subject: [PATCH 86/97] docs:Update 02-context.md (#15700)
* Update 02-context.md
use `const` for key variable and assign it a symbol instead of empty of object.
* Update documentation/docs/06-runtime/02-context.md
---------
Co-authored-by: Rich Harris
---
documentation/docs/06-runtime/02-context.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md
index b698323a04ee..4204bcfe6d6a 100644
--- a/documentation/docs/06-runtime/02-context.md
+++ b/documentation/docs/06-runtime/02-context.md
@@ -94,7 +94,7 @@ interface User {}
// ---cut---
import { getContext, setContext } from 'svelte';
-let key = {};
+const key = {};
/** @param {User} user */
export function setUserContext(user) {
From caf62ee6872fc0c9944aea98585597d2d1712e21 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 8 Apr 2025 07:20:36 -0400
Subject: [PATCH 87/97] fix: allow `$.state` and `$.derived` to be treeshaken
(#15702)
---
.changeset/clever-news-enjoy.md | 5 +++++
packages/svelte/src/internal/client/reactivity/deriveds.js | 1 +
packages/svelte/src/internal/client/reactivity/sources.js | 1 +
3 files changed, 7 insertions(+)
create mode 100644 .changeset/clever-news-enjoy.md
diff --git a/.changeset/clever-news-enjoy.md b/.changeset/clever-news-enjoy.md
new file mode 100644
index 000000000000..2ff3dcbe5668
--- /dev/null
+++ b/.changeset/clever-news-enjoy.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: allow `$.state` and `$.derived` to be treeshaken
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index cd7bbba02f91..86171c2b2df8 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -67,6 +67,7 @@ export function derived(fn) {
* @param {() => V} fn
* @returns {Derived}
*/
+/*#__NO_SIDE_EFFECTS__*/
export function user_derived(fn) {
const d = derived(fn);
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index e4834902fe3f..ceab59b37cbe 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -78,6 +78,7 @@ export function source(v, stack) {
* @param {V} v
* @param {Error | null} [stack]
*/
+/*#__NO_SIDE_EFFECTS__*/
export function state(v, stack) {
const s = source(v, stack);
From 98d14ece6688280f98b185cc9e69fbb309e1afa8 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 8 Apr 2025 22:41:54 +0200
Subject: [PATCH 88/97] fix: rework binding ownership validation (#15678)
* remove old validation
* fix: rework binding ownership validation
Previously we were doing stack-based retrieval of the owner, which while catching more cases was costly (performance-wise) and prone to errors (as shown by many issues over the months).
This drastically simplifies the ownership validation - we now only do simple static analysis to check which props are mutated and wrap them with runtime checks to see if a binding was established.
Besides making the implementation simpler and more performant, this also follows an insight we had over the months: Most people don't really know what to do with this warning when it's shown beyond very simple cases. Either it's not actionable because they don't really know how to fix it or they question if they should at all (in some cases rightfully so). Now that the warning is only shown in simple and easy-to-reason-about cases, it has a much better signal-to-noise-ratio and will hopefully guide people in the right direction early on (learn from the obvious cases to not write spaghetti code in more complex cases).
closes #15532
closes #15210
closes #14893
closes #13607
closes #13139
closes #11861
* remove some now obsolete tests
* fix
* better warnings now that we have more info
* fix
* hoist
* we only care about mutation, not reassignment
* tidy
* handle prop aliases
* mutation validation is only tangentially linked to context requirement
* no need for two vars, one will do
* update warning, include mutation location
* tweak
---------
Co-authored-by: Rich Harris
---
.changeset/sweet-ants-care.md | 5 +
.../.generated/client-warnings.md | 8 +-
.../messages/client-warnings/warnings.md | 6 +-
.../src/compiler/phases/2-analyze/index.js | 1 +
.../3-transform/client/transform-client.js | 6 +
.../phases/3-transform/client/utils.js | 33 ++-
.../client/visitors/AssignmentExpression.js | 7 +-
.../3-transform/client/visitors/ClassBody.js | 27 --
.../client/visitors/UpdateExpression.js | 6 +-
.../client/visitors/shared/component.js | 34 ++-
.../client/visitors/shared/utils.js | 63 ++++-
.../svelte/src/compiler/phases/types.d.ts | 1 +
.../svelte/src/internal/client/constants.js | 1 -
.../svelte/src/internal/client/context.js | 10 -
.../src/internal/client/dev/ownership.js | 247 ++++--------------
packages/svelte/src/internal/client/index.js | 10 +-
packages/svelte/src/internal/client/proxy.js | 81 +-----
.../src/internal/client/reactivity/sources.js | 2 +-
.../svelte/src/internal/client/types.d.ts | 8 -
.../svelte/src/internal/client/warnings.js | 19 +-
packages/svelte/src/utils.js | 6 +-
.../non-local-mutation-discouraged/_config.js | 2 +-
.../non-local-mutation-global-2/_config.js | 11 -
.../non-local-mutation-global-2/child.svelte | 18 --
.../non-local-mutation-global-2/main.svelte | 5 -
.../_config.js | 6 +-
.../main.svelte | 9 +
.../sub.svelte | 0
.../_config.js | 33 +--
.../main.svelte | 11 +-
.../state.svelte.js | 15 +-
.../sub.svelte | 0
.../Child.svelte | 0
.../_config.js | 33 +--
.../main.svelte | 14 +-
.../_config.js | 37 ---
.../main.svelte | 11 -
.../state.svelte.js | 13 -
.../sub.svelte | 8 -
.../_config.js | 24 --
.../main.svelte | 8 -
.../state.svelte.js | 14 -
.../Child.svelte | 9 -
.../_config.js | 37 ---
.../main.svelte | 17 --
.../_config.js | 24 --
.../main.svelte | 13 -
.../Counter.svelte | 7 -
.../main.svelte | 9 -
.../state.svelte.js | 3 -
.../_config.js | 2 +-
.../_config.js | 2 +-
.../_config.js | 1 -
.../CounterBinding.svelte | 7 -
.../CounterContext.svelte | 13 -
.../_config.js | 34 ---
.../main.svelte | 46 ----
57 files changed, 271 insertions(+), 806 deletions(-)
create mode 100644 .changeset/sweet-ants-care.md
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte
rename packages/svelte/tests/runtime-runes/samples/{non-local-mutation-inherited-owner => non-local-mutation-inherited-owner-1}/_config.js (74%)
create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte
rename packages/svelte/tests/runtime-runes/samples/{non-local-mutation-inherited-owner-3 => non-local-mutation-inherited-owner-1}/sub.svelte (100%)
rename packages/svelte/tests/runtime-runes/samples/{non-local-mutation-inherited-owner-5 => non-local-mutation-inherited-owner-2}/sub.svelte (100%)
rename packages/svelte/tests/runtime-runes/samples/{non-local-mutation-inherited-owner-7 => non-local-mutation-inherited-owner-3}/Child.svelte (100%)
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js
delete mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte
diff --git a/.changeset/sweet-ants-care.md b/.changeset/sweet-ants-care.md
new file mode 100644
index 000000000000..b4805626ab4e
--- /dev/null
+++ b/.changeset/sweet-ants-care.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: rework binding ownership validation
diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md
index 284e9a7c3e57..77d1df4cdde2 100644
--- a/documentation/docs/98-reference/.generated/client-warnings.md
+++ b/documentation/docs/98-reference/.generated/client-warnings.md
@@ -161,7 +161,7 @@ Tried to unmount a component that was not mounted
### ownership_invalid_binding
```
-%parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent%
+%parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`)
```
Consider three components `GrandParent`, `Parent` and `Child`. If you do ``, inside `GrandParent` pass on the variable via ` ` (note the missing `bind:`) and then do `` inside `Parent`, this warning is thrown.
@@ -171,11 +171,7 @@ To fix it, `bind:` to the value instead of just passing a property (i.e. in this
### ownership_invalid_mutation
```
-Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
-```
-
-```
-%component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
+Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead
```
Consider the following code:
diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md
index 943cf6f01f4f..f8e9ebd8a047 100644
--- a/packages/svelte/messages/client-warnings/warnings.md
+++ b/packages/svelte/messages/client-warnings/warnings.md
@@ -132,7 +132,7 @@ During development, this error is often preceeded by a `console.error` detailing
## ownership_invalid_binding
-> %parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent%
+> %parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`)
Consider three components `GrandParent`, `Parent` and `Child`. If you do ``, inside `GrandParent` pass on the variable via ` ` (note the missing `bind:`) and then do `` inside `Parent`, this warning is thrown.
@@ -140,9 +140,7 @@ To fix it, `bind:` to the value instead of just passing a property (i.e. in this
## ownership_invalid_mutation
-> Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
-
-> %component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
+> Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead
Consider the following code:
diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index 1f636c32df6d..c62fb03e8fef 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -432,6 +432,7 @@ export function analyze_component(root, source, options) {
uses_component_bindings: false,
uses_render_tags: false,
needs_context: false,
+ needs_mutation_validation: false,
needs_props: false,
event_directive_node: null,
uses_event_attributes: false,
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index 0bdfbae746d0..38fcee8d6fca 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -393,6 +393,12 @@ export function client_component(analysis, options) {
);
}
+ if (analysis.needs_mutation_validation) {
+ component_block.body.unshift(
+ b.var('$$ownership_validator', b.call('$.create_ownership_validator', b.id('$$props')))
+ );
+ }
+
const should_inject_context =
dev ||
analysis.needs_context ||
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index 28e3fabb1990..a37ecd31cc03 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -1,10 +1,10 @@
-/** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Pattern, PrivateIdentifier, Statement } from 'estree' */
-/** @import { AST, Binding } from '#compiler' */
+/** @import { ArrowFunctionExpression, AssignmentExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Node, Pattern, UpdateExpression } from 'estree' */
+/** @import { Binding } from '#compiler' */
/** @import { ClientTransformState, ComponentClientTransformState, ComponentContext } from './types.js' */
/** @import { Analysis } from '../../types.js' */
/** @import { Scope } from '../../scope.js' */
import * as b from '../../../utils/builders.js';
-import { extract_identifiers, is_simple_expression } from '../../../utils/ast.js';
+import { is_simple_expression } from '../../../utils/ast.js';
import {
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
@@ -13,7 +13,8 @@ import {
PROPS_IS_BINDABLE
} from '../../../../constants.js';
import { dev } from '../../../state.js';
-import { get_value } from './visitors/shared/declarations.js';
+import { walk } from 'zimmerframe';
+import { validate_mutation } from './visitors/shared/utils.js';
/**
* @param {Binding} binding
@@ -110,6 +111,30 @@ function get_hoisted_params(node, context) {
}
}
}
+
+ if (dev) {
+ // this is a little hacky, but necessary for ownership validation
+ // to work inside hoisted event handlers
+
+ /**
+ * @param {AssignmentExpression | UpdateExpression} node
+ * @param {{ next: () => void, stop: () => void }} context
+ */
+ function visit(node, { next, stop }) {
+ if (validate_mutation(node, /** @type {any} */ (context), node) !== node) {
+ params.push(b.id('$$ownership_validator'));
+ stop();
+ } else {
+ next();
+ }
+ }
+
+ walk(/** @type {Node} */ (node), null, {
+ AssignmentExpression: visit,
+ UpdateExpression: visit
+ });
+ }
+
return params;
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js
index 850cd83b15b6..4baa1c8e6ce5 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js
@@ -7,9 +7,10 @@ import {
get_attribute_expression,
is_event_attribute
} from '../../../../utils/ast.js';
-import { dev, is_ignored, locate_node } from '../../../../state.js';
+import { dev, locate_node } from '../../../../state.js';
import { should_proxy } from '../utils.js';
import { visit_assignment_expression } from '../../shared/assignments.js';
+import { validate_mutation } from './shared/utils.js';
/**
* @param {AssignmentExpression} node
@@ -20,9 +21,7 @@ export function AssignmentExpression(node, context) {
visit_assignment_expression(node, context, build_assignment) ?? context.next()
);
- return is_ignored(node, 'ownership_invalid_mutation')
- ? b.call('$.skip_ownership_validation', b.thunk(expression))
- : expression;
+ return validate_mutation(node, context, expression);
}
/**
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
index 24c20d7f947f..efc3c95c3c34 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js
@@ -161,33 +161,6 @@ export function ClassBody(node, context) {
body.push(/** @type {MethodDefinition} **/ (context.visit(definition, child_state)));
}
- if (dev && public_state.size > 0) {
- // add an `[$.ADD_OWNER]` method so that a class with state fields can widen ownership
- body.push(
- b.method(
- 'method',
- b.id('$.ADD_OWNER'),
- [b.id('owner')],
- [
- b.stmt(
- b.call(
- '$.add_owner_to_class',
- b.this,
- b.id('owner'),
- b.array(
- Array.from(public_state).map(([name]) =>
- b.thunk(b.call('$.get', b.member(b.this, b.private_id(name))))
- )
- ),
- is_ignored(node, 'ownership_invalid_binding') && b.true
- )
- )
- ],
- true
- )
- );
- }
-
return { ...node, body };
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js
index 13c1b4bc51e1..63c03b0eb6f2 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js
@@ -1,8 +1,8 @@
/** @import { AssignmentExpression, Expression, UpdateExpression } from 'estree' */
/** @import { Context } from '../types' */
-import { is_ignored } from '../../../../state.js';
import { object } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js';
+import { validate_mutation } from './shared/utils.js';
/**
* @param {UpdateExpression} node
@@ -51,7 +51,5 @@ export function UpdateExpression(node, context) {
);
}
- return is_ignored(node, 'ownership_invalid_mutation')
- ? b.call('$.skip_ownership_validation', b.thunk(update))
- : update;
+ return validate_mutation(node, context, update);
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
index 2bae4486dc58..2ea68e206e2e 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
@@ -179,19 +179,29 @@ export function build_component(node, component_name, context, anchor = context.
} else if (attribute.type === 'BindDirective') {
const expression = /** @type {Expression} */ (context.visit(attribute.expression));
- if (dev && attribute.name !== 'this') {
- binding_initializers.push(
- b.stmt(
- b.call(
- b.id('$.add_owner_effect'),
- expression.type === 'SequenceExpression'
- ? expression.expressions[0]
- : b.thunk(expression),
- b.id(component_name),
- is_ignored(node, 'ownership_invalid_binding') && b.true
+ if (
+ dev &&
+ attribute.name !== 'this' &&
+ !is_ignored(node, 'ownership_invalid_binding') &&
+ // bind:x={() => x.y, y => x.y = y} will be handled by the assignment expression binding validation
+ attribute.expression.type !== 'SequenceExpression'
+ ) {
+ const left = object(attribute.expression);
+ const binding = left && context.state.scope.get(left.name);
+
+ if (binding?.kind === 'bindable_prop' || binding?.kind === 'prop') {
+ context.state.analysis.needs_mutation_validation = true;
+ binding_initializers.push(
+ b.stmt(
+ b.call(
+ '$$ownership_validator.binding',
+ b.literal(binding.node.name),
+ b.id(component_name),
+ b.thunk(expression)
+ )
)
- )
- );
+ );
+ }
}
if (expression.type === 'SequenceExpression') {
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
index df6308d6316a..af6e56f70c81 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
@@ -1,13 +1,13 @@
-/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */
+/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression } from 'estree' */
/** @import { AST, ExpressionMetadata } from '#compiler' */
-/** @import { ComponentClientTransformState } from '../../types' */
+/** @import { ComponentClientTransformState, Context } from '../../types' */
import { walk } from 'zimmerframe';
import { object } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js';
import { regex_is_valid_identifier } from '../../../../patterns.js';
import is_reference from 'is-reference';
-import { locator } from '../../../../../state.js';
+import { dev, is_ignored, locator } from '../../../../../state.js';
import { create_derived } from '../../utils.js';
/**
@@ -295,3 +295,60 @@ export function validate_binding(state, binding, expression) {
)
);
}
+
+/**
+ * In dev mode validate mutations to props
+ * @param {AssignmentExpression | UpdateExpression} node
+ * @param {Context} context
+ * @param {Expression} expression
+ */
+export function validate_mutation(node, context, expression) {
+ let left = /** @type {Expression | Super} */ (
+ node.type === 'AssignmentExpression' ? node.left : node.argument
+ );
+
+ if (!dev || left.type !== 'MemberExpression' || is_ignored(node, 'ownership_invalid_mutation')) {
+ return expression;
+ }
+
+ const name = object(left);
+ if (!name) return expression;
+
+ const binding = context.state.scope.get(name.name);
+ if (binding?.kind !== 'prop' && binding?.kind !== 'bindable_prop') return expression;
+
+ const state = /** @type {ComponentClientTransformState} */ (context.state);
+ state.analysis.needs_mutation_validation = true;
+
+ /** @type {Array} */
+ const path = [];
+
+ while (left.type === 'MemberExpression') {
+ if (left.property.type === 'Literal') {
+ path.unshift(left.property);
+ } else if (left.property.type === 'Identifier') {
+ if (left.computed) {
+ path.unshift(left.property);
+ } else {
+ path.unshift(b.literal(left.property.name));
+ }
+ } else {
+ return expression;
+ }
+
+ left = left.object;
+ }
+
+ path.unshift(b.literal(name.name));
+
+ const loc = locator(/** @type {number} */ (left.start));
+
+ return b.call(
+ '$$ownership_validator.mutation',
+ b.literal(binding.prop_alias),
+ b.array(path),
+ expression,
+ loc && b.literal(loc.line),
+ loc && b.literal(loc.column)
+ );
+}
diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts
index abe2b115de02..f09b88130305 100644
--- a/packages/svelte/src/compiler/phases/types.d.ts
+++ b/packages/svelte/src/compiler/phases/types.d.ts
@@ -53,6 +53,7 @@ export interface ComponentAnalysis extends Analysis {
uses_component_bindings: boolean;
uses_render_tags: boolean;
needs_context: boolean;
+ needs_mutation_validation: boolean;
needs_props: boolean;
/** Set to the first event directive (on:x) found on a DOM element in the code */
event_directive_node: AST.OnDirective | null;
diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js
index 21377c1cc85f..7e5196c606b4 100644
--- a/packages/svelte/src/internal/client/constants.js
+++ b/packages/svelte/src/internal/client/constants.js
@@ -23,6 +23,5 @@ export const EFFECT_HAS_DERIVED = 1 << 20;
export const EFFECT_IS_UPDATING = 1 << 21;
export const STATE_SYMBOL = Symbol('$state');
-export const STATE_SYMBOL_METADATA = Symbol('$state metadata');
export const LEGACY_PROPS = Symbol('legacy props');
export const LOADING_ATTR_SYMBOL = Symbol('');
diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js
index bfca9d5e6a72..7a2fdd0edb6d 100644
--- a/packages/svelte/src/internal/client/context.js
+++ b/packages/svelte/src/internal/client/context.js
@@ -1,7 +1,6 @@
/** @import { ComponentContext } from '#client' */
import { DEV } from 'esm-env';
-import { add_owner } from './dev/ownership.js';
import { lifecycle_outside_component } from '../shared/errors.js';
import { source } from './reactivity/sources.js';
import {
@@ -67,15 +66,6 @@ export function getContext(key) {
*/
export function setContext(key, context) {
const context_map = get_or_init_context_map('setContext');
-
- if (DEV) {
- // When state is put into context, we treat as if it's global from now on.
- // We do for performance reasons (it's for example very expensive to call
- // getContext on a big object many times when part of a list component)
- // and danger of false positives.
- untrack(() => add_owner(context, null, true));
- }
-
context_map.set(key, context);
return context;
}
diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js
index 62119b36dbd6..6c40a744dfc5 100644
--- a/packages/svelte/src/internal/client/dev/ownership.js
+++ b/packages/svelte/src/internal/client/dev/ownership.js
@@ -1,12 +1,11 @@
-/** @import { ProxyMetadata } from '#client' */
/** @typedef {{ file: string, line: number, column: number }} Location */
-import { STATE_SYMBOL_METADATA } from '../constants.js';
-import { render_effect, user_pre_effect } from '../reactivity/effects.js';
-import { dev_current_component_function } from '../context.js';
-import { get_prototype_of } from '../../shared/utils.js';
+import { get_descriptor } from '../../shared/utils.js';
+import { LEGACY_PROPS, STATE_SYMBOL } from '../constants.js';
+import { FILENAME } from '../../../constants.js';
+import { component_context } from '../context.js';
import * as w from '../warnings.js';
-import { FILENAME, UNINITIALIZED } from '../../../constants.js';
+import { sanitize_location } from '../../../utils.js';
/** @type {Record>} */
const boundaries = {};
@@ -71,8 +70,6 @@ export function get_component() {
return null;
}
-export const ADD_OWNER = Symbol('ADD_OWNER');
-
/**
* Together with `mark_module_end`, this function establishes the boundaries of a `.svelte` file,
* such that subsequent calls to `get_component` can tell us which component is responsible
@@ -108,197 +105,69 @@ export function mark_module_end(component) {
}
/**
- * @param {any} object
- * @param {any | null} owner
- * @param {boolean} [global]
- * @param {boolean} [skip_warning]
+ * Sets up a validator that
+ * - traverses the path of a prop to find out if it is allowed to be mutated
+ * - checks that the binding chain is not interrupted
+ * @param {Record} props
*/
-export function add_owner(object, owner, global = false, skip_warning = false) {
- if (object && !global) {
- const component = dev_current_component_function;
- const metadata = object[STATE_SYMBOL_METADATA];
- if (metadata && !has_owner(metadata, component)) {
- let original = get_owner(metadata);
-
- if (owner && owner[FILENAME] !== component[FILENAME] && !skip_warning) {
- w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]);
+export function create_ownership_validator(props) {
+ const component = component_context?.function;
+ const parent = component_context?.p?.function;
+
+ return {
+ /**
+ * @param {string} prop
+ * @param {any[]} path
+ * @param {any} result
+ * @param {number} line
+ * @param {number} column
+ */
+ mutation: (prop, path, result, line, column) => {
+ const name = path[0];
+ if (is_bound(props, name) || !parent) {
+ return result;
}
- }
- }
- add_owner_to_object(object, owner, new Set());
-}
-
-/**
- * @param {() => unknown} get_object
- * @param {any} Component
- * @param {boolean} [skip_warning]
- */
-export function add_owner_effect(get_object, Component, skip_warning = false) {
- user_pre_effect(() => {
- add_owner(get_object(), Component, false, skip_warning);
- });
-}
-
-/**
- * @param {any} _this
- * @param {Function} owner
- * @param {Array<() => any>} getters
- * @param {boolean} skip_warning
- */
-export function add_owner_to_class(_this, owner, getters, skip_warning) {
- _this[ADD_OWNER].current ||= getters.map(() => UNINITIALIZED);
-
- for (let i = 0; i < getters.length; i += 1) {
- const current = getters[i]();
- // For performance reasons we only re-add the owner if the state has changed
- if (current !== _this[ADD_OWNER][i]) {
- _this[ADD_OWNER].current[i] = current;
- add_owner(current, owner, false, skip_warning);
- }
- }
-}
-
-/**
- * @param {ProxyMetadata | null} from
- * @param {ProxyMetadata} to
- */
-export function widen_ownership(from, to) {
- if (to.owners === null) {
- return;
- }
-
- while (from) {
- if (from.owners === null) {
- to.owners = null;
- break;
- }
-
- for (const owner of from.owners) {
- to.owners.add(owner);
- }
-
- from = from.parent;
- }
-}
-
-/**
- * @param {any} object
- * @param {Function | null} owner If `null`, then the object is globally owned and will not be checked
- * @param {Set} seen
- */
-function add_owner_to_object(object, owner, seen) {
- const metadata = /** @type {ProxyMetadata} */ (object?.[STATE_SYMBOL_METADATA]);
+ let value = props[name];
- if (metadata) {
- // this is a state proxy, add owner directly, if not globally shared
- if ('owners' in metadata && metadata.owners != null) {
- if (owner) {
- metadata.owners.add(owner);
- } else {
- metadata.owners = null;
+ for (let i = 1; i < path.length - 1; i++) {
+ if (!value?.[STATE_SYMBOL]) {
+ return result;
+ }
+ value = value[path[i]];
}
- }
- } else if (object && typeof object === 'object') {
- if (seen.has(object)) return;
- seen.add(object);
- if (ADD_OWNER in object && object[ADD_OWNER]) {
- // this is a class with state fields. we put this in a render effect
- // so that if state is replaced (e.g. `instance.name = { first, last }`)
- // the new state is also co-owned by the caller of `getContext`
- render_effect(() => {
- object[ADD_OWNER](owner);
- });
- } else {
- var proto = get_prototype_of(object);
- if (proto === Object.prototype) {
- // recurse until we find a state proxy
- for (const key in object) {
- if (Object.getOwnPropertyDescriptor(object, key)?.get) {
- // Similar to the class case; the getter could update with a new state
- let current = UNINITIALIZED;
- render_effect(() => {
- const next = object[key];
- if (current !== next) {
- current = next;
- add_owner_to_object(next, owner, seen);
- }
- });
- } else {
- add_owner_to_object(object[key], owner, seen);
- }
- }
- } else if (proto === Array.prototype) {
- // recurse until we find a state proxy
- for (let i = 0; i < object.length; i += 1) {
- add_owner_to_object(object[i], owner, seen);
- }
+ const location = sanitize_location(`${component[FILENAME]}:${line}:${column}`);
+
+ w.ownership_invalid_mutation(name, location, prop, parent[FILENAME]);
+
+ return result;
+ },
+ /**
+ * @param {any} key
+ * @param {any} child_component
+ * @param {() => any} value
+ */
+ binding: (key, child_component, value) => {
+ if (!is_bound(props, key) && parent && value()?.[STATE_SYMBOL]) {
+ w.ownership_invalid_binding(
+ component[FILENAME],
+ key,
+ child_component[FILENAME],
+ parent[FILENAME]
+ );
}
}
- }
+ };
}
/**
- * @param {ProxyMetadata} metadata
- * @param {Function} component
- * @returns {boolean}
+ * @param {Record} props
+ * @param {string} prop_name
*/
-function has_owner(metadata, component) {
- if (metadata.owners === null) {
- return true;
- }
-
- return (
- metadata.owners.has(component) ||
- // This helps avoid false positives when using HMR, where the component function is replaced
- (FILENAME in component &&
- [...metadata.owners].some(
- (owner) => /** @type {any} */ (owner)[FILENAME] === component[FILENAME]
- )) ||
- (metadata.parent !== null && has_owner(metadata.parent, component))
- );
-}
-
-/**
- * @param {ProxyMetadata} metadata
- * @returns {any}
- */
-function get_owner(metadata) {
- return (
- metadata?.owners?.values().next().value ??
- get_owner(/** @type {ProxyMetadata} */ (metadata.parent))
- );
-}
-
-let skip = false;
-
-/**
- * @param {() => any} fn
- */
-export function skip_ownership_validation(fn) {
- skip = true;
- fn();
- skip = false;
-}
-
-/**
- * @param {ProxyMetadata} metadata
- */
-export function check_ownership(metadata) {
- if (skip) return;
-
- const component = get_component();
-
- if (component && !has_owner(metadata, component)) {
- let original = get_owner(metadata);
-
- // @ts-expect-error
- if (original[FILENAME] !== component[FILENAME]) {
- // @ts-expect-error
- w.ownership_invalid_mutation(component[FILENAME], original[FILENAME]);
- } else {
- w.ownership_invalid_mutation();
- }
- }
+function is_bound(props, prop_name) {
+ // Can be the case when someone does `mount(Component, props)` with `let props = $state({...})`
+ // or `createClassComponent(Component, props)`
+ const is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props;
+ return !!get_descriptor(props, prop_name)?.set || (is_entry_props && prop_name in props);
}
diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js
index a5f93e8b171b..7eed8a744afa 100644
--- a/packages/svelte/src/internal/client/index.js
+++ b/packages/svelte/src/internal/client/index.js
@@ -4,15 +4,7 @@ export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js';
export { cleanup_styles } from './dev/css.js';
export { add_locations } from './dev/elements.js';
export { hmr } from './dev/hmr.js';
-export {
- ADD_OWNER,
- add_owner,
- mark_module_start,
- mark_module_end,
- add_owner_effect,
- add_owner_to_class,
- skip_ownership_validation
-} from './dev/ownership.js';
+export { mark_module_start, mark_module_end, create_ownership_validator } from './dev/ownership.js';
export { check_target, legacy_api } from './dev/legacy.js';
export { trace } from './dev/tracing.js';
export { inspect } from './dev/inspect.js';
diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js
index fab271c91652..5e0aa3dbc35f 100644
--- a/packages/svelte/src/internal/client/proxy.js
+++ b/packages/svelte/src/internal/client/proxy.js
@@ -1,7 +1,6 @@
-/** @import { ProxyMetadata, Source } from '#client' */
+/** @import { Source } from '#client' */
import { DEV } from 'esm-env';
import { get, active_effect, active_reaction, set_active_reaction } from './runtime.js';
-import { component_context } from './context.js';
import {
array_prototype,
get_descriptor,
@@ -9,24 +8,19 @@ import {
is_array,
object_prototype
} from '../shared/utils.js';
-import { check_ownership, widen_ownership } from './dev/ownership.js';
import { state as source, set } from './reactivity/sources.js';
-import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js';
+import { STATE_SYMBOL } from './constants.js';
import { UNINITIALIZED } from '../../constants.js';
import * as e from './errors.js';
import { get_stack } from './dev/tracing.js';
import { tracing_mode_flag } from '../flags/index.js';
-/** @type {ProxyMetadata | null} */
-var parent_metadata = null;
-
/**
* @template T
* @param {T} value
- * @param {Source} [prev] dev mode only
* @returns {T}
*/
-export function proxy(value, prev) {
+export function proxy(value) {
// if non-proxyable, or is already a proxy, return `value`
if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) {
return value;
@@ -55,16 +49,7 @@ export function proxy(value, prev) {
set_active_reaction(reaction);
/** @type {T} */
- var result;
-
- if (DEV) {
- var previous_metadata = parent_metadata;
- parent_metadata = metadata;
- result = fn();
- parent_metadata = previous_metadata;
- } else {
- result = fn();
- }
+ var result = fn();
set_active_reaction(previous_reaction);
return result;
@@ -76,31 +61,6 @@ export function proxy(value, prev) {
sources.set('length', source(/** @type {any[]} */ (value).length, stack));
}
- /** @type {ProxyMetadata} */
- var metadata;
-
- if (DEV) {
- metadata = {
- parent: parent_metadata,
- owners: null
- };
-
- if (prev) {
- // Reuse owners from previous state; necessary because reassignment is not guaranteed to have correct component context.
- // If no previous proxy exists we play it safe and assume ownerless state
- // @ts-expect-error
- const prev_owners = prev.v?.[STATE_SYMBOL_METADATA]?.owners;
- metadata.owners = prev_owners ? new Set(prev_owners) : null;
- } else {
- metadata.owners =
- parent_metadata === null
- ? component_context !== null
- ? new Set([component_context.function])
- : null
- : new Set();
- }
- }
-
return new Proxy(/** @type {any} */ (value), {
defineProperty(_, prop, descriptor) {
if (
@@ -160,10 +120,6 @@ export function proxy(value, prev) {
},
get(target, prop, receiver) {
- if (DEV && prop === STATE_SYMBOL_METADATA) {
- return metadata;
- }
-
if (prop === STATE_SYMBOL) {
return value;
}
@@ -179,22 +135,6 @@ export function proxy(value, prev) {
if (s !== undefined) {
var v = get(s);
-
- // In case of something like `foo = bar.map(...)`, foo would have ownership
- // of the array itself, while the individual items would have ownership
- // of the component that created bar. That means if we later do `foo[0].baz = 42`,
- // we could get a false-positive ownership violation, since the two proxies
- // are not connected to each other via the parent metadata relationship.
- // For this reason, we need to widen the ownership of the children
- // upon access when we detect they are not connected.
- if (DEV) {
- /** @type {ProxyMetadata | undefined} */
- var prop_metadata = v?.[STATE_SYMBOL_METADATA];
- if (prop_metadata && prop_metadata?.parent !== metadata) {
- widen_ownership(metadata, prop_metadata);
- }
- }
-
return v === UNINITIALIZED ? undefined : v;
}
@@ -225,10 +165,6 @@ export function proxy(value, prev) {
},
has(target, prop) {
- if (DEV && prop === STATE_SYMBOL_METADATA) {
- return true;
- }
-
if (prop === STATE_SYMBOL) {
return true;
}
@@ -295,15 +231,6 @@ export function proxy(value, prev) {
);
}
- if (DEV) {
- /** @type {ProxyMetadata | undefined} */
- var prop_metadata = value?.[STATE_SYMBOL_METADATA];
- if (prop_metadata && prop_metadata?.parent !== metadata) {
- widen_ownership(metadata, prop_metadata);
- }
- check_ownership(metadata);
- }
-
var descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
// Set the new value before updating any signals so that any listeners get the new value
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index ceab59b37cbe..2361762519fc 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -140,7 +140,7 @@ export function set(source, value, should_proxy = false) {
e.state_unsafe_mutation();
}
- let new_value = should_proxy ? proxy(value, source) : value;
+ let new_value = should_proxy ? proxy(value) : value;
return internal_set(source, new_value);
}
diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts
index 0c260a0a9ffa..b46bdf201341 100644
--- a/packages/svelte/src/internal/client/types.d.ts
+++ b/packages/svelte/src/internal/client/types.d.ts
@@ -179,14 +179,6 @@ export type TaskCallback = (now: number) => boolean | void;
export type TaskEntry = { c: TaskCallback; f: () => void };
-/** Dev-only */
-export interface ProxyMetadata {
- /** The components that 'own' this state, if any. `null` means no owners, i.e. everyone can mutate this state. */
- owners: null | Set;
- /** The parent metadata object */
- parent: null | ProxyMetadata;
-}
-
export type ProxyStateObject> = T & {
[STATE_SYMBOL]: T;
};
diff --git a/packages/svelte/src/internal/client/warnings.js b/packages/svelte/src/internal/client/warnings.js
index 250c6eca2fe9..c84b487e280d 100644
--- a/packages/svelte/src/internal/client/warnings.js
+++ b/packages/svelte/src/internal/client/warnings.js
@@ -129,27 +129,30 @@ export function lifecycle_double_unmount() {
}
/**
- * %parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent%
+ * %parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`)
* @param {string} parent
+ * @param {string} prop
* @param {string} child
* @param {string} owner
*/
-export function ownership_invalid_binding(parent, child, owner) {
+export function ownership_invalid_binding(parent, prop, child, owner) {
if (DEV) {
- console.warn(`%c[svelte] ownership_invalid_binding\n%c${parent} passed a value to ${child} with \`bind:\`, but the value is owned by ${owner}. Consider creating a binding between ${owner} and ${parent}\nhttps://svelte.dev/e/ownership_invalid_binding`, bold, normal);
+ console.warn(`%c[svelte] ownership_invalid_binding\n%c${parent} passed property \`${prop}\` to ${child} with \`bind:\`, but its parent component ${owner} did not declare \`${prop}\` as a binding. Consider creating a binding between ${owner} and ${parent} (e.g. \`bind:${prop}={...}\` instead of \`${prop}={...}\`)\nhttps://svelte.dev/e/ownership_invalid_binding`, bold, normal);
} else {
console.warn(`https://svelte.dev/e/ownership_invalid_binding`);
}
}
/**
- * %component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
- * @param {string | undefined | null} [component]
- * @param {string | undefined | null} [owner]
+ * Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead
+ * @param {string} name
+ * @param {string} location
+ * @param {string} prop
+ * @param {string} parent
*/
-export function ownership_invalid_mutation(component, owner) {
+export function ownership_invalid_mutation(name, location, prop, parent) {
if (DEV) {
- console.warn(`%c[svelte] ownership_invalid_mutation\n%c${component ? `${component} mutated a value owned by ${owner}. This is strongly discouraged. Consider passing values to child components with \`bind:\`, or use a callback instead` : 'Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead'}\nhttps://svelte.dev/e/ownership_invalid_mutation`, bold, normal);
+ console.warn(`%c[svelte] ownership_invalid_mutation\n%cMutating unbound props (\`${name}\`, at ${location}) is strongly discouraged. Consider using \`bind:${prop}={...}\` in ${parent} (or using a callback) instead\nhttps://svelte.dev/e/ownership_invalid_mutation`, bold, normal);
} else {
console.warn(`https://svelte.dev/e/ownership_invalid_mutation`);
}
diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js
index d4d106d56deb..ada318e85ac7 100644
--- a/packages/svelte/src/utils.js
+++ b/packages/svelte/src/utils.js
@@ -465,8 +465,10 @@ export function is_raw_text_element(name) {
/**
* Prevent devtools trying to make `location` a clickable link by inserting a zero-width space
- * @param {string | undefined} location
+ * @template {string | undefined} T
+ * @param {T} location
+ * @returns {T};
*/
export function sanitize_location(location) {
- return location?.replace(/\//g, '/\u200b');
+ return /** @type {T} */ (location?.replace(/\//g, '/\u200b'));
}
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js
index 62c696124285..84526610260d 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js
@@ -10,7 +10,7 @@ export default test({
test({ assert, target, warnings }) {
const warning =
- 'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead';
+ 'Mutating unbound props (`object`, at Counter.svelte:5:23) is strongly discouraged. Consider using `bind:object={...}` in main.svelte (or using a callback) instead';
const [btn1, btn2] = target.querySelectorAll('button');
btn1.click();
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js
deleted file mode 100644
index b4864154c39a..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { test } from '../../test';
-
-export default test({
- compileOptions: {
- dev: true
- },
-
- async test({ assert, warnings }) {
- assert.deepEqual(warnings, []);
- }
-});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte
deleted file mode 100644
index 13de75364752..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte
+++ /dev/null
@@ -1,18 +0,0 @@
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte
deleted file mode 100644
index 8a6922e9e250..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js
similarity index 74%
rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js
rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js
index c07b9ce129dc..96b18d1854c8 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js
@@ -8,7 +8,7 @@ let warn;
let warnings = [];
export default test({
- html: `clicks: 0 `,
+ html: `[] `,
compileOptions: {
dev: true
@@ -34,8 +34,8 @@ export default test({
btn?.click();
});
- assert.htmlEqual(target.innerHTML, `clicks: 1 `);
+ assert.htmlEqual(target.innerHTML, `[foo] `);
- assert.deepEqual(warnings, []);
+ assert.deepEqual(warnings, [], 'expected getContext to have widened ownership');
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte
new file mode 100644
index 000000000000..2dd7cab141d6
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/sub.svelte
similarity index 100%
rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/sub.svelte
rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/sub.svelte
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js
index c07b9ce129dc..66f1726a2aef 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js
@@ -1,41 +1,24 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
-/** @type {typeof console.warn} */
-let warn;
-
-/** @type {any[]} */
-let warnings = [];
-
export default test({
- html: `clicks: 0 `,
-
compileOptions: {
dev: true
},
- before_test: () => {
- warn = console.warn;
-
- console.warn = (...args) => {
- warnings.push(...args);
- };
- },
+ test({ assert, target, warnings }) {
+ const [btn1, btn2] = target.querySelectorAll('button');
- after_test: () => {
- console.warn = warn;
- warnings = [];
- },
+ flushSync(() => {
+ btn1.click();
+ });
- test({ assert, target }) {
- const btn = target.querySelector('button');
+ assert.deepEqual(warnings.length, 0);
flushSync(() => {
- btn?.click();
+ btn2.click();
});
- assert.htmlEqual(target.innerHTML, `clicks: 1 `);
-
- assert.deepEqual(warnings, []);
+ assert.deepEqual(warnings.length, 1);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte
index ad450a937e40..0be7e434e475 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte
@@ -1,9 +1,8 @@
-
- global.a.b += 1}>
- clicks: {global.a.b}
-
+
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js
index 3e7a68cf97d8..2906b9bce52b 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js
@@ -1 +1,14 @@
-export let global = $state({});
+export function create_my_state() {
+ const my_state = $state({
+ a: 0
+ });
+
+ function inc() {
+ my_state.a++;
+ }
+
+ return {
+ my_state,
+ inc
+ };
+}
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/sub.svelte
similarity index 100%
rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/sub.svelte
rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/sub.svelte
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/Child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/Child.svelte
similarity index 100%
rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/Child.svelte
rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/Child.svelte
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js
index 96b18d1854c8..ab7327ab8b82 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js
@@ -1,41 +1,24 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
-/** @type {typeof console.warn} */
-let warn;
-
-/** @type {any[]} */
-let warnings = [];
-
export default test({
- html: `[] `,
-
compileOptions: {
dev: true
},
- before_test: () => {
- warn = console.warn;
-
- console.warn = (...args) => {
- warnings.push(...args);
- };
- },
+ async test({ assert, target, warnings }) {
+ const [btn1, btn2] = target.querySelectorAll('button');
- after_test: () => {
- console.warn = warn;
- warnings = [];
- },
+ flushSync(() => {
+ btn1.click();
+ });
- test({ assert, target }) {
- const btn = target.querySelector('button');
+ assert.deepEqual(warnings.length, 0);
flushSync(() => {
- btn?.click();
+ btn2.click();
});
- assert.htmlEqual(target.innerHTML, `[foo] `);
-
- assert.deepEqual(warnings, [], 'expected getContext to have widened ownership');
+ assert.deepEqual(warnings.length, 1);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte
index 2dd7cab141d6..8e8343790b1b 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte
@@ -1,9 +1,13 @@
-
+First click here
+
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js
deleted file mode 100644
index aeb3740dfe71..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { flushSync } from 'svelte';
-import { test } from '../../test';
-
-/** @type {typeof console.warn} */
-let warn;
-
-/** @type {any[]} */
-let warnings = [];
-
-export default test({
- compileOptions: {
- dev: true
- },
-
- before_test: () => {
- warn = console.warn;
-
- console.warn = (...args) => {
- warnings.push(...args);
- };
- },
-
- after_test: () => {
- console.warn = warn;
- warnings = [];
- },
-
- test({ assert, target }) {
- const btn = target.querySelector('button');
-
- flushSync(() => {
- btn?.click();
- });
-
- assert.deepEqual(warnings, []);
- }
-});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte
deleted file mode 100644
index 2d40c13949a6..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- global.increment_a_b()}>
- click me
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js
deleted file mode 100644
index 40790591712c..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js
+++ /dev/null
@@ -1,13 +0,0 @@
-class Global {
- state = $state({});
-
- add_a(a) {
- this.state.a = a;
- }
-
- increment_a_b() {
- this.state.a.b++;
- }
-}
-
-export const global = new Global();
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte
deleted file mode 100644
index 044904aa187e..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js
deleted file mode 100644
index 66f1726a2aef..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { flushSync } from 'svelte';
-import { test } from '../../test';
-
-export default test({
- compileOptions: {
- dev: true
- },
-
- test({ assert, target, warnings }) {
- const [btn1, btn2] = target.querySelectorAll('button');
-
- flushSync(() => {
- btn1.click();
- });
-
- assert.deepEqual(warnings.length, 0);
-
- flushSync(() => {
- btn2.click();
- });
-
- assert.deepEqual(warnings.length, 1);
- }
-});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte
deleted file mode 100644
index 0be7e434e475..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js
deleted file mode 100644
index 2906b9bce52b..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export function create_my_state() {
- const my_state = $state({
- a: 0
- });
-
- function inc() {
- my_state.a++;
- }
-
- return {
- my_state,
- inc
- };
-}
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte
deleted file mode 100644
index aa31fd7606dd..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- {
- foo.person.name.last = 'T';
-}}>mutate
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js
deleted file mode 100644
index cc9ea715f0aa..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { flushSync } from 'svelte';
-import { test } from '../../test';
-
-/** @type {typeof console.warn} */
-let warn;
-
-/** @type {any[]} */
-let warnings = [];
-
-export default test({
- compileOptions: {
- dev: true
- },
-
- before_test: () => {
- warn = console.warn;
-
- console.warn = (...args) => {
- warnings.push(...args);
- };
- },
-
- after_test: () => {
- console.warn = warn;
- warnings = [];
- },
-
- async test({ assert, target }) {
- const btn = target.querySelector('button');
-
- flushSync(() => {
- btn?.click();
- });
-
- assert.deepEqual(warnings.length, 0);
- }
-});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte
deleted file mode 100644
index 92d7dbd2db6c..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js
deleted file mode 100644
index ab7327ab8b82..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { flushSync } from 'svelte';
-import { test } from '../../test';
-
-export default test({
- compileOptions: {
- dev: true
- },
-
- async test({ assert, target, warnings }) {
- const [btn1, btn2] = target.querySelectorAll('button');
-
- flushSync(() => {
- btn1.click();
- });
-
- assert.deepEqual(warnings.length, 0);
-
- flushSync(() => {
- btn2.click();
- });
-
- assert.deepEqual(warnings.length, 1);
- }
-});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte
deleted file mode 100644
index 8e8343790b1b..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-First click here
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte
deleted file mode 100644
index ffe6ef75c4ed..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
- global.object.count += 1}>
- clicks: {global.object.count}
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte
deleted file mode 100644
index 5f1c7461f636..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js
deleted file mode 100644
index 6881c2faf66b..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export let global = $state({
- object: { count: -1 }
-});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js
index 87474a05cc33..39fa80c55a4e 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js
@@ -8,6 +8,6 @@ export default test({
},
warnings: [
- 'Intermediate.svelte passed a value to Counter.svelte with `bind:`, but the value is owned by main.svelte. Consider creating a binding between main.svelte and Intermediate.svelte'
+ 'Intermediate.svelte passed property `object` to Counter.svelte with `bind:`, but its parent component main.svelte did not declare `object` as a binding. Consider creating a binding between main.svelte and Intermediate.svelte (e.g. `bind:object={...}` instead of `object={...}`)'
]
});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js
index 66e51843808e..7b8cc676d528 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js
@@ -33,7 +33,7 @@ export default test({
assert.htmlEqual(target.innerHTML, `clicks: 1 clicks: 1 `);
assert.deepEqual(warnings, [
- 'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead'
+ 'Mutating unbound props (`notshared`, at Counter.svelte:10:23) is strongly discouraged. Consider using `bind:notshared={...}` in main.svelte (or using a callback) instead'
]);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js
index e766a946d0dc..bd2ecc28b6f7 100644
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js
@@ -1,7 +1,6 @@
import { flushSync } from 'svelte';
import { ok, test } from '../../test';
-// Tests that proxies widen ownership correctly even if not directly connected to each other
export default test({
compileOptions: {
dev: true
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte
deleted file mode 100644
index d6da559fb176..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-Binding
- linked3.count++}>Increment Linked 1 ({linked3.count})
- linked4.count++}>Increment Linked 2 ({linked4.count})
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte
deleted file mode 100644
index b935f0a472dc..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-Context
- linked1.linked.current.count++}
- >Increment Linked 1 ({linked1.linked.current.count})
- linked2.linked.current.count++}
- >Increment Linked 2 ({linked2.linked.current.count})
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js
deleted file mode 100644
index d6d12d01cd09..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { flushSync } from 'svelte';
-import { test } from '../../test';
-
-// Tests that ownership is widened with $derived (on class or on its own) that contains $state
-export default test({
- compileOptions: {
- dev: true
- },
-
- test({ assert, target, warnings }) {
- const [root, counter_context1, counter_context2, counter_binding1, counter_binding2] =
- target.querySelectorAll('button');
-
- counter_context1.click();
- counter_context2.click();
- counter_binding1.click();
- counter_binding2.click();
- flushSync();
-
- assert.equal(warnings.length, 0);
-
- root.click();
- flushSync();
- counter_context1.click();
- counter_context2.click();
- counter_binding1.click();
- counter_binding2.click();
- flushSync();
-
- assert.equal(warnings.length, 0);
- },
-
- warnings: []
-});
diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte
deleted file mode 100644
index aaade26e162c..000000000000
--- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-Parent
- counter.count++}>
- Increment Original ({counter.count})
-
-
-
-
From 7694818f9cf70cf802058c2a49571aaa69956d8a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 8 Apr 2025 17:09:22 -0400
Subject: [PATCH 89/97] Version Packages (#15705)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/clever-news-enjoy.md | 5 -----
.changeset/sweet-ants-care.md | 5 -----
packages/svelte/CHANGELOG.md | 8 ++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
5 files changed, 10 insertions(+), 12 deletions(-)
delete mode 100644 .changeset/clever-news-enjoy.md
delete mode 100644 .changeset/sweet-ants-care.md
diff --git a/.changeset/clever-news-enjoy.md b/.changeset/clever-news-enjoy.md
deleted file mode 100644
index 2ff3dcbe5668..000000000000
--- a/.changeset/clever-news-enjoy.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: allow `$.state` and `$.derived` to be treeshaken
diff --git a/.changeset/sweet-ants-care.md b/.changeset/sweet-ants-care.md
deleted file mode 100644
index b4805626ab4e..000000000000
--- a/.changeset/sweet-ants-care.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: rework binding ownership validation
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 361de202b11d..eb76e9a9e91f 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,13 @@
# svelte
+## 5.25.9
+
+### Patch Changes
+
+- fix: allow `$.state` and `$.derived` to be treeshaken ([#15702](https://github.com/sveltejs/svelte/pull/15702))
+
+- fix: rework binding ownership validation ([#15678](https://github.com/sveltejs/svelte/pull/15678))
+
## 5.25.8
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 8a225a798d9a..3fb843b3a28d 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.25.8",
+ "version": "5.25.9",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 2c8140e36540..13a69857a706 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.25.8';
+export const VERSION = '5.25.9';
export const PUBLIC_VERSION = '5';
From 708f541ad8ed2e9426cedb03e0bc1ad5d84cb787 Mon Sep 17 00:00:00 2001
From: 7nik
Date: Wed, 9 Apr 2025 00:39:11 +0300
Subject: [PATCH 90/97] fix: better scope `:global()` with nesting selector `&`
(#15671)
Co-authored-by: 7nik
---
.changeset/stupid-vans-draw.md | 5 +++++
.../phases/2-analyze/css/css-prune.js | 20 +++++++++++++++----
.../samples/global-with-nesting/expected.css | 7 ++++++-
.../samples/global-with-nesting/input.svelte | 7 ++++++-
4 files changed, 33 insertions(+), 6 deletions(-)
create mode 100644 .changeset/stupid-vans-draw.md
diff --git a/.changeset/stupid-vans-draw.md b/.changeset/stupid-vans-draw.md
new file mode 100644
index 000000000000..24892f1e8f65
--- /dev/null
+++ b/.changeset/stupid-vans-draw.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: better scope `:global()` with nesting selector `&`
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
index 0646c6341a5f..fbe6ca1cd379 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
@@ -1,6 +1,11 @@
/** @import * as Compiler from '#compiler' */
import { walk } from 'zimmerframe';
-import { get_parent_rules, get_possible_values, is_outer_global } from './utils.js';
+import {
+ get_parent_rules,
+ get_possible_values,
+ is_outer_global,
+ is_unscoped_pseudo_class
+} from './utils.js';
import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../patterns.js';
import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js';
@@ -286,20 +291,26 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
* a global selector
* @param {Compiler.AST.CSS.RelativeSelector} selector
* @param {Compiler.AST.CSS.Rule} rule
+ * @returns {boolean}
*/
function is_global(selector, rule) {
if (selector.metadata.is_global || selector.metadata.is_global_like) {
return true;
}
+ let explicitly_global = false;
+
for (const s of selector.selectors) {
/** @type {Compiler.AST.CSS.SelectorList | null} */
let selector_list = null;
+ let can_be_global = false;
let owner = rule;
if (s.type === 'PseudoClassSelector') {
if ((s.name === 'is' || s.name === 'where') && s.args) {
selector_list = s.args;
+ } else {
+ can_be_global = is_unscoped_pseudo_class(s);
}
}
@@ -308,18 +319,19 @@ function is_global(selector, rule) {
selector_list = owner.prelude;
}
- const has_global_selectors = selector_list?.children.some((complex_selector) => {
+ const has_global_selectors = !!selector_list?.children.some((complex_selector) => {
return complex_selector.children.every((relative_selector) =>
is_global(relative_selector, owner)
);
});
+ explicitly_global ||= has_global_selectors;
- if (!has_global_selectors) {
+ if (!has_global_selectors && !can_be_global) {
return false;
}
}
- return true;
+ return explicitly_global || selector.selectors.length === 0;
}
const regex_backslash_and_following_character = /\\(.)/g;
diff --git a/packages/svelte/tests/css/samples/global-with-nesting/expected.css b/packages/svelte/tests/css/samples/global-with-nesting/expected.css
index dcb8a0e48195..1863c57d853a 100644
--- a/packages/svelte/tests/css/samples/global-with-nesting/expected.css
+++ b/packages/svelte/tests/css/samples/global-with-nesting/expected.css
@@ -1,5 +1,10 @@
div.svelte-xyz {
&.class{
- color: red;
+ color: green;
+ }
+ }
+ * {
+ &:hover .class.svelte-xyz {
+ color: green;
}
}
\ No newline at end of file
diff --git a/packages/svelte/tests/css/samples/global-with-nesting/input.svelte b/packages/svelte/tests/css/samples/global-with-nesting/input.svelte
index 0c73ed7a78a2..2c1d2b5ebdac 100644
--- a/packages/svelte/tests/css/samples/global-with-nesting/input.svelte
+++ b/packages/svelte/tests/css/samples/global-with-nesting/input.svelte
@@ -1,7 +1,12 @@
From 966ccfbe7451b7c00fc63ee43ea65bd7f286b5cf Mon Sep 17 00:00:00 2001
From: Paolo Ricciuti
Date: Tue, 8 Apr 2025 23:41:53 +0200
Subject: [PATCH 91/97] fix: set deriveds as `CLEAN` if they are assigned to
(#15592)
* fix: set deriveds as `CLEAN` if they are assigned to
* chore: remove only
* chore: remove unnecessary typecast
* fix: set unowned as `MAYBE_DIRTY` instead of `CLEAN`
* fix: visit the derived function when to update the dependencies even when it's reassigned
* fix: use `execute_derived` instead of `update_reaction`
* fix: execute deriveds eagerly when they are set if DIRTY
---
.changeset/nervous-kids-shake.md | 5 +++
.../internal/client/reactivity/deriveds.js | 2 +-
.../src/internal/client/reactivity/sources.js | 12 +++++--
.../svelte/src/internal/client/runtime.js | 2 +-
packages/svelte/tests/signals/test.ts | 32 +++++++++++++++++++
5 files changed, 49 insertions(+), 4 deletions(-)
create mode 100644 .changeset/nervous-kids-shake.md
diff --git a/.changeset/nervous-kids-shake.md b/.changeset/nervous-kids-shake.md
new file mode 100644
index 000000000000..3fc642979738
--- /dev/null
+++ b/.changeset/nervous-kids-shake.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: set deriveds as `CLEAN` if they are assigned to
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index 86171c2b2df8..c9a8f7674a21 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -131,7 +131,7 @@ function get_derived_parent_effect(derived) {
* @param {Derived} derived
* @returns {T}
*/
-function execute_derived(derived) {
+export function execute_derived(derived) {
var value;
var prev_active_effect = active_effect;
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 2361762519fc..cae49c18323f 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -28,14 +28,14 @@ import {
UNOWNED,
MAYBE_DIRTY,
BLOCK_EFFECT,
- ROOT_EFFECT,
- EFFECT_IS_UPDATING
+ ROOT_EFFECT
} from '../constants.js';
import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
import { get_stack } from '../dev/tracing.js';
import { component_context, is_runes } from '../context.js';
import { proxy } from '../proxy.js';
+import { execute_derived } from './deriveds.js';
export let inspect_effects = new Set();
export const old_values = new Map();
@@ -172,6 +172,14 @@ export function internal_set(source, value) {
}
}
+ if ((source.f & DERIVED) !== 0) {
+ // if we are assigning to a dirty derived we set it to clean/maybe dirty but we also eagerly execute it to track the dependencies
+ if ((source.f & DIRTY) !== 0) {
+ execute_derived(/** @type {Derived} */ (source));
+ }
+ set_signal_status(source, (source.f & UNOWNED) === 0 ? CLEAN : MAYBE_DIRTY);
+ }
+
mark_reactions(source, DIRTY);
// It's possible that the current reaction might not have up-to-date dependencies
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index c1c91f35515d..a7662be617b8 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -27,7 +27,7 @@ import {
} from './constants.js';
import { flush_tasks } from './dom/task.js';
import { internal_set, old_values } from './reactivity/sources.js';
-import { destroy_derived_effects, update_derived } from './reactivity/deriveds.js';
+import { destroy_derived_effects, execute_derived, update_derived } from './reactivity/deriveds.js';
import * as e from './errors.js';
import { FILENAME } from '../../constants.js';
import { tracing_mode_flag } from '../flags/index.js';
diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts
index 3977caae36ad..3a427e939274 100644
--- a/packages/svelte/tests/signals/test.ts
+++ b/packages/svelte/tests/signals/test.ts
@@ -1080,6 +1080,38 @@ describe('signals', () => {
};
});
+ test("deriveds set after they are DIRTY doesn't get updated on get", () => {
+ return () => {
+ const a = state(0);
+ const b = derived(() => $.get(a));
+
+ set(b, 1);
+ assert.equal($.get(b), 1);
+
+ set(a, 2);
+ assert.equal($.get(b), 2);
+ set(b, 3);
+
+ assert.equal($.get(b), 3);
+ };
+ });
+
+ test("unowned deriveds set after they are DIRTY doesn't get updated on get", () => {
+ return () => {
+ const a = state(0);
+ const b = derived(() => $.get(a));
+ const c = derived(() => $.get(b));
+
+ set(b, 1);
+ assert.equal($.get(c), 1);
+
+ set(a, 2);
+
+ assert.equal($.get(b), 2);
+ assert.equal($.get(c), 2);
+ };
+ });
+
test('deriveds containing effects work correctly when used with untrack', () => {
return () => {
let a = render_effect(() => {});
From e9a16c4b4209d104e0bef7cf28df51f830de1254 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 8 Apr 2025 18:53:18 -0400
Subject: [PATCH 92/97] chore: revert corepack override (#15197)
* Revert "try this (#15196)"
This reverts commit f878736f3825e1832fa7344306358e877e20bd7f.
* upgrade node
* upgrade pnpm
---------
Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
---
.github/workflows/pkg.pr.new.yml | 5 +----
package.json | 2 +-
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml
index 90d219faae6a..b2b521dc6fdd 100644
--- a/.github/workflows/pkg.pr.new.yml
+++ b/.github/workflows/pkg.pr.new.yml
@@ -11,13 +11,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- - name: install corepack
- run: npm i -g corepack@0.31.0
-
- run: corepack enable
- uses: actions/setup-node@v4
with:
- node-version: 18.x
+ node-version: 22.x
cache: pnpm
- name: Install dependencies
diff --git a/package.json b/package.json
index ad69bfc9cafb..70e85438f045 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"private": true,
"type": "module",
"license": "MIT",
- "packageManager": "pnpm@9.4.0",
+ "packageManager": "pnpm@10.4.0",
"engines": {
"pnpm": ">=9.0.0"
},
From 0ca1f4a37ec008092fc1798e374c6108308addef Mon Sep 17 00:00:00 2001
From: Min Idzelis
Date: Tue, 8 Apr 2025 21:26:54 -0400
Subject: [PATCH 93/97] docs: raise importance of global vs local transitions
(#15479)
* Doc: Raise importance of global vs local transitions
* switch order
---------
Co-authored-by: Rich Harris
---
documentation/docs/03-template-syntax/13-transition.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/documentation/docs/03-template-syntax/13-transition.md b/documentation/docs/03-template-syntax/13-transition.md
index 51c11e8b34e9..c51175c272bc 100644
--- a/documentation/docs/03-template-syntax/13-transition.md
+++ b/documentation/docs/03-template-syntax/13-transition.md
@@ -22,10 +22,6 @@ The `transition:` directive indicates a _bidirectional_ transition, which means
{/if}
```
-## Built-in transitions
-
-A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module.
-
## Local vs global
Transitions are local by default. Local transitions only play when the block they belong to is created or destroyed, _not_ when parent blocks are created or destroyed.
@@ -40,6 +36,10 @@ Transitions are local by default. Local transitions only play when the block the
{/if}
```
+## Built-in transitions
+
+A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module.
+
## Transition parameters
Transitions can have parameters.
From 0ff3d7452092511a55a63a7639a8e8798fea9fed Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Tue, 8 Apr 2025 19:40:05 -0700
Subject: [PATCH 94/97] docs: update `$effect` examples (#15463)
* docs: update effect examples
* revert
* Update documentation/docs/02-runes/04-$effect.md
* update example
* revert
* update effect root example
---------
Co-authored-by: Rich Harris
---
documentation/docs/02-runes/04-$effect.md | 55 ++++++++++++++---------
1 file changed, 34 insertions(+), 21 deletions(-)
diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md
index ae1a2146c9d4..46ea9b81e92e 100644
--- a/documentation/docs/02-runes/04-$effect.md
+++ b/documentation/docs/02-runes/04-$effect.md
@@ -25,7 +25,7 @@ You can create an effect with the `$effect` rune ([demo](/playground/untitled#H4
});
-
+
```
When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes.
@@ -135,19 +135,33 @@ An effect only reruns when the object it reads changes, not when a property insi
An effect only depends on the values that it read the last time it ran. This has interesting implications for effects that have conditional code.
-For instance, if `a` is `true` in the code snippet below, the code inside the `if` block will run and `b` will be evaluated. As such, changes to either `a` or `b` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE3VQzWrDMAx-FdUU4kBp71li6EPstOxge0ox8-QQK2PD-N1nLy2F0Z2Evj9_chKkP1B04pnYscc3cRCT8xhF95IEf8-Vq0DBr8rzPB_jJ3qumNERH-E2ECNxiRF9tIubWY00lgcYNAywj6wZJS8rtk83wjwgCrXHaULLUrYwKEgVGrnkx-Dx6MNFNstK5OjSbFGbwE0gdXuT_zGYrjmAuco515Hr1p_uXak3K3MgCGS9s-9D2grU-judlQYXIencnzad-tdR79qZrMyvw9wd5Z8Yv1h09dz8mn8AkM7Pfo0BAAA=).
+For instance, if `condition` is `true` in the code snippet below, the code inside the `if` block will run and `color` will be evaluated. As such, changes to either `condition` or `color` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE21RQW6DMBD8ytaNBJHaJFLViwNIVZ8RcnBgXVk1xsILTYT4e20TQg89IOPZ2fHM7siMaJBx9tmaWpFqjQNlAKXEihx7YVJpdIyfRkY3G4gB8Pi97cPanRtQU8AuwuF_eNUaQuPlOMtc1SlLRWlKUo1tOwJflUikQHZtA0klzCDc64Imx0ANn8bInV1CDhtHgjClrsftcSXotluLybOUb3g4JJHhOZs5WZpuIS9gjNqkJKQP5e2ClrR4SMdZ13E4xZ8zTPOTJU2A2uE_PQ9COCI926_hTVarIU4hu_REPlBrKq2q73ycrf1N-vS4TMUsulaVg3EtR8H9rFgsg8uUsT1B2F9eshigZHBRpuaD0D3mY8Qm2BfB5N2YyRzdNEYVDy0Ja-WsFjcOUuP1HvFLWA6H3XuHTUSmmDV2--0TXonxsKbp7G9C6R__NONS-MFNvxj_d6mBAgAA).
-Conversely, if `a` is `false`, `b` will not be evaluated, and the effect will _only_ re-run when `a` changes.
+Conversely, if `condition` is `false`, `color` will not be evaluated, and the effect will _only_ re-run again when `condition` changes.
```ts
-let a = false;
-let b = false;
+// @filename: ambient.d.ts
+declare module 'canvas-confetti' {
+ interface ConfettiOptions {
+ colors: string[];
+ }
+
+ function confetti(opts?: ConfettiOptions): void;
+ export default confetti;
+}
+
+// @filename: index.js
// ---cut---
-$effect(() => {
- console.log('running');
+import confetti from 'canvas-confetti';
- if (a) {
- console.log('b:', b);
+let condition = $state(true);
+let color = $state('#ff3e00');
+
+$effect(() => {
+ if (condition) {
+ confetti({ colors: [color] });
+ } else {
+ confetti();
}
});
```
@@ -211,20 +225,19 @@ It is used to implement abstractions like [`createSubscriber`](/docs/svelte/svel
The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for nested effects that you want to manually control. This rune also allows for the creation of effects outside of the component initialisation phase.
-```svelte
-
+// later...
+destroy();
```
## When not to use `$effect`
From 93110a32469779794d2c022a7605e958ce75cb34 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 9 Apr 2025 06:30:53 -0400
Subject: [PATCH 95/97] docs: explain restriction on exporting reassigned state
(#15713)
---
.../01-introduction/04-svelte-js-files.md | 2 +-
documentation/docs/02-runes/02-$state.md | 80 +++++++++++++++++++
2 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/documentation/docs/01-introduction/04-svelte-js-files.md b/documentation/docs/01-introduction/04-svelte-js-files.md
index 0e05484299db..1d3e3dd61a8a 100644
--- a/documentation/docs/01-introduction/04-svelte-js-files.md
+++ b/documentation/docs/01-introduction/04-svelte-js-files.md
@@ -4,7 +4,7 @@ title: .svelte.js and .svelte.ts files
Besides `.svelte` files, Svelte also operates on `.svelte.js` and `.svelte.ts` files.
-These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app.
+These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app (though note that you [cannot export reassigned state]($state#Passing-state-across-modules)).
> [!LEGACY]
> This is a concept that didn't exist prior to Svelte 5
diff --git a/documentation/docs/02-runes/02-$state.md b/documentation/docs/02-runes/02-$state.md
index 49e17cd08ff3..16630a977b62 100644
--- a/documentation/docs/02-runes/02-$state.md
+++ b/documentation/docs/02-runes/02-$state.md
@@ -250,3 +250,83 @@ console.log(total.value); // 7
```
...though if you find yourself writing code like that, consider using [classes](#Classes) instead.
+
+## Passing state across modules
+
+You can declare state in `.svelte.js` and `.svelte.ts` files, but you can only _export_ that state if it's not directly reassigned. In other words you can't do this:
+
+```js
+/// file: state.svelte.js
+export let count = $state(0);
+
+export function increment() {
+ count += 1;
+}
+```
+
+That's because every reference to `count` is transformed by the Svelte compiler — the code above is roughly equivalent to this:
+
+```js
+/// file: state.svelte.js (compiler output)
+// @filename: index.ts
+interface Signal {
+ value: T;
+}
+
+interface Svelte {
+ state(value?: T): Signal;
+ get(source: Signal): T;
+ set(source: Signal, value: T): void;
+}
+declare const $: Svelte;
+// ---cut---
+export let count = $.state(0);
+
+export function increment() {
+ $.set(count, $.get(count) + 1);
+}
+```
+
+> [!NOTE] You can see the code Svelte generates by clicking the 'JS Output' tab in the [playground](/playground).
+
+Since the compiler only operates on one file at a time, if another file imports `count` Svelte doesn't know that it needs to wrap each reference in `$.get` and `$.set`:
+
+```js
+// @filename: state.svelte.js
+export let count = 0;
+
+// @filename: index.js
+// ---cut---
+import { count } from './state.svelte.js';
+
+console.log(typeof count); // 'object', not 'number'
+```
+
+This leaves you with two options for sharing state between modules — either don't reassign it...
+
+```js
+// This is allowed — since we're updating
+// `counter.count` rather than `counter`,
+// Svelte doesn't wrap it in `$.state`
+export const counter = $state({
+ count: 0
+});
+
+export function increment() {
+ counter.count += 1;
+}
+```
+
+...or don't directly export it:
+
+```js
+let count = $state(0);
+
+export function getCount() {
+ return count;
+}
+
+export function increment() {
+ count += 1;
+}
+```
From c23f15134e85c17e85ad326fcb50bbc4a1cdffa4 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 9 Apr 2025 12:51:21 -0400
Subject: [PATCH 96/97] chore: remove stack-based module boundaries (#15711)
---
.../98-reference/.generated/client-errors.md | 2 +-
.../svelte/messages/client-errors/errors.md | 2 +-
.../3-transform/client/transform-client.js | 3 -
.../svelte/src/internal/client/dev/legacy.js | 5 +-
.../src/internal/client/dev/ownership.js | 97 -------------------
packages/svelte/src/internal/client/errors.js | 7 +-
packages/svelte/src/internal/client/index.js | 2 +-
7 files changed, 7 insertions(+), 111 deletions(-)
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index fd9419176d81..32348bb78182 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -21,7 +21,7 @@ A component is attempting to bind to a non-bindable property `%key%` belonging t
### component_api_changed
```
-%parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5
+Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5
```
See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md
index ca06122cb581..c4e68f8fee80 100644
--- a/packages/svelte/messages/client-errors/errors.md
+++ b/packages/svelte/messages/client-errors/errors.md
@@ -12,7 +12,7 @@
## component_api_changed
-> %parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5
+> Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5
See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index 38fcee8d6fca..098b3ecae8c3 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -536,9 +536,6 @@ export function client_component(analysis, options) {
b.assignment('=', b.member(b.id(analysis.name), '$.FILENAME', true), b.literal(filename))
)
);
-
- body.unshift(b.stmt(b.call(b.id('$.mark_module_start'))));
- body.push(b.stmt(b.call(b.id('$.mark_module_end'), b.id(analysis.name))));
}
if (!analysis.runes) {
diff --git a/packages/svelte/src/internal/client/dev/legacy.js b/packages/svelte/src/internal/client/dev/legacy.js
index 138213c5517b..02428dc82457 100644
--- a/packages/svelte/src/internal/client/dev/legacy.js
+++ b/packages/svelte/src/internal/client/dev/legacy.js
@@ -1,7 +1,6 @@
import * as e from '../errors.js';
import { component_context } from '../context.js';
import { FILENAME } from '../../../constants.js';
-import { get_component } from './ownership.js';
/** @param {Function & { [FILENAME]: string }} target */
export function check_target(target) {
@@ -15,9 +14,7 @@ export function legacy_api() {
/** @param {string} method */
function error(method) {
- // @ts-expect-error
- const parent = get_component()?.[FILENAME] ?? 'Something';
- e.component_api_changed(parent, method, component[FILENAME]);
+ e.component_api_changed(method, component[FILENAME]);
}
return {
diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js
index 6c40a744dfc5..e28a40dd77ee 100644
--- a/packages/svelte/src/internal/client/dev/ownership.js
+++ b/packages/svelte/src/internal/client/dev/ownership.js
@@ -7,103 +7,6 @@ import { component_context } from '../context.js';
import * as w from '../warnings.js';
import { sanitize_location } from '../../../utils.js';
-/** @type {Record>} */
-const boundaries = {};
-
-const chrome_pattern = /at (?:.+ \()?(.+):(\d+):(\d+)\)?$/;
-const firefox_pattern = /@(.+):(\d+):(\d+)$/;
-
-function get_stack() {
- const stack = new Error().stack;
- if (!stack) return null;
-
- const entries = [];
-
- for (const line of stack.split('\n')) {
- let match = chrome_pattern.exec(line) ?? firefox_pattern.exec(line);
-
- if (match) {
- entries.push({
- file: match[1],
- line: +match[2],
- column: +match[3]
- });
- }
- }
-
- return entries;
-}
-
-/**
- * Determines which `.svelte` component is responsible for a given state change
- * @returns {Function | null}
- */
-export function get_component() {
- // first 4 lines are svelte internals; adjust this number if we change the internal call stack
- const stack = get_stack()?.slice(4);
- if (!stack) return null;
-
- for (let i = 0; i < stack.length; i++) {
- const entry = stack[i];
- const modules = boundaries[entry.file];
- if (!modules) {
- // If the first entry is not a component, that means the modification very likely happened
- // within a .svelte.js file, possibly triggered by a component. Since these files are not part
- // of the bondaries/component context heuristic, we need to bail in this case, else we would
- // have false positives when the .svelte.ts file provides a state creator function, encapsulating
- // the state and its mutations, and is being called from a component other than the one who
- // called the state creator function.
- if (i === 0) return null;
- continue;
- }
-
- for (const module of modules) {
- if (module.end == null) {
- return null;
- }
- if (module.start.line < entry.line && module.end.line > entry.line) {
- return module.component;
- }
- }
- }
-
- return null;
-}
-
-/**
- * Together with `mark_module_end`, this function establishes the boundaries of a `.svelte` file,
- * such that subsequent calls to `get_component` can tell us which component is responsible
- * for a given state change
- */
-export function mark_module_start() {
- const start = get_stack()?.[2];
-
- if (start) {
- (boundaries[start.file] ??= []).push({
- start,
- // @ts-expect-error
- end: null,
- // @ts-expect-error we add the component at the end, since HMR will overwrite the function
- component: null
- });
- }
-}
-
-/**
- * @param {Function} component
- */
-export function mark_module_end(component) {
- const end = get_stack()?.[2];
-
- if (end) {
- const boundaries_file = boundaries[end.file];
- const boundary = boundaries_file[boundaries_file.length - 1];
-
- boundary.end = end;
- boundary.component = component;
- }
-}
-
/**
* Sets up a validator that
* - traverses the path of a prop to find out if it is allowed to be mutated
diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js
index 8a5b5033a78c..429dd99da9b9 100644
--- a/packages/svelte/src/internal/client/errors.js
+++ b/packages/svelte/src/internal/client/errors.js
@@ -54,15 +54,14 @@ export function bind_not_bindable(key, component, name) {
}
/**
- * %parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5
- * @param {string} parent
+ * Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5
* @param {string} method
* @param {string} component
* @returns {never}
*/
-export function component_api_changed(parent, method, component) {
+export function component_api_changed(method, component) {
if (DEV) {
- const error = new Error(`component_api_changed\n${parent} called \`${method}\` on an instance of ${component}, which is no longer valid in Svelte 5\nhttps://svelte.dev/e/component_api_changed`);
+ const error = new Error(`component_api_changed\nCalling \`${method}\` on a component instance (of ${component}) is no longer valid in Svelte 5\nhttps://svelte.dev/e/component_api_changed`);
error.name = 'Svelte error';
throw error;
diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js
index 7eed8a744afa..a865419c5f1b 100644
--- a/packages/svelte/src/internal/client/index.js
+++ b/packages/svelte/src/internal/client/index.js
@@ -4,7 +4,7 @@ export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js';
export { cleanup_styles } from './dev/css.js';
export { add_locations } from './dev/elements.js';
export { hmr } from './dev/hmr.js';
-export { mark_module_start, mark_module_end, create_ownership_validator } from './dev/ownership.js';
+export { create_ownership_validator } from './dev/ownership.js';
export { check_target, legacy_api } from './dev/legacy.js';
export { trace } from './dev/tracing.js';
export { inspect } from './dev/inspect.js';
From 475b5dbe83732fd031fa4f97aac712550385c700 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 9 Apr 2025 14:09:51 -0400
Subject: [PATCH 97/97] Version Packages (#15712)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/nervous-kids-shake.md | 5 -----
.changeset/stupid-vans-draw.md | 5 -----
packages/svelte/CHANGELOG.md | 8 ++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
5 files changed, 10 insertions(+), 12 deletions(-)
delete mode 100644 .changeset/nervous-kids-shake.md
delete mode 100644 .changeset/stupid-vans-draw.md
diff --git a/.changeset/nervous-kids-shake.md b/.changeset/nervous-kids-shake.md
deleted file mode 100644
index 3fc642979738..000000000000
--- a/.changeset/nervous-kids-shake.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: set deriveds as `CLEAN` if they are assigned to
diff --git a/.changeset/stupid-vans-draw.md b/.changeset/stupid-vans-draw.md
deleted file mode 100644
index 24892f1e8f65..000000000000
--- a/.changeset/stupid-vans-draw.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: better scope `:global()` with nesting selector `&`
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index eb76e9a9e91f..6f999f381ebb 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,13 @@
# svelte
+## 5.25.10
+
+### Patch Changes
+
+- fix: set deriveds as `CLEAN` if they are assigned to ([#15592](https://github.com/sveltejs/svelte/pull/15592))
+
+- fix: better scope `:global()` with nesting selector `&` ([#15671](https://github.com/sveltejs/svelte/pull/15671))
+
## 5.25.9
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 3fb843b3a28d..b9f434e68809 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.25.9",
+ "version": "5.25.10",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 13a69857a706..2ea9890df92e 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.25.9';
+export const VERSION = '5.25.10';
export const PUBLIC_VERSION = '5';