Merge remote-tracking branch 'upstream/main' into $state-invalidate

pull/15673/head
ComputerGuy 5 months ago
commit 1d3833f3b5

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: ensure clearing of old values happens independent of root flushes

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: set deriveds as `CLEAN` if they are assigned to

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: better scope `:global()` with nesting selector `&`

@ -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

@ -25,7 +25,7 @@ You can create an effect with the `$effect` rune ([demo](/playground/untitled#H4
});
</script>
<canvas bind:this={canvas} width="100" height="100" />
<canvas bind:this={canvas} width="100" height="100"></canvas>
```
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
<script>
let count = $state(0);
```js
const destroy = $effect.root(() => {
$effect(() => {
// setup
});
const cleanup = $effect.root(() => {
$effect(() => {
console.log(count);
});
return () => {
// cleanup
};
});
return () => {
console.log('effect root cleanup');
};
});
</script>
// later...
destroy();
```
## When not to use `$effect`

@ -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)):
<!-- prettier-ignore -->
```svelte

@ -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.

@ -94,7 +94,7 @@ interface User {}
// ---cut---
import { getContext, setContext } from 'svelte';
let key = {};
const key = {};
/** @param {User} user */
export function setUserContext(user) {

@ -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 `<GrandParent bind:value>`, inside `GrandParent` pass on the variable via `<Parent {value} />` (note the missing `bind:`) and then do `<Child bind:value>` 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:

@ -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"
},

@ -1,5 +1,25 @@
# 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
- fix: address untracked_writes memory leak ([#15694](https://github.com/sveltejs/svelte/pull/15694))
## 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

@ -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 `<GrandParent bind:value>`, inside `GrandParent` pass on the variable via `<Parent {value} />` (note the missing `bind:`) and then do `<Child bind:value>` 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:

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.25.6",
"version": "5.25.9",
"type": "module",
"types": "./types/index.d.ts",
"engines": {

@ -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;

@ -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,

@ -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 ||

@ -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;
}

@ -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);
}
/**

@ -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 };
}

@ -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);
}

@ -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') {

@ -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<Identifier | Literal>} */
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)
);
}

@ -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;

@ -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('');

@ -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;
}

@ -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<string, Array<{ start: Location, end: Location, component: Function }>>} */
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<string, any>} 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<any>} 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<string, any>} 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);
}

@ -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';

@ -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<T>} [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

@ -67,6 +67,7 @@ export function derived(fn) {
* @param {() => V} fn
* @returns {Derived<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function user_derived(fn) {
const d = derived(fn);
@ -130,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;

@ -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();
@ -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);
@ -139,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);
}
@ -171,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

@ -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';
@ -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) {

@ -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<Function>;
/** The parent metadata object */
parent: null | ProxyMetadata;
}
export type ProxyStateObject<T = Record<string | symbol, any>> = T & {
[STATE_SYMBOL]: T;
};

@ -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`);
}

@ -466,8 +466,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'));
}

@ -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.9';
export const PUBLIC_VERSION = '5';

@ -1,5 +1,10 @@
div.svelte-xyz {
&.class{
color: red;
color: green;
}
}
* {
&:hover .class.svelte-xyz {
color: green;
}
}

@ -1,7 +1,12 @@
<style>
div {
&:global(.class){
color: red;
color: green;
}
}
:global(*) {
&:hover .class {
color: green;
}
}
</style>

@ -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();

@ -1,11 +0,0 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
async test({ assert, warnings }) {
assert.deepEqual(warnings, []);
}
});

@ -1,18 +0,0 @@
<script module>
let toast1 = $state();
let toast2 = $state({});
export async function show_toast() {
toast1 = {
message: 'foo',
show: true
};
toast1.show = false;
toast2 = {
message: 'foo',
show: true
};
toast2.show = false;
}
</script>

@ -1,5 +0,0 @@
<script>
import { show_toast } from "./child.svelte";
show_toast();
</script>

@ -8,7 +8,7 @@ let warn;
let warnings = [];
export default test({
html: `<button>clicks: 0</button>`,
html: `<button>[]</button>`,
compileOptions: {
dev: true
@ -34,8 +34,8 @@ export default test({
btn?.click();
});
assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button>`);
assert.htmlEqual(target.innerHTML, `<button>[foo]</button>`);
assert.deepEqual(warnings, []);
assert.deepEqual(warnings, [], 'expected getContext to have widened ownership');
}
});

@ -0,0 +1,9 @@
<script>
import { setContext } from 'svelte';
import Sub from './sub.svelte';
let list = $state([]);
setContext('list', list);
</script>
<Sub />

@ -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: `<button>clicks: 0</button>`,
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, `<button>clicks: 1</button>`);
assert.deepEqual(warnings, []);
assert.deepEqual(warnings.length, 1);
}
});

@ -1,9 +1,8 @@
<script>
import { global } from './state.svelte.js';
<script lang="ts">
import Sub from './sub.svelte';
import { create_my_state } from './state.svelte';
global.a = { b: 0 };
const myState = create_my_state();
</script>
<button onclick={() => global.a.b += 1}>
clicks: {global.a.b}
</button>
<Sub count={myState.my_state} inc={myState.inc} />

@ -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
};
}

@ -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: `<button>[]</button>`,
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, `<button>[foo]</button>`);
assert.deepEqual(warnings, [], 'expected getContext to have widened ownership');
assert.deepEqual(warnings.length, 1);
}
});

@ -1,9 +1,13 @@
<script>
import { setContext } from 'svelte';
import Sub from './sub.svelte';
import Child from './Child.svelte';
let list = $state([]);
setContext('list', list);
let items = $state([{ id: "test", name: "this is a test"}, { id:"test2", name: "this is a second test"}]);
let found = $state();
function onclick() {
found = items.find(c => c.id === 'test2');
}
</script>
<Sub />
<button {onclick}>First click here</button>
<Child item={found} />

@ -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, []);
}
});

@ -1,11 +0,0 @@
<script>
import { global } from './state.svelte.js';
import Sub from './sub.svelte';
</script>
<Sub />
<!-- it's important _NOT_ to read global.a.b in the template,
else the proxy would set up the structure with its own component context already -->
<button onclick={() => global.increment_a_b()}>
click me
</button>

@ -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();

@ -1,8 +0,0 @@
<script>
import { global } from "./state.svelte";
// The real world use case would be someone manipulating state locally, for example form state,
// and then push the values to a global state for everyone else to see / possibly mutate.
const local_soon_global = $state({ b: 0 });
global.add_a(local_soon_global);
</script>

@ -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);
}
});

@ -1,8 +0,0 @@
<script lang="ts">
import Sub from './sub.svelte';
import { create_my_state } from './state.svelte';
const myState = create_my_state();
</script>
<Sub count={myState.my_state} inc={myState.inc} />

@ -1,14 +0,0 @@
export function create_my_state() {
const my_state = $state({
a: 0
});
function inc() {
my_state.a++;
}
return {
my_state,
inc
};
}

@ -1,9 +0,0 @@
<script>
import { getContext } from 'svelte';
const foo = getContext('foo')
</script>
<button onclick={() => {
foo.person.name.last = 'T';
}}>mutate</button>

@ -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);
}
});

@ -1,17 +0,0 @@
<script>
import { setContext } from 'svelte';
import Child from './Child.svelte';
class Person {
name = $state({
first: 'Mr',
last: 'Bean'
});
}
setContext('foo', {
person: new Person()
});
</script>
<Child />

@ -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);
}
});

@ -1,13 +0,0 @@
<script>
import Child from './Child.svelte';
let items = $state([{ id: "test", name: "this is a test"}, { id:"test2", name: "this is a second test"}]);
let found = $state();
function onclick() {
found = items.find(c => c.id === 'test2');
}
</script>
<button {onclick}>First click here</button>
<Child item={found} />

@ -1,7 +0,0 @@
<script>
import { global } from './state.svelte.js';
</script>
<button onclick={() => global.object.count += 1}>
clicks: {global.object.count}
</button>

@ -1,9 +0,0 @@
<script>
import Counter from './Counter.svelte';
import { global } from './state.svelte.js';
let object = $state({ count: 0 });
global.object = object;
</script>
<Counter />

@ -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={...}`)'
]
});

@ -33,7 +33,7 @@ export default test({
assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button><button>clicks: 1</button>`);
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'
]);
}
});

@ -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

@ -1,7 +0,0 @@
<script>
let { linked3 = $bindable(), linked4 = $bindable() } = $props();
</script>
<p>Binding</p>
<button onclick={() => linked3.count++}>Increment Linked 1 ({linked3.count})</button>
<button onclick={() => linked4.count++}>Increment Linked 2 ({linked4.count})</button>

@ -1,13 +0,0 @@
<script>
import { getContext } from 'svelte';
const linked1 = getContext('linked1');
const linked2 = getContext('linked2');
</script>
<p>Context</p>
<button onclick={() => linked1.linked.current.count++}
>Increment Linked 1 ({linked1.linked.current.count})</button
>
<button onclick={() => linked2.linked.current.count++}
>Increment Linked 2 ({linked2.linked.current.count})</button
>

@ -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: []
});

@ -1,46 +0,0 @@
<script>
import CounterBinding from './CounterBinding.svelte';
import CounterContext from './CounterContext.svelte';
import { setContext } from 'svelte';
let counter = $state({ count: 0 });
class Linked {
#getter;
linked = $derived.by(() => {
const state = $state({ current: $state.snapshot(this.#getter()) });
return state;
});
constructor(fn) {
this.#getter = fn;
}
}
const linked1 = $derived.by(() => {
const state = $state({ current: $state.snapshot(counter) });
return state;
});
const linked2 = new Linked(() => counter);
setContext('linked1', {
get linked() {
return linked1;
}
});
setContext('linked2', linked2);
const linked3 = $derived.by(() => {
const state = $state({ current: $state.snapshot(counter) });
return state;
});
const linked4 = new Linked(() => counter);
</script>
<p>Parent</p>
<button onclick={() => counter.count++}>
Increment Original ({counter.count})
</button>
<CounterContext />
<CounterBinding bind:linked3={linked3.current} bind:linked4={linked4.linked.current} />

@ -1086,6 +1086,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(() => {});

@ -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"
}
}

@ -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:

Loading…
Cancel
Save