From 09510c8c5c0888578446781e54f2229428e21bfc Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 21 Jan 2025 20:01:14 +0000 Subject: [PATCH 01/67] fix: address regression with untrack (#15079) * fix: address regression with untrack * add test --- .changeset/ninety-chefs-know.md | 5 ++ .../src/internal/client/reactivity/sources.js | 2 +- .../untracked-derived-local/_config.js | 5 ++ .../untracked-derived-local/main.svelte | 47 +++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 .changeset/ninety-chefs-know.md create mode 100644 packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte diff --git a/.changeset/ninety-chefs-know.md b/.changeset/ninety-chefs-know.md new file mode 100644 index 0000000000..b5eac6a07a --- /dev/null +++ b/.changeset/ninety-chefs-know.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: address regression with untrack diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index d10008dae2..c2448c9ee5 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -116,7 +116,7 @@ export function mutable_state(v, immutable = false) { */ /*#__NO_SIDE_EFFECTS__*/ function push_derived_source(source) { - if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) { + if (active_reaction !== null && !untracking && (active_reaction.f & DERIVED) !== 0) { if (derived_sources === null) { set_derived_sources([source]); } else { diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js new file mode 100644 index 0000000000..1fe92ed2d7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `3` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte new file mode 100644 index 0000000000..0873eb741d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte @@ -0,0 +1,47 @@ + + + + +{test} From ee024ffd0e57dd5eca90d0c7aca5dc0e83d55449 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:30:18 +0000 Subject: [PATCH 02/67] Version Packages (#15080) Co-authored-by: github-actions[bot] --- .changeset/ninety-chefs-know.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/ninety-chefs-know.md diff --git a/.changeset/ninety-chefs-know.md b/.changeset/ninety-chefs-know.md deleted file mode 100644 index b5eac6a07a..0000000000 --- a/.changeset/ninety-chefs-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: address regression with untrack diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index d16bdbc327..e15c0e0a09 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.19.2 + +### Patch Changes + +- fix: address regression with untrack ([#15079](https://github.com/sveltejs/svelte/pull/15079)) + ## 5.19.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b725e52fed..48287cf901 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.19.1", + "version": "5.19.2", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index b0ea99b6f4..7a3f016aaa 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.19.1'; +export const VERSION = '5.19.2'; export const PUBLIC_VERSION = '5'; From 7da86ef80cd3503a14e26393cbbb2e6a2a933ed9 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Wed, 22 Jan 2025 14:34:57 +0100 Subject: [PATCH 03/67] fix: don't throw for `undefined` non delegated event handlers (#15087) * fix: don't throw for `undefined` non delegated event handlers * chore: update typings --- .changeset/clever-cherries-hear.md | 5 +++++ .../src/internal/client/dom/elements/events.js | 12 ++++++------ .../samples/undefined-event-handler/_config.js | 9 +++++++++ .../samples/undefined-event-handler/main.svelte | 1 + 4 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 .changeset/clever-cherries-hear.md create mode 100644 packages/svelte/tests/runtime-runes/samples/undefined-event-handler/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/undefined-event-handler/main.svelte diff --git a/.changeset/clever-cherries-hear.md b/.changeset/clever-cherries-hear.md new file mode 100644 index 0000000000..df37943b46 --- /dev/null +++ b/.changeset/clever-cherries-hear.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't throw for `undefined` non delegated event handlers diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index f2038f96ad..363b8e1ed5 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -49,10 +49,10 @@ export function replay_events(dom) { /** * @param {string} event_name * @param {EventTarget} dom - * @param {EventListener} handler - * @param {AddEventListenerOptions} options + * @param {EventListener} [handler] + * @param {AddEventListenerOptions} [options] */ -export function create_event(event_name, dom, handler, options) { +export function create_event(event_name, dom, handler, options = {}) { /** * @this {EventTarget} */ @@ -63,7 +63,7 @@ export function create_event(event_name, dom, handler, options) { } if (!event.cancelBubble) { return without_reactive_context(() => { - return handler.call(this, event); + return handler?.call(this, event); }); } } @@ -108,8 +108,8 @@ export function on(element, type, handler, options = {}) { /** * @param {string} event_name * @param {Element} dom - * @param {EventListener} handler - * @param {boolean} capture + * @param {EventListener} [handler] + * @param {boolean} [capture] * @param {boolean} [passive] * @returns {void} */ diff --git a/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/_config.js b/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/_config.js new file mode 100644 index 0000000000..012fedb160 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/_config.js @@ -0,0 +1,9 @@ +import { ok, test } from '../../test'; + +export default test({ + async test({ target }) { + const button = target.querySelector('button'); + ok(button); + button.dispatchEvent(new window.MouseEvent('mouseenter')); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/main.svelte b/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/main.svelte new file mode 100644 index 0000000000..ea4b4443e9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/main.svelte @@ -0,0 +1 @@ + From eee808fb79cfb182df7285b7107d5f7d2bee44d1 Mon Sep 17 00:00:00 2001 From: "Trevor N. Suarez" Date: Wed, 22 Jan 2025 06:35:26 -0700 Subject: [PATCH 04/67] fix: correctly handle `novalidate` attribute casing (#15083) This PR fixes #15082 by correctly handling the HTML element novalidate attribute casing through noValidate DOM property mapping. --- .changeset/pink-wolves-search.md | 5 +++++ packages/svelte/src/utils.js | 3 ++- .../samples/form-novalidate-casing/_config.js | 8 ++++++++ .../samples/form-novalidate-casing/main.svelte | 6 ++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .changeset/pink-wolves-search.md create mode 100644 packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte diff --git a/.changeset/pink-wolves-search.md b/.changeset/pink-wolves-search.md new file mode 100644 index 0000000000..cd734062c6 --- /dev/null +++ b/.changeset/pink-wolves-search.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly handle `novalidate` attribute casing diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index 76486d32ac..b16c0551f1 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -196,7 +196,8 @@ const ATTRIBUTE_ALIASES = { readonly: 'readOnly', defaultvalue: 'defaultValue', defaultchecked: 'defaultChecked', - srcobject: 'srcObject' + srcobject: 'srcObject', + novalidate: 'noValidate' }; /** diff --git a/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js new file mode 100644 index 0000000000..4fdf3632d6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + html: ` +
+
+` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte new file mode 100644 index 0000000000..1e8115ff62 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte @@ -0,0 +1,6 @@ + + +
+
From 8bba70b8e30a824218f909a36f9dd255fae9710f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 22 Jan 2025 10:37:43 -0500 Subject: [PATCH 05/67] fix: avoid double deriveds in component props (#15089) * fix: prevent double deriveds in component props * unused * reuse mechanism * move code to where it is used * changeset --- .changeset/tasty-pigs-greet.md | 5 ++ .../phases/3-transform/client/utils.js | 35 -------------- .../3-transform/client/visitors/AwaitBlock.js | 41 +++++++++++++++- .../client/visitors/RegularElement.js | 8 ++-- .../client/visitors/SlotElement.js | 6 ++- .../client/visitors/shared/component.js | 47 +++++++++---------- .../client/visitors/shared/element.js | 23 ++++----- .../client/visitors/shared/utils.js | 15 +++--- 8 files changed, 93 insertions(+), 87 deletions(-) create mode 100644 .changeset/tasty-pigs-greet.md diff --git a/.changeset/tasty-pigs-greet.md b/.changeset/tasty-pigs-greet.md new file mode 100644 index 0000000000..63decceed8 --- /dev/null +++ b/.changeset/tasty-pigs-greet.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: avoid double deriveds in component props 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 c59a5544df..421118cf68 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -269,41 +269,6 @@ export function should_proxy(node, scope) { return true; } -/** - * @param {Pattern} node - * @param {import('zimmerframe').Context} context - * @returns {{ id: Pattern, declarations: null | Statement[] }} - */ -export function create_derived_block_argument(node, context) { - if (node.type === 'Identifier') { - context.state.transform[node.name] = { read: get_value }; - return { id: node, declarations: null }; - } - - const pattern = /** @type {Pattern} */ (context.visit(node)); - const identifiers = extract_identifiers(node); - - const id = b.id('$$source'); - const value = b.id('$$value'); - - const block = b.block([ - b.var(pattern, b.call('$.get', id)), - b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier)))) - ]); - - const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))]; - - for (const id of identifiers) { - context.state.transform[id.name] = { read: get_value }; - - declarations.push( - b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id)))) - ); - } - - return { id, declarations }; -} - /** * Svelte legacy mode should use safe equals in most places, runes mode shouldn't * @param {ComponentClientTransformState} state 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 146f75d405..e0aef2d316 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 @@ -1,8 +1,10 @@ /** @import { BlockStatement, Expression, Pattern, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ -/** @import { ComponentContext } from '../types' */ +/** @import { ComponentClientTransformState, ComponentContext } from '../types' */ +import { extract_identifiers } from '../../../../utils/ast.js'; import * as b from '../../../../utils/builders.js'; -import { create_derived_block_argument } from '../utils.js'; +import { create_derived } from '../utils.js'; +import { get_value } from './shared/declarations.js'; /** * @param {AST.AwaitBlock} node @@ -65,3 +67,38 @@ export function AwaitBlock(node, context) { ) ); } + +/** + * @param {Pattern} node + * @param {import('zimmerframe').Context} context + * @returns {{ id: Pattern, declarations: null | Statement[] }} + */ +function create_derived_block_argument(node, context) { + if (node.type === 'Identifier') { + context.state.transform[node.name] = { read: get_value }; + return { id: node, declarations: null }; + } + + const pattern = /** @type {Pattern} */ (context.visit(node)); + const identifiers = extract_identifiers(node); + + const id = b.id('$$source'); + const value = b.id('$$value'); + + const block = b.block([ + b.var(pattern, b.call('$.get', id)), + b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier)))) + ]); + + const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))]; + + for (const id of identifiers) { + context.state.transform[id.name] = { read: get_value }; + + declarations.push( + b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id)))) + ); + } + + return { id, declarations }; +} 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 21a78de032..d0a148f216 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 @@ -537,8 +537,8 @@ function build_element_attribute_update_assignment( const is_svg = context.state.metadata.namespace === 'svg' || element.name === 'svg'; const is_mathml = context.state.metadata.namespace === 'mathml'; - let { value, has_state } = build_attribute_value(attribute.value, context, (value) => - get_expression_id(state, value) + let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => + metadata.has_call ? get_expression_id(state, value) : value ); if (name === 'autofocus') { @@ -665,8 +665,8 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co */ function build_element_special_value_attribute(element, node_id, attribute, context) { const state = context.state; - const { value, has_state } = build_attribute_value(attribute.value, context, (value) => - get_expression_id(state, value) + const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => + metadata.has_call ? get_expression_id(state, value) : value ); const inner_assignment = b.assignment( 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 f1b08acbc6..fdd705e32e 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 @@ -30,8 +30,10 @@ export function SlotElement(node, context) { if (attribute.type === 'SpreadAttribute') { spreads.push(b.thunk(/** @type {Expression} */ (context.visit(attribute)))); } else if (attribute.type === 'Attribute') { - const { value, has_state } = build_attribute_value(attribute.value, context, (value) => - memoize_expression(context.state, value) + const { value, has_state } = build_attribute_value( + attribute.value, + context, + (value, metadata) => (metadata.has_call ? memoize_expression(context.state, value) : value) ); if (attribute.name === 'name') { 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 9ac0bac120..15e4f68e9e 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 @@ -4,7 +4,6 @@ import { dev, is_ignored } from '../../../../../state.js'; import { get_attribute_chunks, object } from '../../../../../utils/ast.js'; import * as b from '../../../../../utils/builders.js'; -import { create_derived } from '../../utils.js'; import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js'; import { build_attribute_value } from '../shared/element.js'; import { build_event_handler } from './events.js'; @@ -134,9 +133,9 @@ export function build_component(node, component_name, context, anchor = context. custom_css_props.push( b.init( attribute.name, - build_attribute_value(attribute.value, context, (value) => + build_attribute_value(attribute.value, context, (value, metadata) => // TODO put the derived in the local block - memoize_expression(context.state, value) + metadata.has_call ? memoize_expression(context.state, value) : value ).value ) ); @@ -151,31 +150,29 @@ export function build_component(node, component_name, context, anchor = context. has_children_prop = true; } - const { value, has_state } = build_attribute_value(attribute.value, context, (value) => - memoize_expression(context.state, value) - ); - - if (has_state) { - let arg = value; - - // When we have a non-simple computation, anything other than an Identifier or Member expression, - // then there's a good chance it needs to be memoized to avoid over-firing when read within the - // child component. - const should_wrap_in_derived = get_attribute_chunks(attribute.value).some((n) => { - return ( - n.type === 'ExpressionTag' && - n.expression.type !== 'Identifier' && - n.expression.type !== 'MemberExpression' - ); - }); + const { value, has_state } = build_attribute_value( + attribute.value, + context, + (value, metadata) => { + if (!metadata.has_state) return value; + + // When we have a non-simple computation, anything other than an Identifier or Member expression, + // then there's a good chance it needs to be memoized to avoid over-firing when read within the + // child component (e.g. `active={i === index}`) + const should_wrap_in_derived = get_attribute_chunks(attribute.value).some((n) => { + return ( + n.type === 'ExpressionTag' && + n.expression.type !== 'Identifier' && + n.expression.type !== 'MemberExpression' + ); + }); - if (should_wrap_in_derived) { - const id = b.id(context.state.scope.generate(attribute.name)); - context.state.init.push(b.var(id, create_derived(context.state, b.thunk(value)))); - arg = b.call('$.get', id); + return should_wrap_in_derived ? memoize_expression(context.state, value) : value; } + ); - push_prop(b.get(attribute.name, [b.return(arg)])); + if (has_state) { + push_prop(b.get(attribute.name, [b.return(value)])); } else { push_prop(b.init(attribute.name, value)); } 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 8fb6b8bdde..abffad0ff7 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,11 +1,11 @@ /** @import { Expression, Identifier, ObjectExpression } from 'estree' */ -/** @import { AST } from '#compiler' */ +/** @import { AST, ExpressionMetadata } from '#compiler' */ /** @import { ComponentClientTransformState, ComponentContext } from '../../types' */ 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, create_derived } from '../../utils.js'; +import { build_getter } from '../../utils.js'; import { build_template_chunk, get_expression_id } from './utils.js'; /** @@ -35,8 +35,10 @@ export function build_set_attributes( for (const attribute of attributes) { if (attribute.type === 'Attribute') { - const { value, has_state } = build_attribute_value(attribute.value, context, (value) => - get_expression_id(context.state, value) + const { value, has_state } = build_attribute_value( + attribute.value, + context, + (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value) ); if ( @@ -59,10 +61,9 @@ export function build_set_attributes( let value = /** @type {Expression} */ (context.visit(attribute)); if (attribute.metadata.expression.has_call) { - const id = b.id(state.scope.generate('spread_with_call')); - state.init.push(b.const(id, create_derived(state, b.thunk(value)))); - value = b.call('$.get', id); + value = get_expression_id(context.state, value); } + values.push(b.spread(value)); } } @@ -111,8 +112,8 @@ export function build_style_directives( let value = directive.value === true ? build_getter({ name: directive.name, type: 'Identifier' }, context.state) - : build_attribute_value(directive.value, context, (value) => - get_expression_id(context.state, value) + : build_attribute_value(directive.value, context, (value, metadata) => + metadata.has_call ? get_expression_id(context.state, value) : value ).value; const update = b.stmt( @@ -169,7 +170,7 @@ export function build_class_directives( /** * @param {AST.Attribute['value']} value * @param {ComponentContext} context - * @param {(value: Expression) => Expression} memoize + * @param {(value: Expression, metadata: ExpressionMetadata) => Expression} memoize * @returns {{ value: Expression, has_state: boolean }} */ export function build_attribute_value(value, context, memoize = (value) => value) { @@ -187,7 +188,7 @@ export function build_attribute_value(value, context, memoize = (value) => value let expression = /** @type {Expression} */ (context.visit(chunk.expression)); return { - value: chunk.metadata.expression.has_call ? memoize(expression) : expression, + value: memoize(expression, chunk.metadata.expression), has_state: chunk.metadata.expression.has_state }; } 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 c4f81274d9..4c28616665 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,5 +1,5 @@ /** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */ -/** @import { AST } from '#compiler' */ +/** @import { AST, ExpressionMetadata } from '#compiler' */ /** @import { ComponentClientTransformState } from '../../types' */ import { walk } from 'zimmerframe'; import { object } from '../../../../../utils/ast.js'; @@ -79,14 +79,14 @@ function compare_expressions(a, b) { * @param {Array} values * @param {(node: AST.SvelteNode, state: any) => any} visit * @param {ComponentClientTransformState} state - * @param {(value: Expression) => Expression} memoize + * @param {(value: Expression, metadata: ExpressionMetadata) => Expression} memoize * @returns {{ value: Expression, has_state: boolean }} */ export function build_template_chunk( values, visit, state, - memoize = (value) => get_expression_id(state, value) + memoize = (value, metadata) => (metadata.has_call ? get_expression_id(state, value) : value) ) { /** @type {Expression[]} */ const expressions = []; @@ -106,14 +106,13 @@ export function build_template_chunk( quasi.value.cooked += node.expression.value + ''; } } else { - let value = /** @type {Expression} */ (visit(node.expression, state)); + let value = memoize( + /** @type {Expression} */ (visit(node.expression, state)), + node.metadata.expression + ); has_state ||= node.metadata.expression.has_state; - if (node.metadata.expression.has_call) { - value = memoize(value); - } - if (values.length === 1) { // If we have a single expression, then pass that in directly to possibly avoid doing // extra work in the template_effect (instead we do the work in set_text). From c2e805f05cb652ed3e7fa29f4a7c3f2912a79678 Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Wed, 22 Jan 2025 13:54:12 -0600 Subject: [PATCH 06/67] fix: consistently set value to blank string when value attribute is undefined (#15057) Fixes #4467 --- .changeset/dirty-sloths-move.md | 5 ++ .../client/dom/elements/attributes.js | 4 +- .../value-attribute-null-undefined/_config.js | 49 +++++++++++++++++++ .../main.svelte | 9 ++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 .changeset/dirty-sloths-move.md create mode 100644 packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte diff --git a/.changeset/dirty-sloths-move.md b/.changeset/dirty-sloths-move.md new file mode 100644 index 0000000000..07436a0b2e --- /dev/null +++ b/.changeset/dirty-sloths-move.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: consistently set value to blank string when value attribute is undefined diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index a2fffe8696..eab27e6c02 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -68,14 +68,14 @@ export function set_value(element, value) { // treat null and undefined the same for the initial value value ?? undefined) || // @ts-expect-error - // `progress` elements always need their value set when its `0` + // `progress` elements always need their value set when it's `0` (element.value === value && (value !== 0 || element.nodeName !== 'PROGRESS')) ) { return; } // @ts-expect-error - element.value = value; + element.value = value ?? ''; } /** diff --git a/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js new file mode 100644 index 0000000000..08f0c5aec7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js @@ -0,0 +1,49 @@ +import { test, ok } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + mode: ['client'], + + async test({ assert, target }) { + /** + * @type {HTMLInputElement | null} + */ + const input = target.querySelector('input[type=text]'); + /** + * @type {HTMLButtonElement | null} + */ + const setString = target.querySelector('#setString'); + /** + * @type {HTMLButtonElement | null} + */ + const setNull = target.querySelector('#setNull'); + /** + * @type {HTMLButtonElement | null} + */ + const setUndefined = target.querySelector('#setUndefined'); + + ok(input); + ok(setString); + ok(setNull); + ok(setUndefined); + + // value should always be blank string when value attribute is set to null or undefined + + assert.equal(input.value, ''); + setString.click(); + flushSync(); + assert.equal(input.value, 'foo'); + + setNull.click(); + flushSync(); + assert.equal(input.value, ''); + + setString.click(); + flushSync(); + assert.equal(input.value, 'foo'); + + setUndefined.click(); + flushSync(); + assert.equal(input.value, ''); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte new file mode 100644 index 0000000000..9f944923c2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte @@ -0,0 +1,9 @@ + + + + + + + From 7740d4576b95f2e4e4e95a9df2d5504cb12a9461 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 22 Jan 2025 15:48:41 -0500 Subject: [PATCH 07/67] fix: optimise || expressions in template (#15092) --- .changeset/large-islands-cover.md | 5 ++++ .../client/visitors/shared/utils.js | 26 +++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 .changeset/large-islands-cover.md diff --git a/.changeset/large-islands-cover.md b/.changeset/large-islands-cover.md new file mode 100644 index 0000000000..969d2fc254 --- /dev/null +++ b/.changeset/large-islands-cover.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: optimise || expressions in template 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 4c28616665..00634f229e 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 @@ -118,22 +118,22 @@ export function build_template_chunk( // extra work in the template_effect (instead we do the work in set_text). return { value, has_state }; } else { - let expression = value; - // only add nullish coallescence if it hasn't been added already - if (value.type === 'LogicalExpression' && value.operator === '??') { - const { right } = value; - // `undefined` isn't a Literal (due to pre-ES5 shenanigans), so the only nullish literal is `null` - // however, you _can_ make a variable called `undefined` in a Svelte component, so we can't just treat it the same way - if (right.type !== 'Literal') { - expression = b.logical('??', value, b.literal('')); - } else if (right.value === null) { - // if they do something weird like `stuff ?? null`, replace `null` with empty string - value.right = b.literal(''); + // add `?? ''` where necessary (TODO optimise more cases) + if ( + value.type === 'LogicalExpression' && + value.right.type === 'Literal' && + (value.operator === '??' || value.operator === '||') + ) { + // `foo ?? null` -=> `foo ?? ''` + // otherwise leave the expression untouched + if (value.right.value === null) { + value = { ...value, right: b.literal('') }; } } else { - expression = b.logical('??', value, b.literal('')); + value = b.logical('??', value, b.literal('')); } - expressions.push(expression); + + expressions.push(value); } quasi = b.quasi('', i + 1 === values.length); From d17f5c748d4b842e63d0017461ccde49b00b78e0 Mon Sep 17 00:00:00 2001 From: Edoardo Cavazza Date: Wed, 22 Jan 2025 21:51:26 +0100 Subject: [PATCH 08/67] fix: add check for is attribute (#15086) Fixes #15085 Add a check for is attribute in is_custom_element_node function. --- .changeset/two-doors-exercise.md | 5 +++++ .../client/visitors/RegularElement.js | 2 +- .../client/visitors/shared/fragment.js | 3 ++- packages/svelte/src/compiler/phases/nodes.js | 8 ++++++-- .../custom-element-attributes/_config.js | 19 +++++++++++++++++++ .../custom-element-attributes/main.svelte | 2 ++ 6 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .changeset/two-doors-exercise.md create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte diff --git a/.changeset/two-doors-exercise.md b/.changeset/two-doors-exercise.md new file mode 100644 index 0000000000..b41e582ae7 --- /dev/null +++ b/.changeset/two-doors-exercise.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: add check for `is` attribute to correctly detect custom elements 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 d0a148f216..c7e218d521 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 @@ -227,7 +227,7 @@ export function RegularElement(node, context) { node_id, attributes_id, (node.metadata.svg || node.metadata.mathml || is_custom_element_node(node)) && b.true, - node.name.includes('-') && b.true, + is_custom_element_node(node) && b.true, context.state ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index 0b4ac87342..5bc3041ca4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -4,6 +4,7 @@ import { cannot_be_set_statically } from '../../../../../../utils.js'; import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js'; import * as b from '../../../../../utils/builders.js'; +import { is_custom_element_node } from '../../../../nodes.js'; import { build_template_chunk } from './utils.js'; /** @@ -128,7 +129,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) { function is_static_element(node, state) { if (node.type !== 'RegularElement') return false; if (node.fragment.metadata.dynamic) return false; - if (node.name.includes('-')) return false; // we're setting all attributes on custom elements through properties + if (is_custom_element_node(node)) return false; // we're setting all attributes on custom elements through properties for (const attribute of node.attributes) { if (attribute.type !== 'Attribute') { diff --git a/packages/svelte/src/compiler/phases/nodes.js b/packages/svelte/src/compiler/phases/nodes.js index 5ca4ce3380..003c3a2c49 100644 --- a/packages/svelte/src/compiler/phases/nodes.js +++ b/packages/svelte/src/compiler/phases/nodes.js @@ -23,10 +23,14 @@ export function is_element_node(node) { /** * @param {AST.RegularElement | AST.SvelteElement} node - * @returns {node is AST.RegularElement} + * @returns {boolean} */ export function is_custom_element_node(node) { - return node.type === 'RegularElement' && node.name.includes('-'); + return ( + node.type === 'RegularElement' && + (node.name.includes('-') || + node.attributes.some((attr) => attr.type === 'Attribute' && attr.name === 'is')) + ); } /** diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js new file mode 100644 index 0000000000..118a51157e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js @@ -0,0 +1,19 @@ +import { test } from '../../test'; + +export default test({ + mode: ['client', 'server'], + async test({ assert, target }) { + const my_element = /** @type HTMLElement & { object: { test: true }; } */ ( + target.querySelector('my-element') + ); + const my_link = /** @type HTMLAnchorElement & { object: { test: true }; } */ ( + target.querySelector('a') + ); + assert.equal(my_element.getAttribute('string'), 'test'); + assert.equal(my_element.hasAttribute('object'), false); + assert.deepEqual(my_element.object, { test: true }); + assert.equal(my_link.getAttribute('string'), 'test'); + assert.equal(my_link.hasAttribute('object'), false); + assert.deepEqual(my_link.object, { test: true }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte new file mode 100644 index 0000000000..ff94a9484c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte @@ -0,0 +1,2 @@ + + From d47f4f59084a26d8b47d864e961ddf76336a0ed8 Mon Sep 17 00:00:00 2001 From: "Trevor N. Suarez" Date: Thu, 23 Jan 2025 00:55:12 -0700 Subject: [PATCH 09/67] fix: expand boolean attribute support (#15095) * Adding test for expected boolean attribute support * Improving support for more boolean attributes: Added: - `defer` - `disablepictureinpicture` - `disableremoteplayback` Improved: - `allowfullscreen` - `novalidate` * Skipping JSDOM version of boolean attribute test JSDOM lacks support for some attributes, so we'll skip it for now. See: - `async`: https://github.com/jsdom/jsdom/issues/1564 - `nomodule`: https://github.com/jsdom/jsdom/issues/2475 - `autofocus`: https://github.com/jsdom/jsdom/issues/3041 - `inert`: https://github.com/jsdom/jsdom/issues/3605 - etc...: https://github.com/jestjs/jest/issues/139#issuecomment-592673550 * Adding changeset --- .changeset/strange-hairs-trade.md | 5 ++ packages/svelte/src/utils.js | 16 ++++- .../_config.js | 60 +++++++++++++++++++ .../main.svelte | 22 +++++++ 4 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 .changeset/strange-hairs-trade.md create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/main.svelte diff --git a/.changeset/strange-hairs-trade.md b/.changeset/strange-hairs-trade.md new file mode 100644 index 0000000000..192e5ef3d1 --- /dev/null +++ b/.changeset/strange-hairs-trade.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: expand boolean attribute support diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index b16c0551f1..e8e1bc224c 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -170,7 +170,10 @@ const DOM_BOOLEAN_ATTRIBUTES = [ 'reversed', 'seamless', 'selected', - 'webkitdirectory' + 'webkitdirectory', + 'defer', + 'disablepictureinpicture', + 'disableremoteplayback' ]; /** @@ -197,7 +200,10 @@ const ATTRIBUTE_ALIASES = { defaultvalue: 'defaultValue', defaultchecked: 'defaultChecked', srcobject: 'srcObject', - novalidate: 'noValidate' + novalidate: 'noValidate', + allowfullscreen: 'allowFullscreen', + disablepictureinpicture: 'disablePictureInPicture', + disableremoteplayback: 'disableRemotePlayback' }; /** @@ -219,7 +225,11 @@ const DOM_PROPERTIES = [ 'volume', 'defaultValue', 'defaultChecked', - 'srcObject' + 'srcObject', + 'noValidate', + 'allowFullscreen', + 'disablePictureInPicture', + 'disableRemotePlayback' ]; /** diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js new file mode 100644 index 0000000000..d0b8a421b3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js @@ -0,0 +1,60 @@ +import { test } from '../../test'; + +export default test({ + // JSDOM lacks support for some of these attributes, so we'll skip it for now. + // + // See: + // - `async`: https://github.com/jsdom/jsdom/issues/1564 + // - `nomodule`: https://github.com/jsdom/jsdom/issues/2475 + // - `autofocus`: https://github.com/jsdom/jsdom/issues/3041 + // - `inert`: https://github.com/jsdom/jsdom/issues/3605 + // - etc...: https://github.com/jestjs/jest/issues/139#issuecomment-592673550 + skip_mode: ['client'], + + html: ` + +
+ + + + + + + + +
+
    +
    + + + +
    + + + + + + + + +
    +
      +
      + + + +
      + + + + + + + + +
      +
        +
        + +` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/main.svelte b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/main.svelte new file mode 100644 index 0000000000..e9e5a16168 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/main.svelte @@ -0,0 +1,22 @@ + + +{#each attributeValues as val} + +
        + + + + + + + + +
        +
          +
          + +{/each} From 3d828b46b24964163cbe184e683e1db9f4b0ddfd Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Thu, 23 Jan 2025 16:45:14 -0700 Subject: [PATCH 10/67] feat: Remove repository-sync workflows in favor of github-docs-bot (#15097) * Revert "fix: ensure untrack correctly retains the active reaction (#15065)" This reverts commit 2ad519542dcba2d7802394068d412bb73d9cb803. * feat: Remove repository-sync workflows in favor of github-docs-bot * Revert "Revert "fix: ensure untrack correctly retains the active reaction (#15065)"" This reverts commit f4e98bf89f80cc332703c1da3c2bd3d1451d1dce. --- .../workflows/docs-preview-create-request.yml | 26 ------------------ .../workflows/docs-preview-delete-request.yml | 27 ------------------- .github/workflows/sync-request.yml | 22 --------------- 3 files changed, 75 deletions(-) delete mode 100644 .github/workflows/docs-preview-create-request.yml delete mode 100644 .github/workflows/docs-preview-delete-request.yml delete mode 100644 .github/workflows/sync-request.yml diff --git a/.github/workflows/docs-preview-create-request.yml b/.github/workflows/docs-preview-create-request.yml deleted file mode 100644 index f57766dc36..0000000000 --- a/.github/workflows/docs-preview-create-request.yml +++ /dev/null @@ -1,26 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Docs preview create request - -on: - pull_request_target: - branches: - - main - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: docs-preview-create - client-payload: |- - { - "package": "svelte", - "repo": "${{ github.repository }}", - "owner": "${{ github.event.pull_request.head.repo.owner.login }}", - "branch": "${{ github.event.pull_request.head.ref }}", - "pr": ${{ github.event.pull_request.number }} - } diff --git a/.github/workflows/docs-preview-delete-request.yml b/.github/workflows/docs-preview-delete-request.yml deleted file mode 100644 index 4eb0e996a6..0000000000 --- a/.github/workflows/docs-preview-delete-request.yml +++ /dev/null @@ -1,27 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Docs preview delete request - -on: - pull_request_target: - branches: - - main - types: [closed] - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: docs-preview-delete - client-payload: |- - { - "package": "svelte", - "repo": "${{ github.repository }}", - "owner": "${{ github.event.pull_request.head.repo.owner.login }}", - "branch": "${{ github.event.pull_request.head.ref }}", - "pr": ${{ github.event.pull_request.number }} - } diff --git a/.github/workflows/sync-request.yml b/.github/workflows/sync-request.yml deleted file mode 100644 index de2ce77692..0000000000 --- a/.github/workflows/sync-request.yml +++ /dev/null @@ -1,22 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Sync request - -on: - push: - branches: - - main - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: sync-request - client-payload: |- - { - "package": "svelte" - } From e9cc7dc9a17392651512223c2fde7fe6d69eda72 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jan 2025 22:22:14 -0500 Subject: [PATCH 11/67] chore: tweak each block code (#15094) --- .../3-transform/client/visitors/EachBlock.js | 36 ++++++++++++------- packages/svelte/src/compiler/phases/scope.js | 11 ------ .../svelte/src/compiler/types/template.d.ts | 2 -- .../src/internal/client/dom/blocks/each.js | 6 ++-- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index 9f70981205..24a696e7d2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -35,10 +35,6 @@ export function EachBlock(node, context) { context.state.template.push(''); } - if (each_node_meta.array_name !== null) { - context.state.init.push(b.const(each_node_meta.array_name, b.thunk(collection))); - } - let flags = 0; if (node.metadata.keyed && node.index) { @@ -120,8 +116,21 @@ export function EachBlock(node, context) { return [array, ...transitive_dependencies]; }); - if (each_node_meta.array_name) { - indirect_dependencies.push(b.call(each_node_meta.array_name)); + /** @type {Identifier | null} */ + let collection_id = null; + + // Check if inner scope shadows something from outer scope. + // This is necessary because we need access to the array expression of the each block + // in the inner scope if bindings are used, in order to invalidate the array. + for (const [name] of context.state.scope.declarations) { + if (context.state.scope.parent?.get(name) != null) { + collection_id = context.state.scope.root.unique('$$array'); + break; + } + } + + if (collection_id) { + indirect_dependencies.push(b.call(collection_id)); } else { indirect_dependencies.push(collection); @@ -195,7 +204,7 @@ export function EachBlock(node, context) { // TODO 6.0 this only applies in legacy mode, reassignments are // forbidden in runes mode return b.member( - each_node_meta.array_name ? b.call(each_node_meta.array_name) : collection, + collection_id ? b.call(collection_id) : collection, (flags & EACH_INDEX_REACTIVE) !== 0 ? get_value(index) : index, true ); @@ -207,7 +216,7 @@ export function EachBlock(node, context) { uses_index = true; const left = b.member( - each_node_meta.array_name ? b.call(each_node_meta.array_name) : collection, + collection_id ? b.call(collection_id) : collection, (flags & EACH_INDEX_REACTIVE) !== 0 ? get_value(index) : index, true ); @@ -283,16 +292,17 @@ export function EachBlock(node, context) { ); } + const render_args = [b.id('$$anchor'), item]; + if (uses_index || collection_id) render_args.push(index); + if (collection_id) render_args.push(collection_id); + /** @type {Expression[]} */ const args = [ context.state.node, b.literal(flags), - each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection), + b.thunk(collection), key_function, - b.arrow( - uses_index ? [b.id('$$anchor'), item, index] : [b.id('$$anchor'), item], - b.block(declarations.concat(block.body)) - ) + b.arrow(render_args, b.block(declarations.concat(block.body))) ]; if (node.fallback) { diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 3536dd6a18..51e9eb088d 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -575,21 +575,10 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } if (node.fallback) visit(node.fallback, { scope }); - // Check if inner scope shadows something from outer scope. - // This is necessary because we need access to the array expression of the each block - // in the inner scope if bindings are used, in order to invalidate the array. - let needs_array_deduplication = false; - for (const [name] of scope.declarations) { - if (state.scope.get(name) !== null) { - needs_array_deduplication = true; - } - } - node.metadata = { expression: create_expression_metadata(), keyed: false, contains_group_binding: false, - array_name: needs_array_deduplication ? state.scope.root.unique('$$array') : null, index: scope.root.unique('$$index'), declarations: scope.declarations, is_controlled: false diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index fb60966895..1f266c5248 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -414,8 +414,6 @@ export namespace AST { expression: ExpressionMetadata; keyed: boolean; contains_group_binding: boolean; - /** Set if something in the array expression is shadowed within the each block */ - array_name: Identifier | null; index: Identifier; declarations: Map; /** diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index b17090948a..040e585215 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -271,7 +271,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f * @param {Array} array * @param {EachState} state * @param {Element | Comment | Text} anchor - * @param {(anchor: Node, item: MaybeSource, index: number | Source) => void} render_fn + * @param {(anchor: Node, item: MaybeSource, index: number | Source, collection: () => V[]) => void} render_fn * @param {number} flags * @param {boolean} is_inert * @param {(value: V, index: number) => any} get_key @@ -510,7 +510,7 @@ function update_item(item, value, index, type) { * @param {V} value * @param {unknown} key * @param {number} index - * @param {(anchor: Node, item: V | Source, index: number | Value) => void} render_fn + * @param {(anchor: Node, item: V | Source, index: number | Value, collection: () => V[]) => void} render_fn * @param {number} flags * @param {() => V[]} get_collection * @returns {EachItem} @@ -559,7 +559,7 @@ function create_item( current_each_item = item; try { - item.e = branch(() => render_fn(anchor, v, i), hydrating); + item.e = branch(() => render_fn(anchor, v, i, get_collection), hydrating); item.e.prev = prev && prev.e; item.e.next = next && next.e; From 24b6fab58b92e02703f9200d727d124493a020c9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Jan 2025 18:49:11 -0500 Subject: [PATCH 12/67] chore: construct analysis before module walk (#15108) --- .../src/compiler/phases/2-analyze/index.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 76c1e94277..c65eb5f955 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -243,7 +243,15 @@ export function analyze_module(ast, options) { } } - const analysis = { runes: true, tracing: false }; + /** @type {Analysis} */ + const analysis = { + module: { ast, scope, scopes }, + name: options.filename, + accessors: false, + runes: true, + immutable: true, + tracing: false + }; walk( /** @type {Node} */ (ast), @@ -256,14 +264,7 @@ export function analyze_module(ast, options) { visitors ); - return { - module: { ast, scope, scopes }, - name: options.filename, - accessors: false, - runes: true, - immutable: true, - tracing: analysis.tracing - }; + return analysis; } /** From e3d4a0fa8eb76c4f63d3f7c62fe768001972d3c8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Jan 2025 19:02:43 -0500 Subject: [PATCH 13/67] chore: tweak render tag logic (#15109) --- .../src/compiler/phases/1-parse/state/tag.js | 2 +- .../src/compiler/phases/2-analyze/index.js | 2 -- .../src/compiler/phases/2-analyze/types.d.ts | 2 -- .../2-analyze/visitors/CallExpression.js | 12 ------------ .../phases/2-analyze/visitors/RenderTag.js | 16 ++++++++++++++-- .../3-transform/client/visitors/RenderTag.js | 18 +++++++++++------- .../svelte/src/compiler/types/template.d.ts | 2 +- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 95d7d00677..0eb98c27e8 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -706,7 +706,7 @@ function special(parser) { expression: /** @type {AST.RenderTag['expression']} */ (expression), metadata: { dynamic: false, - args_with_call_expression: new Set(), + arguments: [], path: [], snippets: new Set() } diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index c65eb5f955..bce6672505 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -605,7 +605,6 @@ export function analyze_component(root, source, options) { has_props_rune: false, component_slots: new Set(), expression: null, - render_tag: null, private_derived_state: [], function_depth: scope.function_depth, instance_scope: instance.scope, @@ -677,7 +676,6 @@ export function analyze_component(root, source, options) { reactive_statements: analysis.reactive_statements, component_slots: new Set(), expression: null, - render_tag: null, private_derived_state: [], function_depth: scope.function_depth }; diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index b4ca4dc262..1e71accb9f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -19,8 +19,6 @@ export interface AnalysisState { component_slots: Set; /** Information about the current expression/directive/block value */ expression: ExpressionMetadata | null; - /** The current {@render ...} tag, if any */ - render_tag: null | AST.RenderTag; private_derived_state: string[]; function_depth: number; 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 9f51cd61de..96788d9f93 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -186,18 +186,6 @@ export function CallExpression(node, context) { break; } - if (context.state.render_tag) { - // Find out which of the render tag arguments contains this call expression - const arg_idx = unwrap_optional(context.state.render_tag.expression).arguments.findIndex( - (arg) => arg === node || context.path.includes(arg) - ); - - // -1 if this is the call expression of the render tag itself - if (arg_idx !== -1) { - context.state.render_tag.metadata.args_with_call_expression.add(arg_idx); - } - } - if (node.callee.type === 'Identifier') { const binding = context.state.scope.get(node.callee.name); diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js index 045224276a..a8c9d408bd 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js @@ -5,6 +5,7 @@ import * as e from '../../../errors.js'; import { validate_opening_tag } from './shared/utils.js'; import { mark_subtree_dynamic } from './shared/fragment.js'; import { is_resolved_snippet } from './shared/snippets.js'; +import { create_expression_metadata } from '../../nodes.js'; /** * @param {AST.RenderTag} node @@ -15,7 +16,8 @@ export function RenderTag(node, context) { node.metadata.path = [...context.path]; - const callee = unwrap_optional(node.expression).callee; + const expression = unwrap_optional(node.expression); + const callee = expression.callee; const binding = callee.type === 'Identifier' ? context.state.scope.get(callee.name) : null; @@ -52,5 +54,15 @@ export function RenderTag(node, context) { mark_subtree_dynamic(context.path); - context.next({ ...context.state, render_tag: node }); + context.visit(callee); + + for (const arg of expression.arguments) { + const metadata = create_expression_metadata(); + node.metadata.arguments.push(metadata); + + context.visit(arg, { + ...context.state, + expression: metadata + }); + } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js index 7da987f6cc..33ae6d4d2b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js @@ -10,20 +10,24 @@ import * as b from '../../../../utils/builders.js'; */ export function RenderTag(node, context) { context.state.template.push(''); - const callee = unwrap_optional(node.expression).callee; - const raw_args = unwrap_optional(node.expression).arguments; + + const expression = unwrap_optional(node.expression); + + const callee = expression.callee; + const raw_args = expression.arguments; /** @type {Expression[]} */ let args = []; for (let i = 0; i < raw_args.length; i++) { - const raw = raw_args[i]; - const arg = /** @type {Expression} */ (context.visit(raw)); - if (node.metadata.args_with_call_expression.has(i)) { + let thunk = b.thunk(/** @type {Expression} */ (context.visit(raw_args[i]))); + const { has_call } = node.metadata.arguments[i]; + + if (has_call) { const id = b.id(context.state.scope.generate('render_arg')); - context.state.init.push(b.var(id, b.call('$.derived_safe_equal', b.thunk(arg)))); + context.state.init.push(b.var(id, b.call('$.derived_safe_equal', thunk))); args.push(b.thunk(b.call('$.get', id))); } else { - args.push(b.thunk(arg)); + args.push(thunk); } } diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 1f266c5248..a544cd1dec 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -166,7 +166,7 @@ export namespace AST { /** @internal */ metadata: { dynamic: boolean; - args_with_call_expression: Set; + arguments: ExpressionMetadata[]; path: SvelteNode[]; /** The set of locally-defined snippets that this render tag could correspond to, * used for CSS pruning purposes */ From 357e1a74b4af25c3e3839d303cc3d8d239d43087 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 19:03:51 -0500 Subject: [PATCH 14/67] Version Packages (#15088) Co-authored-by: github-actions[bot] --- .changeset/clever-cherries-hear.md | 5 ----- .changeset/dirty-sloths-move.md | 5 ----- .changeset/large-islands-cover.md | 5 ----- .changeset/pink-wolves-search.md | 5 ----- .changeset/strange-hairs-trade.md | 5 ----- .changeset/tasty-pigs-greet.md | 5 ----- .changeset/two-doors-exercise.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/clever-cherries-hear.md delete mode 100644 .changeset/dirty-sloths-move.md delete mode 100644 .changeset/large-islands-cover.md delete mode 100644 .changeset/pink-wolves-search.md delete mode 100644 .changeset/strange-hairs-trade.md delete mode 100644 .changeset/tasty-pigs-greet.md delete mode 100644 .changeset/two-doors-exercise.md diff --git a/.changeset/clever-cherries-hear.md b/.changeset/clever-cherries-hear.md deleted file mode 100644 index df37943b46..0000000000 --- a/.changeset/clever-cherries-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't throw for `undefined` non delegated event handlers diff --git a/.changeset/dirty-sloths-move.md b/.changeset/dirty-sloths-move.md deleted file mode 100644 index 07436a0b2e..0000000000 --- a/.changeset/dirty-sloths-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"svelte": patch ---- - -fix: consistently set value to blank string when value attribute is undefined diff --git a/.changeset/large-islands-cover.md b/.changeset/large-islands-cover.md deleted file mode 100644 index 969d2fc254..0000000000 --- a/.changeset/large-islands-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: optimise || expressions in template diff --git a/.changeset/pink-wolves-search.md b/.changeset/pink-wolves-search.md deleted file mode 100644 index cd734062c6..0000000000 --- a/.changeset/pink-wolves-search.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly handle `novalidate` attribute casing diff --git a/.changeset/strange-hairs-trade.md b/.changeset/strange-hairs-trade.md deleted file mode 100644 index 192e5ef3d1..0000000000 --- a/.changeset/strange-hairs-trade.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: expand boolean attribute support diff --git a/.changeset/tasty-pigs-greet.md b/.changeset/tasty-pigs-greet.md deleted file mode 100644 index 63decceed8..0000000000 --- a/.changeset/tasty-pigs-greet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: avoid double deriveds in component props diff --git a/.changeset/two-doors-exercise.md b/.changeset/two-doors-exercise.md deleted file mode 100644 index b41e582ae7..0000000000 --- a/.changeset/two-doors-exercise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"svelte": patch ---- - -fix: add check for `is` attribute to correctly detect custom elements diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index e15c0e0a09..ba912c1dd8 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,23 @@ # svelte +## 5.19.3 + +### Patch Changes + +- fix: don't throw for `undefined` non delegated event handlers ([#15087](https://github.com/sveltejs/svelte/pull/15087)) + +- fix: consistently set value to blank string when value attribute is undefined ([#15057](https://github.com/sveltejs/svelte/pull/15057)) + +- fix: optimise || expressions in template ([#15092](https://github.com/sveltejs/svelte/pull/15092)) + +- fix: correctly handle `novalidate` attribute casing ([#15083](https://github.com/sveltejs/svelte/pull/15083)) + +- fix: expand boolean attribute support ([#15095](https://github.com/sveltejs/svelte/pull/15095)) + +- fix: avoid double deriveds in component props ([#15089](https://github.com/sveltejs/svelte/pull/15089)) + +- fix: add check for `is` attribute to correctly detect custom elements ([#15086](https://github.com/sveltejs/svelte/pull/15086)) + ## 5.19.2 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 48287cf901..76ce768cb5 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.19.2", + "version": "5.19.3", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 7a3f016aaa..0e5fec3b81 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.19.2'; +export const VERSION = '5.19.3'; export const PUBLIC_VERSION = '5'; From 94d80c6e2c456d79a283164ae11f3991745be97d Mon Sep 17 00:00:00 2001 From: Shay Molcho <152275799+shaymolcho@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:59:58 +0200 Subject: [PATCH 15/67] Added missing period for consistency and readability (#15114) Added a missing period in a specific part of the text to maintain consistency across the document. This ensures a uniform writing style, improves readability, and aligns with the formatting used throughout the content. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7d15f905e..0e2628f84f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ We use [GitHub issues](https://github.com/sveltejs/svelte/issues) for our public If you have questions about using Svelte, contact us on Discord at [svelte.dev/chat](https://svelte.dev/chat), and we will do our best to answer your questions. -If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml) +If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml). ### Reporting new issues From 0e0f01ee1cfe11461f799c034030840a1b7cd763 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Jan 2025 08:13:13 -0500 Subject: [PATCH 16/67] fix: disallow $state/$derived in const tags (#15115) --- .changeset/strong-shoes-whisper.md | 5 +++++ .../phases/2-analyze/visitors/CallExpression.js | 3 ++- .../const-tag-invalid-rune-usage/errors.json | 14 ++++++++++++++ .../const-tag-invalid-rune-usage/input.svelte | 3 +++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .changeset/strong-shoes-whisper.md create mode 100644 packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json create mode 100644 packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte diff --git a/.changeset/strong-shoes-whisper.md b/.changeset/strong-shoes-whisper.md new file mode 100644 index 0000000000..ea7f8d6c5e --- /dev/null +++ b/.changeset/strong-shoes-whisper.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: disallow $state/$derived in const tags 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 96788d9f93..0a6b3f3ee5 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -79,7 +79,8 @@ export function CallExpression(node, context) { case '$derived': case '$derived.by': if ( - parent.type !== 'VariableDeclarator' && + (parent.type !== 'VariableDeclarator' || + get_parent(context.path, -3).type === 'ConstTag') && !(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed) ) { e.state_invalid_placement(node, rune); diff --git a/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json new file mode 100644 index 0000000000..32594e4268 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "state_invalid_placement", + "message": "`$derived(...)` can only be used as a variable declaration initializer or a class field", + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 26 + } + } +] diff --git a/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte new file mode 100644 index 0000000000..a056058cc5 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte @@ -0,0 +1,3 @@ +{#snippet test()} + {@const der = $derived(0)} +{/snippet} \ No newline at end of file From 3f8ce21f4709fe555fcc5ffc63728c6e8d139781 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Mon, 27 Jan 2025 13:55:49 -0700 Subject: [PATCH 17/67] fix: Add `bind:focused` to `HTMLAttributes` (#15122) * Revert "fix: ensure untrack correctly retains the active reaction (#15065)" This reverts commit 2ad519542dcba2d7802394068d412bb73d9cb803. * feat: Add `bind:focused` to `HTMLAttributes` * Revert "Revert "fix: ensure untrack correctly retains the active reaction (#15065)"" This reverts commit f4e98bf89f80cc332703c1da3c2bd3d1451d1dce. * changeset * tests --- .changeset/eleven-scissors-cheat.md | 5 +++++ packages/svelte/elements.d.ts | 1 + packages/svelte/tests/types/bindings.svelte | 8 ++++++++ 3 files changed, 14 insertions(+) create mode 100644 .changeset/eleven-scissors-cheat.md create mode 100644 packages/svelte/tests/types/bindings.svelte diff --git a/.changeset/eleven-scissors-cheat.md b/.changeset/eleven-scissors-cheat.md new file mode 100644 index 0000000000..5aa577d95d --- /dev/null +++ b/.changeset/eleven-scissors-cheat.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: Add `bind:focused` property to `HTMLAttributes` type diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 96f1589800..6d256b5620 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -839,6 +839,7 @@ export interface HTMLAttributes extends AriaAttributes, D readonly 'bind:contentBoxSize'?: Array | undefined | null; readonly 'bind:borderBoxSize'?: Array | undefined | null; readonly 'bind:devicePixelContentBoxSize'?: Array | undefined | null; + readonly 'bind:focused'?: boolean | undefined | null; // SvelteKit 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; diff --git a/packages/svelte/tests/types/bindings.svelte b/packages/svelte/tests/types/bindings.svelte new file mode 100644 index 0000000000..ce99b2c296 --- /dev/null +++ b/packages/svelte/tests/types/bindings.svelte @@ -0,0 +1,8 @@ + + + + + +
          From fc4dd2dec4387933579762f1cf4113a26cc1e22b Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 28 Jan 2025 13:15:12 +0000 Subject: [PATCH 18/67] fix: lazily connect derievds (in deriveds) to their parent (#15129) --- .changeset/few-beans-bow.md | 5 ++++ .../internal/client/reactivity/deriveds.js | 4 --- .../svelte/src/internal/client/runtime.js | 24 ++++++++++------- packages/svelte/tests/signals/test.ts | 27 +++++++++++++++++++ 4 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 .changeset/few-beans-bow.md diff --git a/.changeset/few-beans-bow.md b/.changeset/few-beans-bow.md new file mode 100644 index 0000000000..5c17c242a8 --- /dev/null +++ b/.changeset/few-beans-bow.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: lazily connect derievds (in deriveds) to their parent diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 7ec1ed30bd..da5a75eb6e 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -68,10 +68,6 @@ export function derived(fn) { signal.created = get_stack('CreatedAt'); } - if (parent_derived !== null) { - (parent_derived.children ??= []).push(signal); - } - return signal; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 1d695e1fee..a002665b51 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -957,26 +957,30 @@ export function get(signal) { new_deps.push(signal); } } - } else if (is_derived && /** @type {Derived} */ (signal).deps === null) { + } + + if ( + is_derived && + /** @type {Derived} */ (signal).deps === null && + (active_reaction === null || untracking || (active_reaction.f & DERIVED) !== 0) + ) { var derived = /** @type {Derived} */ (signal); var parent = derived.parent; - var target = derived; - while (parent !== null) { - // Attach the derived to the nearest parent effect, if there are deriveds - // in between then we also need to attach them too + if (parent !== null) { + // Attach the derived to the nearest parent effect or derived if ((parent.f & DERIVED) !== 0) { var parent_derived = /** @type {Derived} */ (parent); - target = parent_derived; - parent = parent_derived.parent; + if (!parent_derived.children?.includes(derived)) { + (parent_derived.children ??= []).push(derived); + } } else { var parent_effect = /** @type {Effect} */ (parent); - if (!parent_effect.deriveds?.includes(target)) { - (parent_effect.deriveds ??= []).push(target); + if (!parent_effect.deriveds?.includes(derived)) { + (parent_effect.deriveds ??= []).push(derived); } - break; } } } diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index e147fd1d0d..5f0b93e136 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -803,6 +803,33 @@ describe('signals', () => { }; }); + test('nested deriveds do not connect inside parent deriveds if unused', () => { + return () => { + let a = render_effect(() => {}); + let b: Derived | undefined; + + const destroy = effect_root(() => { + a = render_effect(() => { + $.untrack(() => { + b = derived(() => { + derived(() => {}); + derived(() => {}); + derived(() => {}); + }); + $.get(b); + }); + }); + }); + + assert.deepEqual(a.deriveds?.length, 1); + assert.deepEqual(b?.children, null); + + destroy(); + + assert.deepEqual(a.deriveds, null); + }; + }); + test('deriveds containing effects work correctly when used with untrack', () => { return () => { let a = render_effect(() => {}); From b2c8224a734511ad33e2fb7f4ce8309d4e722554 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:29:21 +0000 Subject: [PATCH 19/67] Version Packages (#15118) Co-authored-by: github-actions[bot] --- .changeset/eleven-scissors-cheat.md | 5 ----- .changeset/few-beans-bow.md | 5 ----- .changeset/strong-shoes-whisper.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/eleven-scissors-cheat.md delete mode 100644 .changeset/few-beans-bow.md delete mode 100644 .changeset/strong-shoes-whisper.md diff --git a/.changeset/eleven-scissors-cheat.md b/.changeset/eleven-scissors-cheat.md deleted file mode 100644 index 5aa577d95d..0000000000 --- a/.changeset/eleven-scissors-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: Add `bind:focused` property to `HTMLAttributes` type diff --git a/.changeset/few-beans-bow.md b/.changeset/few-beans-bow.md deleted file mode 100644 index 5c17c242a8..0000000000 --- a/.changeset/few-beans-bow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: lazily connect derievds (in deriveds) to their parent diff --git a/.changeset/strong-shoes-whisper.md b/.changeset/strong-shoes-whisper.md deleted file mode 100644 index ea7f8d6c5e..0000000000 --- a/.changeset/strong-shoes-whisper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: disallow $state/$derived in const tags diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index ba912c1dd8..3b42ad6927 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.19.4 + +### Patch Changes + +- fix: Add `bind:focused` property to `HTMLAttributes` type ([#15122](https://github.com/sveltejs/svelte/pull/15122)) + +- fix: lazily connect derievds (in deriveds) to their parent ([#15129](https://github.com/sveltejs/svelte/pull/15129)) + +- fix: disallow $state/$derived in const tags ([#15115](https://github.com/sveltejs/svelte/pull/15115)) + ## 5.19.3 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 76ce768cb5..f94992e42e 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.19.3", + "version": "5.19.4", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 0e5fec3b81..b45197aa9c 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.19.3'; +export const VERSION = '5.19.4'; export const PUBLIC_VERSION = '5'; From 5e9b29c351294e84d8e652ac871fbe3e7d620351 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Jan 2025 15:57:26 -0500 Subject: [PATCH 20/67] chore: move context code into new module (#15132) --- packages/svelte/src/index-client.js | 15 +- .../svelte/src/internal/client/context.js | 214 +++++++++++++++++ .../svelte/src/internal/client/dev/legacy.js | 2 +- .../src/internal/client/dev/ownership.js | 2 +- .../src/internal/client/dom/blocks/await.js | 13 +- .../internal/client/dom/blocks/boundary.js | 3 +- .../src/internal/client/dom/blocks/html.js | 2 +- .../src/internal/client/dom/blocks/snippet.js | 2 +- .../client/dom/blocks/svelte-element.js | 3 +- .../internal/client/dom/legacy/lifecycle.js | 3 +- packages/svelte/src/internal/client/index.js | 9 +- packages/svelte/src/internal/client/proxy.js | 5 +- .../internal/client/reactivity/deriveds.js | 4 +- .../src/internal/client/reactivity/effects.js | 3 +- .../src/internal/client/reactivity/sources.js | 2 +- packages/svelte/src/internal/client/render.js | 3 +- .../svelte/src/internal/client/runtime.js | 220 +----------------- .../svelte/src/internal/client/validate.js | 2 +- packages/svelte/src/legacy/legacy-client.js | 10 +- packages/svelte/tests/signals/test.ts | 5 +- packages/svelte/types/index.d.ts | 56 ++--- 21 files changed, 288 insertions(+), 290 deletions(-) create mode 100644 packages/svelte/src/internal/client/context.js diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index 587d766233..bd2b353395 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -1,12 +1,13 @@ /** @import { ComponentContext, ComponentContextLegacy } from '#client' */ /** @import { EventDispatcher } from './index.js' */ /** @import { NotFunction } from './internal/types.js' */ -import { component_context, flush_sync, untrack } from './internal/client/runtime.js'; +import { flush_sync, untrack } from './internal/client/runtime.js'; import { is_array } from './internal/shared/utils.js'; import { user_effect } from './internal/client/index.js'; import * as e from './internal/client/errors.js'; import { lifecycle_outside_component } from './internal/shared/errors.js'; import { legacy_mode_flag } from './internal/flags/index.js'; +import { component_context } from './internal/client/context.js'; /** * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. @@ -179,15 +180,7 @@ export function flushSync(fn) { flush_sync(fn); } +export { getContext, getAllContexts, hasContext, setContext } from './internal/client/context.js'; export { hydrate, mount, unmount } from './internal/client/render.js'; - -export { - getContext, - getAllContexts, - hasContext, - setContext, - tick, - untrack -} from './internal/client/runtime.js'; - +export { tick, untrack } from './internal/client/runtime.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js new file mode 100644 index 0000000000..1d7f0747cd --- /dev/null +++ b/packages/svelte/src/internal/client/context.js @@ -0,0 +1,214 @@ +/** @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 { + active_effect, + active_reaction, + set_active_effect, + set_active_reaction +} from './runtime.js'; +import { effect } from './reactivity/effects.js'; +import { legacy_mode_flag } from '../flags/index.js'; + +/** @type {ComponentContext | null} */ +export let component_context = null; + +/** @param {ComponentContext | null} context */ +export function set_component_context(context) { + component_context = context; +} + +/** + * The current component function. Different from current component context: + * ```html + * + * + * + * + * ``` + * @type {ComponentContext['function']} + */ +export let dev_current_component_function = null; + +/** @param {ComponentContext['function']} fn */ +export function set_dev_current_component_function(fn) { + dev_current_component_function = fn; +} + +/** + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * + * @template T + * @param {any} key + * @returns {T} + */ +export function getContext(key) { + const context_map = get_or_init_context_map('getContext'); + const result = /** @type {T} */ (context_map.get(key)); + + if (DEV) { + const fn = /** @type {ComponentContext} */ (component_context).function; + if (fn) { + add_owner(result, fn, true); + } + } + + return result; +} + +/** + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component + * (including slotted content) with `getContext`. + * + * Like lifecycle functions, this must be called during component initialisation. + * + * @template T + * @param {any} key + * @param {T} context + * @returns {T} + */ +export function setContext(key, context) { + const context_map = get_or_init_context_map('setContext'); + context_map.set(key, context); + return context; +} + +/** + * Checks whether a given `key` has been set in the context of a parent component. + * Must be called during component initialisation. + * + * @param {any} key + * @returns {boolean} + */ +export function hasContext(key) { + const context_map = get_or_init_context_map('hasContext'); + return context_map.has(key); +} + +/** + * Retrieves the whole context map that belongs to the closest parent component. + * Must be called during component initialisation. Useful, for example, if you + * programmatically create a component and want to pass the existing context to it. + * + * @template {Map} [T=Map] + * @returns {T} + */ +export function getAllContexts() { + const context_map = get_or_init_context_map('getAllContexts'); + + if (DEV) { + const fn = component_context?.function; + if (fn) { + for (const value of context_map.values()) { + add_owner(value, fn, true); + } + } + } + + return /** @type {T} */ (context_map); +} + +/** + * @param {Record} props + * @param {any} runes + * @param {Function} [fn] + * @returns {void} + */ +export function push(props, runes = false, fn) { + component_context = { + p: component_context, + c: null, + e: null, + m: false, + s: props, + x: null, + l: null + }; + + if (legacy_mode_flag && !runes) { + component_context.l = { + s: null, + u: null, + r1: [], + r2: source(false) + }; + } + + if (DEV) { + // component function + component_context.function = fn; + dev_current_component_function = fn; + } +} + +/** + * @template {Record} T + * @param {T} [component] + * @returns {T} + */ +export function pop(component) { + const context_stack_item = component_context; + if (context_stack_item !== null) { + if (component !== undefined) { + context_stack_item.x = component; + } + const component_effects = context_stack_item.e; + if (component_effects !== null) { + var previous_effect = active_effect; + var previous_reaction = active_reaction; + context_stack_item.e = null; + try { + for (var i = 0; i < component_effects.length; i++) { + var component_effect = component_effects[i]; + set_active_effect(component_effect.effect); + set_active_reaction(component_effect.reaction); + effect(component_effect.fn); + } + } finally { + set_active_effect(previous_effect); + set_active_reaction(previous_reaction); + } + } + component_context = context_stack_item.p; + if (DEV) { + dev_current_component_function = context_stack_item.p?.function ?? null; + } + context_stack_item.m = true; + } + // Micro-optimization: Don't set .a above to the empty object + // so it can be garbage-collected when the return here is unused + return component || /** @type {T} */ ({}); +} + +/** + * @param {string} name + * @returns {Map} + */ +function get_or_init_context_map(name) { + if (component_context === null) { + lifecycle_outside_component(name); + } + + return (component_context.c ??= new Map(get_parent_context(component_context) || undefined)); +} + +/** + * @param {ComponentContext} component_context + * @returns {Map | null} + */ +function get_parent_context(component_context) { + let parent = component_context.p; + while (parent !== null) { + const context_map = parent.c; + if (context_map !== null) { + return context_map; + } + parent = parent.p; + } + return null; +} diff --git a/packages/svelte/src/internal/client/dev/legacy.js b/packages/svelte/src/internal/client/dev/legacy.js index a1d2fc8823..138213c551 100644 --- a/packages/svelte/src/internal/client/dev/legacy.js +++ b/packages/svelte/src/internal/client/dev/legacy.js @@ -1,5 +1,5 @@ import * as e from '../errors.js'; -import { component_context } from '../runtime.js'; +import { component_context } from '../context.js'; import { FILENAME } from '../../../constants.js'; import { get_component } from './ownership.js'; diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index d113d9ae90..70cfbb47f3 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -3,7 +3,7 @@ import { STATE_SYMBOL_METADATA } from '../constants.js'; import { render_effect, user_pre_effect } from '../reactivity/effects.js'; -import { dev_current_component_function } from '../runtime.js'; +import { dev_current_component_function } from '../context.js'; import { get_prototype_of } from '../../shared/utils.js'; import * as w from '../warnings.js'; import { FILENAME } from '../../../constants.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 62b2e4dd0c..633a6e5bab 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -3,18 +3,15 @@ import { DEV } from 'esm-env'; import { is_promise } from '../../../shared/utils.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; import { internal_set, mutable_source, source } from '../../reactivity/sources.js'; +import { flush_sync, is_runes, set_active_effect, set_active_reaction } from '../../runtime.js'; +import { hydrate_next, hydrate_node, hydrating } from '../hydration.js'; +import { queue_micro_task } from '../task.js'; +import { UNINITIALIZED } from '../../../../constants.js'; import { component_context, - flush_sync, - is_runes, - set_active_effect, - set_active_reaction, set_component_context, set_dev_current_component_function -} from '../../runtime.js'; -import { hydrate_next, hydrate_node, hydrating } from '../hydration.js'; -import { queue_micro_task } from '../task.js'; -import { UNINITIALIZED } from '../../../../constants.js'; +} from '../../context.js'; const PENDING = 0; const THEN = 1; diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 7f4f000dce..c1ca7a9600 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,15 +1,14 @@ /** @import { Effect, TemplateNode, } from '#client' */ import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '../../constants.js'; +import { component_context, set_component_context } from '../../context.js'; import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import { active_effect, active_reaction, - component_context, handle_error, set_active_effect, set_active_reaction, - set_component_context, reset_is_throwing_error } from '../../runtime.js'; import { diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index 04ab0aee87..b3fc5a9c72 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -7,7 +7,7 @@ import { assign_nodes } from '../template.js'; import * as w from '../../warnings.js'; import { hash, sanitize_location } from '../../../../utils.js'; import { DEV } from 'esm-env'; -import { dev_current_component_function } from '../../runtime.js'; +import { dev_current_component_function } from '../../context.js'; import { get_first_child, get_next_sibling } from '../operations.js'; /** diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index cec57f83b7..b916a02ce5 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -6,7 +6,7 @@ import { branch, block, destroy_effect, teardown } from '../../reactivity/effect import { dev_current_component_function, set_dev_current_component_function -} from '../../runtime.js'; +} from '../../context.js'; import { hydrate_next, hydrate_node, hydrating } from '../hydration.js'; import { create_fragment_from_html } from '../reconciler.js'; import { assign_nodes } from '../template.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index 35d2f223ae..18641300e5 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -17,7 +17,8 @@ import { } from '../../reactivity/effects.js'; import { set_should_intro } from '../../render.js'; import { current_each_item, set_current_each_item } from './each.js'; -import { component_context, active_effect } from '../../runtime.js'; +import { active_effect } from '../../runtime.js'; +import { component_context } from '../../context.js'; import { DEV } from 'esm-env'; import { EFFECT_TRANSPARENT } from '../../constants.js'; import { assign_nodes } from '../template.js'; diff --git a/packages/svelte/src/internal/client/dom/legacy/lifecycle.js b/packages/svelte/src/internal/client/dom/legacy/lifecycle.js index 5ffbacc670..a564712f04 100644 --- a/packages/svelte/src/internal/client/dom/legacy/lifecycle.js +++ b/packages/svelte/src/internal/client/dom/legacy/lifecycle.js @@ -1,8 +1,9 @@ /** @import { ComponentContextLegacy } from '#client' */ import { run, run_all } from '../../../shared/utils.js'; +import { component_context } from '../../context.js'; import { derived } from '../../reactivity/deriveds.js'; import { user_pre_effect, user_effect } from '../../reactivity/effects.js'; -import { component_context, deep_read_state, get, untrack } from '../../runtime.js'; +import { deep_read_state, get, untrack } from '../../runtime.js'; /** * Legacy-mode only: Call `onMount` callbacks and set up `beforeUpdate`/`afterUpdate` effects diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 2bf58c51f7..877f3c59ee 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -1,4 +1,5 @@ export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js'; +export { push, pop } from './context.js'; 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'; @@ -141,14 +142,8 @@ export { update, update_pre, exclude_from_object, - pop, - push, deep_read, - deep_read_state, - getAllContexts, - getContext, - setContext, - hasContext + deep_read_state } from './runtime.js'; export { validate_binding, validate_each_keys } from './validate.js'; export { raf } from './timing.js'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 6cbd6394df..4c262880f1 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -1,6 +1,7 @@ -/** @import { ProxyMetadata, ProxyStateObject, Source } from '#client' */ +/** @import { ProxyMetadata, Source } from '#client' */ import { DEV } from 'esm-env'; -import { get, component_context, active_effect } from './runtime.js'; +import { get, active_effect } from './runtime.js'; +import { component_context } from './context.js'; import { array_prototype, get_descriptor, diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index da5a75eb6e..e86963408c 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -17,8 +17,7 @@ import { skip_reaction, update_reaction, increment_write_version, - set_active_effect, - component_context + set_active_effect } from '../runtime.js'; import { equals, safe_equals } from './equality.js'; import * as e from '../errors.js'; @@ -26,6 +25,7 @@ import { destroy_effect } from './effects.js'; import { inspect_effects, set_inspect_effects } from './sources.js'; import { get_stack } from '../dev/tracing.js'; import { tracing_mode_flag } from '../../flags/index.js'; +import { component_context } from '../context.js'; /** * @template V diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 1faf9a47a0..8a7cffecd6 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -1,10 +1,8 @@ /** @import { ComponentContext, ComponentContextLegacy, Derived, Effect, TemplateNode, TransitionManager } from '#client' */ import { check_dirtiness, - component_context, active_effect, active_reaction, - dev_current_component_function, update_effect, get, is_destroying_effect, @@ -45,6 +43,7 @@ import { DEV } from 'esm-env'; import { define_property } from '../../shared/utils.js'; import { get_next_sibling } from '../dom/operations.js'; import { derived, destroy_derived } from './deriveds.js'; +import { component_context, dev_current_component_function } from '../context.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index c2448c9ee5..b3e66a3336 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -1,7 +1,6 @@ /** @import { Derived, Effect, Reaction, Source, Value } from '#client' */ import { DEV } from 'esm-env'; import { - component_context, active_reaction, active_effect, untracked_writes, @@ -35,6 +34,7 @@ import { 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 } from '../context.js'; export let inspect_effects = new Set(); diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 767b230131..bc74047f64 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -9,7 +9,8 @@ import { init_operations } from './dom/operations.js'; import { HYDRATION_END, HYDRATION_ERROR, HYDRATION_START } from '../../constants.js'; -import { push, pop, component_context, active_effect } from './runtime.js'; +import { active_effect } from './runtime.js'; +import { push, pop, component_context } from './context.js'; import { component_root, branch } from './reactivity/effects.js'; import { hydrate_next, diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index a002665b51..859925186f 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -5,7 +5,6 @@ import { destroy_block_effect_children, destroy_effect_children, destroy_effect_deriveds, - effect, execute_effect_teardown, unlink_effect } from './reactivity/effects.js'; @@ -28,14 +27,18 @@ import { BOUNDARY_EFFECT } from './constants.js'; import { flush_tasks } from './dom/task.js'; -import { add_owner } from './dev/ownership.js'; -import { internal_set, set, source } from './reactivity/sources.js'; +import { internal_set, set } from './reactivity/sources.js'; import { destroy_derived, execute_derived, update_derived } from './reactivity/deriveds.js'; import * as e from './errors.js'; -import { lifecycle_outside_component } from '../shared/errors.js'; import { FILENAME } from '../../constants.js'; import { legacy_mode_flag, tracing_mode_flag } from '../flags/index.js'; import { tracing_expressions, get_stack } from './dev/tracing.js'; +import { + component_context, + dev_current_component_function, + set_component_context, + set_dev_current_component_function +} from './context.js'; const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; @@ -150,32 +153,6 @@ export function set_captured_signals(value) { captured_signals = value; } -// Handling runtime component context -/** @type {ComponentContext | null} */ -export let component_context = null; - -/** @param {ComponentContext | null} context */ -export function set_component_context(context) { - component_context = context; -} - -/** - * The current component function. Different from current component context: - * ```html - * - * - * - * - * ``` - * @type {ComponentContext['function']} - */ -export let dev_current_component_function = null; - -/** @param {ComponentContext['function']} fn */ -export function set_dev_current_component_function(fn) { - dev_current_component_function = fn; -} - export function increment_write_version() { return ++write_version; } @@ -434,7 +411,7 @@ export function update_reaction(reaction) { active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0; derived_sources = null; - component_context = reaction.ctx; + set_component_context(reaction.ctx); untracking = false; read_version++; @@ -498,7 +475,7 @@ export function update_reaction(reaction) { active_reaction = previous_reaction; skip_reaction = previous_skip_reaction; derived_sources = prev_derived_sources; - component_context = previous_component_context; + set_component_context(previous_component_context); untracking = previous_untracking; } } @@ -578,7 +555,7 @@ export function update_effect(effect) { if (DEV) { var previous_component_fn = dev_current_component_function; - dev_current_component_function = effect.component_function; + set_dev_current_component_function(effect.component_function); } try { @@ -620,7 +597,7 @@ export function update_effect(effect) { active_effect = previous_effect; if (DEV) { - dev_current_component_function = previous_component_fn; + set_dev_current_component_function(previous_component_fn); } } } @@ -1114,109 +1091,6 @@ export function set_signal_status(signal, status) { signal.f = (signal.f & STATUS_MASK) | status; } -/** - * Retrieves the context that belongs to the closest parent component with the specified `key`. - * Must be called during component initialisation. - * - * @template T - * @param {any} key - * @returns {T} - */ -export function getContext(key) { - const context_map = get_or_init_context_map('getContext'); - const result = /** @type {T} */ (context_map.get(key)); - - if (DEV) { - const fn = /** @type {ComponentContext} */ (component_context).function; - if (fn) { - add_owner(result, fn, true); - } - } - - return result; -} - -/** - * Associates an arbitrary `context` object with the current component and the specified `key` - * and returns that object. The context is then available to children of the component - * (including slotted content) with `getContext`. - * - * Like lifecycle functions, this must be called during component initialisation. - * - * @template T - * @param {any} key - * @param {T} context - * @returns {T} - */ -export function setContext(key, context) { - const context_map = get_or_init_context_map('setContext'); - context_map.set(key, context); - return context; -} - -/** - * Checks whether a given `key` has been set in the context of a parent component. - * Must be called during component initialisation. - * - * @param {any} key - * @returns {boolean} - */ -export function hasContext(key) { - const context_map = get_or_init_context_map('hasContext'); - return context_map.has(key); -} - -/** - * Retrieves the whole context map that belongs to the closest parent component. - * Must be called during component initialisation. Useful, for example, if you - * programmatically create a component and want to pass the existing context to it. - * - * @template {Map} [T=Map] - * @returns {T} - */ -export function getAllContexts() { - const context_map = get_or_init_context_map('getAllContexts'); - - if (DEV) { - const fn = component_context?.function; - if (fn) { - for (const value of context_map.values()) { - add_owner(value, fn, true); - } - } - } - - return /** @type {T} */ (context_map); -} - -/** - * @param {string} name - * @returns {Map} - */ -function get_or_init_context_map(name) { - if (component_context === null) { - lifecycle_outside_component(name); - } - - return (component_context.c ??= new Map(get_parent_context(component_context) || undefined)); -} - -/** - * @param {ComponentContext} component_context - * @returns {Map | null} - */ -function get_parent_context(component_context) { - let parent = component_context.p; - while (parent !== null) { - const context_map = parent.c; - if (context_map !== null) { - return context_map; - } - parent = parent.p; - } - return null; -} - /** * @template {number | bigint} T * @param {Value} signal @@ -1264,78 +1138,6 @@ export function exclude_from_object(obj, keys) { return result; } -/** - * @param {Record} props - * @param {any} runes - * @param {Function} [fn] - * @returns {void} - */ -export function push(props, runes = false, fn) { - component_context = { - p: component_context, - c: null, - e: null, - m: false, - s: props, - x: null, - l: null - }; - - if (legacy_mode_flag && !runes) { - component_context.l = { - s: null, - u: null, - r1: [], - r2: source(false) - }; - } - - if (DEV) { - // component function - component_context.function = fn; - dev_current_component_function = fn; - } -} - -/** - * @template {Record} T - * @param {T} [component] - * @returns {T} - */ -export function pop(component) { - const context_stack_item = component_context; - if (context_stack_item !== null) { - if (component !== undefined) { - context_stack_item.x = component; - } - const component_effects = context_stack_item.e; - if (component_effects !== null) { - var previous_effect = active_effect; - var previous_reaction = active_reaction; - context_stack_item.e = null; - try { - for (var i = 0; i < component_effects.length; i++) { - var component_effect = component_effects[i]; - set_active_effect(component_effect.effect); - set_active_reaction(component_effect.reaction); - effect(component_effect.fn); - } - } finally { - set_active_effect(previous_effect); - set_active_reaction(previous_reaction); - } - } - component_context = context_stack_item.p; - if (DEV) { - dev_current_component_function = context_stack_item.p?.function ?? null; - } - context_stack_item.m = true; - } - // Micro-optimization: Don't set .a above to the empty object - // so it can be garbage-collected when the return here is unused - return component || /** @type {T} */ ({}); -} - /** * Possibly traverse an object and read all its properties so that they're all reactive in case this is `$state`. * Does only check first level of an object for performance reasons (heuristic should be good for 99% of all cases). diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 24e280edf8..ec3d805447 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -1,4 +1,4 @@ -import { dev_current_component_function } from './runtime.js'; +import { dev_current_component_function } from './context.js'; import { is_array } from '../shared/utils.js'; import * as e from './errors.js'; import { FILENAME } from '../../constants.js'; diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index 3715617f4c..3a05bc0496 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -3,19 +3,13 @@ import { DIRTY, LEGACY_PROPS, MAYBE_DIRTY } from '../internal/client/constants.j import { user_pre_effect } from '../internal/client/reactivity/effects.js'; import { mutable_source, set } from '../internal/client/reactivity/sources.js'; import { hydrate, mount, unmount } from '../internal/client/render.js'; -import { - active_effect, - component_context, - dev_current_component_function, - flush_sync, - get, - set_signal_status -} from '../internal/client/runtime.js'; +import { active_effect, flush_sync, get, set_signal_status } from '../internal/client/runtime.js'; import { lifecycle_outside_component } from '../internal/shared/errors.js'; import { define_property, is_array } from '../internal/shared/utils.js'; import * as w from '../internal/client/warnings.js'; import { DEV } from 'esm-env'; import { FILENAME } from '../constants.js'; +import { component_context, dev_current_component_function } from '../internal/client/context.js'; /** * Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component. diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 5f0b93e136..aa1dbf3132 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1,6 +1,7 @@ import { describe, assert, it } from 'vitest'; import { flushSync } from '../../src/index-client'; import * as $ from '../../src/internal/client/runtime'; +import { push, pop } from '../../src/internal/client/context'; import { effect, effect_root, @@ -22,13 +23,13 @@ import { SvelteSet } from '../../src/reactivity/set'; function run_test(runes: boolean, fn: (runes: boolean) => () => void) { return () => { // Create a component context to test runes vs legacy mode - $.push({}, runes); + push({}, runes); // Create a render context so that effect validations etc don't fail let execute: any; const destroy = effect_root(() => { execute = fn(runes); }); - $.pop(); + pop(); execute(); destroy(); }; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index d00b2b01ed..eb3e93e4b5 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -421,6 +421,34 @@ declare module 'svelte' { }): Snippet; /** Anything except a function */ type NotFunction = T extends Function ? never : T; + /** + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * + * */ + export function getContext(key: any): T; + /** + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component + * (including slotted content) with `getContext`. + * + * Like lifecycle functions, this must be called during component initialisation. + * + * */ + export function setContext(key: any, context: T): T; + /** + * Checks whether a given `key` has been set in the context of a parent component. + * Must be called during component initialisation. + * + * */ + export function hasContext(key: any): boolean; + /** + * Retrieves the whole context map that belongs to the closest parent component. + * Must be called during component initialisation. Useful, for example, if you + * programmatically create a component and want to pass the existing context to it. + * + * */ + export function getAllContexts = Map>(): T; /** * Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component. * Transitions will play during the initial render unless the `intro` option is set to `false`. @@ -484,34 +512,6 @@ declare module 'svelte' { * ``` * */ export function untrack(fn: () => T): T; - /** - * Retrieves the context that belongs to the closest parent component with the specified `key`. - * Must be called during component initialisation. - * - * */ - export function getContext(key: any): T; - /** - * Associates an arbitrary `context` object with the current component and the specified `key` - * and returns that object. The context is then available to children of the component - * (including slotted content) with `getContext`. - * - * Like lifecycle functions, this must be called during component initialisation. - * - * */ - export function setContext(key: any, context: T): T; - /** - * Checks whether a given `key` has been set in the context of a parent component. - * Must be called during component initialisation. - * - * */ - export function hasContext(key: any): boolean; - /** - * Retrieves the whole context map that belongs to the closest parent component. - * Must be called during component initialisation. Useful, for example, if you - * programmatically create a component and want to pass the existing context to it. - * - * */ - export function getAllContexts = Map>(): T; type Getters = { [K in keyof T]: () => T[K]; }; From 9410ad0318587c338c539ca7cbca1ff9d61db6a0 Mon Sep 17 00:00:00 2001 From: 7nik Date: Wed, 29 Jan 2025 13:50:07 +0200 Subject: [PATCH 21/67] fix: correctly look for sibling elements inside blocks and components (#15106) Fixes #15027 and fixes #14995 When the target element is inside a block or component, get_following_sibling_elements was looking only for sibling elements after the block/component. This PR fixes it by starting the search from the begging of the block/component and skipping nodes that go before the target element. --- .changeset/curly-balloons-relate.md | 5 + .../phases/2-analyze/css/css-prune.js | 25 ++-- .../svelte/tests/css/samples/has/_config.js | 118 ++++++++++-------- .../svelte/tests/css/samples/has/expected.css | 10 ++ .../svelte/tests/css/samples/has/input.svelte | 12 ++ 5 files changed, 109 insertions(+), 61 deletions(-) create mode 100644 .changeset/curly-balloons-relate.md diff --git a/.changeset/curly-balloons-relate.md b/.changeset/curly-balloons-relate.md new file mode 100644 index 0000000000..69d13b8692 --- /dev/null +++ b/.changeset/curly-balloons-relate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly look for sibling elements inside blocks and components when pruning CSS 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 ca7476ef7f..109010e88c 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 @@ -638,19 +638,30 @@ function get_following_sibling_elements(element, include_self) { /** @type {Array} */ const siblings = []; - // ...then walk them, starting from the node after the one - // containing the element in question + // ...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) { - siblings.push(node); + if (node === element) { + skip = false; + if (include_self) siblings.push(node); + } else if (!skip) { + siblings.push(node); + } }, SvelteElement(node) { - siblings.push(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) { @@ -663,14 +674,10 @@ function get_following_sibling_elements(element, include_self) { }); } - for (const node of nodes.slice(nodes.indexOf(start) + 1)) { + for (const node of nodes.slice(nodes.indexOf(start))) { get_siblings(node); } - if (include_self) { - siblings.push(element); - } - return siblings; } diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js index e5dc5f3459..33bbe74949 100644 --- a/packages/svelte/tests/css/samples/has/_config.js +++ b/packages/svelte/tests/css/samples/has/_config.js @@ -6,182 +6,196 @@ export default test({ code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(y)"', start: { - line: 31, + line: 33, column: 1, - character: 308 + character: 330 }, end: { - line: 31, + line: 33, column: 15, - character: 322 + character: 344 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(:global(y))"', start: { - line: 34, + line: 36, column: 1, - character: 343 + character: 365 }, end: { - line: 34, + line: 36, column: 24, - character: 366 + character: 388 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(.unused)"', start: { - line: 37, + line: 39, column: 1, - character: 387 + character: 409 }, end: { - line: 37, + line: 39, column: 15, - character: 401 + character: 423 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ":global(.foo):has(.unused)"', start: { - line: 40, + line: 42, column: 1, - character: 422 + character: 444 }, end: { - line: 40, + line: 42, column: 27, - character: 448 + character: 470 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(y):has(.unused)"', start: { - line: 50, + line: 52, column: 1, - character: 556 + character: 578 }, end: { - line: 50, + line: 52, column: 22, - character: 577 + character: 599 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused"', start: { - line: 69, + line: 71, column: 2, - character: 782 + character: 804 }, end: { - line: 69, + line: 71, column: 9, - character: 789 + character: 811 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused x:has(y)"', start: { - line: 85, + line: 87, column: 1, - character: 936 + character: 958 }, end: { - line: 85, + line: 87, column: 17, - character: 952 + character: 974 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(.unused)"', start: { - line: 88, + line: 90, column: 1, - character: 973 + character: 995 }, end: { - line: 88, + line: 90, column: 21, - character: 993 + character: 1015 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(> z)"', start: { - line: 98, + line: 100, column: 1, - character: 1093 + character: 1115 }, end: { - line: 98, + line: 100, column: 11, - character: 1103 + character: 1125 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(> d)"', start: { - line: 101, + line: 103, column: 1, - character: 1124 + character: 1146 }, end: { - line: 101, + line: 103, column: 11, - character: 1134 + character: 1156 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(~ y)"', start: { - line: 121, + line: 123, column: 1, - character: 1326 + character: 1348 }, end: { - line: 121, + line: 123, column: 11, - character: 1336 + character: 1358 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "f:has(~ d)"', + start: { + line: 133, + column: 1, + character: 1446 + }, + end: { + line: 133, + column: 11, + character: 1456 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ":has(.unused)"', start: { - line: 129, + line: 141, column: 2, - character: 1409 + character: 1529 }, end: { - line: 129, + line: 141, column: 15, - character: 1422 + character: 1542 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "&:has(.unused)"', start: { - line: 135, + line: 147, column: 2, - character: 1480 + character: 1600 }, end: { - line: 135, + line: 147, column: 16, - character: 1494 + character: 1614 } } ] diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 68d6aad68a..9627bf730c 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -112,6 +112,16 @@ color: red; }*/ + d.svelte-xyz:has(+ e:where(.svelte-xyz)) { + color: green; + } + d.svelte-xyz:has(~ f:where(.svelte-xyz)) { + color: green; + } + /* (unused) f:has(~ d) { + color: red; + }*/ + .foo { .svelte-xyz:has(x:where(.svelte-xyz)) { color: green; diff --git a/packages/svelte/tests/css/samples/has/input.svelte b/packages/svelte/tests/css/samples/has/input.svelte index 3487b64e8c..946cf2df90 100644 --- a/packages/svelte/tests/css/samples/has/input.svelte +++ b/packages/svelte/tests/css/samples/has/input.svelte @@ -3,6 +3,8 @@ {#if foo} + + {/if} @@ -122,6 +124,16 @@ color: red; } + d:has(+ e) { + color: green; + } + d:has(~ f) { + color: green; + } + f:has(~ d) { + color: red; + } + :global(.foo) { :has(x) { color: green; From 13a6d555c05f0833d828a518f70722dba351fabc Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 29 Jan 2025 13:17:12 +0000 Subject: [PATCH 22/67] fix: improve derived connection to ownership graph (#15137) * fix: improve derived connection to ownership graph * revised * revised * revised * revised * feedback * feedback * invasive change * fix bugs * fix other bug --- .changeset/blue-rocks-play.md | 5 + .../internal/client/reactivity/deriveds.js | 42 ++-- .../src/internal/client/reactivity/effects.js | 22 +- .../src/internal/client/reactivity/types.d.ts | 6 +- .../svelte/src/internal/client/runtime.js | 46 ++-- packages/svelte/tests/signals/test.ts | 201 +++++++++++++----- 6 files changed, 199 insertions(+), 123 deletions(-) create mode 100644 .changeset/blue-rocks-play.md diff --git a/.changeset/blue-rocks-play.md b/.changeset/blue-rocks-play.md new file mode 100644 index 0000000000..31e9991148 --- /dev/null +++ b/.changeset/blue-rocks-play.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve derived connection to ownership graph diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index e86963408c..1f65ff38c3 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -35,8 +35,12 @@ import { component_context } from '../context.js'; /*#__NO_SIDE_EFFECTS__*/ export function derived(fn) { var flags = DERIVED | DIRTY; + var parent_derived = + active_reaction !== null && (active_reaction.f & DERIVED) !== 0 + ? /** @type {Derived} */ (active_reaction) + : null; - if (active_effect === null) { + if (active_effect === null || (parent_derived !== null && (parent_derived.f & UNOWNED) !== 0)) { flags |= UNOWNED; } else { // Since deriveds are evaluated lazily, any effects created inside them are @@ -44,16 +48,11 @@ export function derived(fn) { active_effect.f |= EFFECT_HAS_DERIVED; } - var parent_derived = - active_reaction !== null && (active_reaction.f & DERIVED) !== 0 - ? /** @type {Derived} */ (active_reaction) - : null; - /** @type {Derived} */ const signal = { - children: null, ctx: component_context, deps: null, + effects: null, equals, f: flags, fn, @@ -87,19 +86,14 @@ export function derived_safe_equal(fn) { * @param {Derived} derived * @returns {void} */ -function destroy_derived_children(derived) { - var children = derived.children; - - if (children !== null) { - derived.children = null; - - for (var i = 0; i < children.length; i += 1) { - var child = children[i]; - if ((child.f & DERIVED) !== 0) { - destroy_derived(/** @type {Derived} */ (child)); - } else { - destroy_effect(/** @type {Effect} */ (child)); - } +export function destroy_derived_effects(derived) { + var effects = derived.effects; + + if (effects !== null) { + derived.effects = null; + + for (var i = 0; i < effects.length; i += 1) { + destroy_effect(/** @type {Effect} */ (effects[i])); } } } @@ -147,7 +141,7 @@ export function execute_derived(derived) { stack.push(derived); - destroy_derived_children(derived); + destroy_derived_effects(derived); value = update_reaction(derived); } finally { set_active_effect(prev_active_effect); @@ -156,7 +150,7 @@ export function execute_derived(derived) { } } else { try { - destroy_derived_children(derived); + destroy_derived_effects(derived); value = update_reaction(derived); } finally { set_active_effect(prev_active_effect); @@ -188,9 +182,9 @@ export function update_derived(derived) { * @returns {void} */ export function destroy_derived(derived) { - destroy_derived_children(derived); + destroy_derived_effects(derived); remove_reactions(derived, 0); set_signal_status(derived, DESTROYED); - derived.v = derived.children = derived.deps = derived.ctx = derived.reactions = null; + derived.v = derived.deps = derived.ctx = derived.reactions = null; } diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 8a7cffecd6..d014ff793d 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -53,7 +53,7 @@ export function validate_effect(rune) { e.effect_orphan(rune); } - if (active_reaction !== null && (active_reaction.f & UNOWNED) !== 0) { + if (active_reaction !== null && (active_reaction.f & UNOWNED) !== 0 && active_effect === null) { e.effect_in_unowned_derived(); } @@ -99,7 +99,6 @@ function create_effect(type, fn, sync, push = true) { var effect = { ctx: component_context, deps: null, - deriveds: null, nodes_start: null, nodes_end: null, f: type | DIRTY, @@ -153,7 +152,7 @@ function create_effect(type, fn, sync, push = true) { // if we're in a derived, add the effect there too if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) { var derived = /** @type {Derived} */ (active_reaction); - (derived.children ??= []).push(effect); + (derived.effects ??= []).push(effect); } } @@ -395,22 +394,6 @@ export function execute_effect_teardown(effect) { } } -/** - * @param {Effect} signal - * @returns {void} - */ -export function destroy_effect_deriveds(signal) { - var deriveds = signal.deriveds; - - if (deriveds !== null) { - signal.deriveds = null; - - for (var i = 0; i < deriveds.length; i += 1) { - destroy_derived(deriveds[i]); - } - } -} - /** * @param {Effect} signal * @param {boolean} remove_dom @@ -468,7 +451,6 @@ export function destroy_effect(effect, remove_dom = true) { } destroy_effect_children(effect, remove_dom && !removed); - destroy_effect_deriveds(effect); remove_reactions(effect, 0); set_signal_status(effect, DESTROYED); diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 3a76a3ff83..5ef0097649 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -36,8 +36,8 @@ export interface Reaction extends Signal { export interface Derived extends Value, Reaction { /** The derived function */ fn: () => V; - /** Reactions created inside this signal */ - children: null | Reaction[]; + /** Effects created inside this signal */ + effects: null | Effect[]; /** Parent effect or derived */ parent: Effect | Derived | null; } @@ -51,8 +51,6 @@ export interface Effect extends Reaction { */ nodes_start: null | TemplateNode; nodes_end: null | TemplateNode; - /** Reactions created inside this signal */ - deriveds: null | Derived[]; /** The effect function */ fn: null | (() => void | (() => void)); /** The teardown function returned from the effect function */ diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 859925186f..2c53ac8f7b 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -4,7 +4,6 @@ import { define_property, get_descriptors, get_prototype_of, index_of } from '.. import { destroy_block_effect_children, destroy_effect_children, - destroy_effect_deriveds, execute_effect_teardown, unlink_effect } from './reactivity/effects.js'; @@ -28,7 +27,12 @@ import { } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { internal_set, set } from './reactivity/sources.js'; -import { destroy_derived, execute_derived, update_derived } from './reactivity/deriveds.js'; +import { + destroy_derived, + destroy_derived_effects, + execute_derived, + update_derived +} from './reactivity/deriveds.js'; import * as e from './errors.js'; import { FILENAME } from '../../constants.js'; import { legacy_mode_flag, tracing_mode_flag } from '../flags/index.js'; @@ -409,7 +413,16 @@ export function update_reaction(reaction) { skipped_deps = 0; untracked_writes = null; active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; - skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0; + // prettier-ignore + skip_reaction = + (flags & UNOWNED) !== 0 && + (!is_flushing_effect || + // If we were previously not in a reactive context and we're reading an unowned derived + // that was created inside another reaction, then we don't fully know the real owner and thus + // we need to skip adding any reactions for this unowned + ((previous_reaction === null || previous_untracking) && + /** @type {Derived} */ (reaction).parent !== null)); + derived_sources = null; set_component_context(reaction.ctx); untracking = false; @@ -517,6 +530,8 @@ function remove_reaction(signal, dependency) { if ((dependency.f & (UNOWNED | DISCONNECTED)) === 0) { dependency.f ^= DISCONNECTED; } + // Disconnect any reactions owned by this reaction + destroy_derived_effects(/** @type {Derived} **/ (dependency)); remove_reactions(/** @type {Derived} **/ (dependency), 0); } } @@ -564,7 +579,6 @@ export function update_effect(effect) { } else { destroy_effect_children(effect); } - destroy_effect_deriveds(effect); execute_effect_teardown(effect); var teardown = update_reaction(effect); @@ -934,30 +948,20 @@ export function get(signal) { new_deps.push(signal); } } - } - - if ( + } else if ( is_derived && /** @type {Derived} */ (signal).deps === null && - (active_reaction === null || untracking || (active_reaction.f & DERIVED) !== 0) + /** @type {Derived} */ (signal).effects === null ) { var derived = /** @type {Derived} */ (signal); var parent = derived.parent; if (parent !== null) { - // Attach the derived to the nearest parent effect or derived - if ((parent.f & DERIVED) !== 0) { - var parent_derived = /** @type {Derived} */ (parent); - - if (!parent_derived.children?.includes(derived)) { - (parent_derived.children ??= []).push(derived); - } - } else { - var parent_effect = /** @type {Effect} */ (parent); - - if (!parent_effect.deriveds?.includes(derived)) { - (parent_effect.deriveds ??= []).push(derived); - } + // If the derived is owned by another derived then mark it as unowned + // as the derived value might have been referenced in a different context + // since and thus its parent might not be its true owner anymore + if ((parent.f & UNOWNED) === 0) { + derived.f ^= UNOWNED; } } } diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index aa1dbf3132..2ce624a777 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -9,11 +9,12 @@ import { user_effect } from '../../src/internal/client/reactivity/effects'; import { state, set } from '../../src/internal/client/reactivity/sources'; -import type { Derived, Value } from '../../src/internal/client/types'; +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'; import { snapshot } from '../../src/internal/shared/clone.js'; import { SvelteSet } from '../../src/reactivity/set'; +import { DESTROYED } from '../../src/internal/client/constants'; /** * @param runes runes mode @@ -68,7 +69,7 @@ describe('signals', () => { }; }); - test('multiple effects with state and derived in it#1', () => { + test('multiple effects with state and derived in it #1', () => { const log: string[] = []; let count = state(0); @@ -89,7 +90,7 @@ describe('signals', () => { }; }); - test('multiple effects with state and derived in it#2', () => { + test('multiple effects with state and derived in it #2', () => { const log: string[] = []; let count = state(0); @@ -256,12 +257,16 @@ describe('signals', () => { const a = state(0); const b = state(0); - const c = derived(() => { - const a_2 = derived(() => $.get(a) + '!'); - const b_2 = derived(() => $.get(b) + '?'); - nested.push(a_2, b_2); + let c: any; + + const destroy = effect_root(() => { + c = derived(() => { + const a_2 = derived(() => $.get(a) + '!'); + const b_2 = derived(() => $.get(b) + '?'); + nested.push(a_2, b_2); - return { a: $.get(a_2), b: $.get(b_2) }; + return { a: $.get(a_2), b: $.get(b_2) }; + }); }); $.get(c); @@ -274,11 +279,10 @@ describe('signals', () => { $.get(c); - // Ensure we're not leaking dependencies - assert.deepEqual( - nested.slice(0, -2).map((s) => s.deps), - [null, null, null, null] - ); + destroy(); + + assert.equal(a.reactions, null); + assert.equal(b.reactions, null); }; }); @@ -477,6 +481,7 @@ describe('signals', () => { effect(() => { log.push('inner', $.get(inner)); }); + return $.get(outer); }); }); }); @@ -530,6 +535,103 @@ describe('signals', () => { }; }); + test('mixed nested deriveds correctly cleanup when no longer connected to graph #1', () => { + let a: Derived; + let b: Derived; + let s = state(0); + + const destroy = effect_root(() => { + render_effect(() => { + a = derived(() => { + b = derived(() => { + $.get(s); + }); + $.untrack(() => { + $.get(b); + }); + $.get(s); + }); + $.get(a); + }); + }); + + return () => { + flushSync(); + assert.equal(a?.deps?.length, 1); + assert.equal(s?.reactions?.length, 1); + destroy(); + assert.equal(s?.reactions, null); + }; + }); + + test('mixed nested deriveds correctly cleanup when no longer connected to graph #2', () => { + let a: Derived; + let b: Derived; + let s = state(0); + + const destroy = effect_root(() => { + render_effect(() => { + a = derived(() => { + b = derived(() => { + $.get(s); + }); + effect_root(() => { + $.get(b); + }); + $.get(s); + }); + $.get(a); + }); + }); + + return () => { + flushSync(); + assert.equal(a?.deps?.length, 1); + assert.equal(s?.reactions?.length, 1); + destroy(); + assert.equal(s?.reactions, null); + }; + }); + + test('mixed nested deriveds correctly cleanup when no longer connected to graph #3', () => { + let a: Derived; + let b: Derived; + let s = state(0); + let logs: any[] = []; + + const destroy = effect_root(() => { + render_effect(() => { + a = derived(() => { + b = derived(() => { + return $.get(s); + }); + effect_root(() => { + $.get(b); + }); + render_effect(() => { + logs.push($.get(b)); + }); + $.get(s); + }); + $.get(a); + }); + }); + + return () => { + flushSync(); + assert.equal(a?.deps?.length, 1); + assert.equal(s?.reactions?.length, 2); + + set(s, 1); + flushSync(); + + assert.deepEqual(logs, [0, 1]); + + destroy(); + assert.equal(s?.reactions, null); + }; + }); + test('deriveds update upon reconnection #1', () => { let a = state(false); let b = state(false); @@ -778,56 +880,44 @@ describe('signals', () => { }; }); - test('nested deriveds clean up the relationships when used with untrack', () => { + test('deriveds containing effects work correctly', () => { return () => { let a = render_effect(() => {}); + let b = state(0); + let c; + let effects: Effect[] = []; const destroy = effect_root(() => { a = render_effect(() => { - $.untrack(() => { - const b = derived(() => { - const c = derived(() => {}); - $.untrack(() => { - $.get(c); - }); - }); + c = derived(() => { + effects.push( + effect(() => { + $.get(b); + }) + ); $.get(b); }); + $.get(c); }); }); - assert.deepEqual(a.deriveds?.length, 1); - - destroy(); + assert.equal(c!.effects?.length, 1); + assert.equal(a.first, a.last); - assert.deepEqual(a.deriveds, null); - }; - }); - - test('nested deriveds do not connect inside parent deriveds if unused', () => { - return () => { - let a = render_effect(() => {}); - let b: Derived | undefined; + set(b, 1); - const destroy = effect_root(() => { - a = render_effect(() => { - $.untrack(() => { - b = derived(() => { - derived(() => {}); - derived(() => {}); - derived(() => {}); - }); - $.get(b); - }); - }); - }); + flushSync(); - assert.deepEqual(a.deriveds?.length, 1); - assert.deepEqual(b?.children, null); + assert.equal(c!.effects?.length, 1); + assert.equal(a.first, a.last); destroy(); - assert.deepEqual(a.deriveds, null); + assert.equal(a.first, null); + + assert.equal(effects.length, 2); + assert.equal((effects[0].f & DESTROYED) !== 0, true); + assert.equal((effects[1].f & DESTROYED) !== 0, true); }; }); @@ -836,7 +926,7 @@ describe('signals', () => { let a = render_effect(() => {}); let b = state(0); let c; - let effects = []; + let effects: Effect[] = []; const destroy = effect_root(() => { a = render_effect(() => { @@ -854,20 +944,23 @@ describe('signals', () => { }); }); - assert.deepEqual(c!.children?.length, 1); - assert.deepEqual(a.first, a.last); + assert.equal(c!.effects?.length, 1); + assert.equal(a.first, a.last); set(b, 1); flushSync(); - assert.deepEqual(c!.children?.length, 1); - assert.deepEqual(a.first, a.last); + assert.equal(c!.effects?.length, 1); + assert.equal(a.first, a.last); destroy(); - assert.deepEqual(a.deriveds, null); - assert.deepEqual(a.first, null); + assert.equal(a.first, null); + + assert.equal(effects.length, 2); + assert.equal((effects[0].f & DESTROYED) !== 0, true); + assert.equal((effects[1].f & DESTROYED) !== 0, true); }; }); From c8bbb15693c59a3a5b3d7478d7adc1b0fa089ba7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:18:43 +0000 Subject: [PATCH 23/67] Version Packages (#15139) Co-authored-by: github-actions[bot] --- .changeset/blue-rocks-play.md | 5 ----- .changeset/curly-balloons-relate.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/blue-rocks-play.md delete mode 100644 .changeset/curly-balloons-relate.md diff --git a/.changeset/blue-rocks-play.md b/.changeset/blue-rocks-play.md deleted file mode 100644 index 31e9991148..0000000000 --- a/.changeset/blue-rocks-play.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: improve derived connection to ownership graph diff --git a/.changeset/curly-balloons-relate.md b/.changeset/curly-balloons-relate.md deleted file mode 100644 index 69d13b8692..0000000000 --- a/.changeset/curly-balloons-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly look for sibling elements inside blocks and components when pruning CSS diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 3b42ad6927..bb48391e0e 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.19.5 + +### Patch Changes + +- fix: improve derived connection to ownership graph ([#15137](https://github.com/sveltejs/svelte/pull/15137)) + +- fix: correctly look for sibling elements inside blocks and components when pruning CSS ([#15106](https://github.com/sveltejs/svelte/pull/15106)) + ## 5.19.4 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index f94992e42e..a44ba43364 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.19.4", + "version": "5.19.5", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index b45197aa9c..a509ef3fec 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.19.4'; +export const VERSION = '5.19.5'; export const PUBLIC_VERSION = '5'; From 8e83127e1a84f173855dd14d46efb402ce3523dc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Jan 2025 09:22:35 -0500 Subject: [PATCH 24/67] chore: move more code (#15133) * move more code * lint --- packages/svelte/src/index-client.js | 35 +++++++++ .../svelte/src/internal/client/context.js | 5 ++ .../src/internal/client/dom/blocks/await.js | 3 +- .../src/internal/client/dom/blocks/key.js | 2 +- .../client/dom/elements/bindings/input.js | 3 +- packages/svelte/src/internal/client/index.js | 4 +- .../src/internal/client/reactivity/props.js | 11 +-- .../src/internal/client/reactivity/sources.js | 32 +++++++- .../svelte/src/internal/client/runtime.js | 73 +------------------ packages/svelte/tests/signals/test.ts | 10 +-- 10 files changed, 86 insertions(+), 92 deletions(-) diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index bd2b353395..ca29d5bfbe 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -8,6 +8,41 @@ import * as e from './internal/client/errors.js'; import { lifecycle_outside_component } from './internal/shared/errors.js'; import { legacy_mode_flag } from './internal/flags/index.js'; import { component_context } from './internal/client/context.js'; +import { DEV } from 'esm-env'; + +if (DEV) { + /** + * @param {string} rune + */ + function throw_rune_error(rune) { + if (!(rune in globalThis)) { + // TODO if people start adjusting the "this can contain runes" config through v-p-s more, adjust this message + /** @type {any} */ + let value; // let's hope noone modifies this global, but belts and braces + Object.defineProperty(globalThis, rune, { + configurable: true, + // eslint-disable-next-line getter-return + get: () => { + if (value !== undefined) { + return value; + } + + e.rune_outside_svelte(rune); + }, + set: (v) => { + value = v; + } + }); + } + } + + throw_rune_error('$state'); + throw_rune_error('$effect'); + throw_rune_error('$derived'); + throw_rune_error('$inspect'); + throw_rune_error('$props'); + throw_rune_error('$bindable'); +} /** * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 1d7f0747cd..e1088edf30 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -185,6 +185,11 @@ export function pop(component) { return component || /** @type {T} */ ({}); } +/** @returns {boolean} */ +export function is_runes() { + return !legacy_mode_flag || (component_context !== null && component_context.l === null); +} + /** * @param {string} name * @returns {Map} diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 633a6e5bab..c8c7c1c0ea 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -3,12 +3,13 @@ import { DEV } from 'esm-env'; import { is_promise } from '../../../shared/utils.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; import { internal_set, mutable_source, source } from '../../reactivity/sources.js'; -import { flush_sync, is_runes, set_active_effect, set_active_reaction } from '../../runtime.js'; +import { flush_sync, set_active_effect, set_active_reaction } from '../../runtime.js'; import { hydrate_next, hydrate_node, hydrating } from '../hydration.js'; import { queue_micro_task } from '../task.js'; import { UNINITIALIZED } from '../../../../constants.js'; import { component_context, + is_runes, set_component_context, set_dev_current_component_function } from '../../context.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/key.js b/packages/svelte/src/internal/client/dom/blocks/key.js index 4a8b7b94fc..a697163548 100644 --- a/packages/svelte/src/internal/client/dom/blocks/key.js +++ b/packages/svelte/src/internal/client/dom/blocks/key.js @@ -2,7 +2,7 @@ import { UNINITIALIZED } from '../../../../constants.js'; import { block, branch, pause_effect } from '../../reactivity/effects.js'; import { not_equal, safe_not_equal } from '../../reactivity/equality.js'; -import { is_runes } from '../../runtime.js'; +import { is_runes } from '../../context.js'; import { hydrate_next, hydrate_node, hydrating } from '../hydration.js'; /** diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js index 3ea1a24d7e..f1992007ed 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js @@ -5,7 +5,8 @@ import * as e from '../../../errors.js'; import { is } from '../../../proxy.js'; import { queue_micro_task } from '../../task.js'; import { hydrating } from '../../hydration.js'; -import { is_runes, untrack } from '../../../runtime.js'; +import { untrack } from '../../../runtime.js'; +import { is_runes } from '../../../context.js'; /** * @param {HTMLInputElement} input diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 877f3c59ee..3f3b29b029 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -110,7 +110,7 @@ export { user_effect, user_pre_effect } from './reactivity/effects.js'; -export { mutable_state, mutate, set, state } from './reactivity/sources.js'; +export { mutable_state, mutate, set, state, update, update_pre } from './reactivity/sources.js'; export { prop, rest_props, @@ -139,8 +139,6 @@ export { flush_sync, tick, untrack, - update, - update_pre, exclude_from_object, deep_read, deep_read_state diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 3e5a0258c7..d157642d5d 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -8,16 +8,9 @@ import { PROPS_IS_UPDATED } from '../../../constants.js'; import { get_descriptor, is_function } from '../../shared/utils.js'; -import { mutable_source, set, source } from './sources.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, - update -} from '../runtime.js'; +import { active_effect, get, captured_signals, set_active_effect, untrack } from '../runtime.js'; import { safe_equals } from './equality.js'; import * as e from '../errors.js'; import { diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index b3e66a3336..ded0ca0584 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -5,7 +5,6 @@ import { active_effect, untracked_writes, get, - is_runes, schedule_effect, set_untracked_writes, set_signal_status, @@ -34,7 +33,7 @@ import { 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 } from '../context.js'; +import { component_context, is_runes } from '../context.js'; export let inspect_effects = new Set(); @@ -226,6 +225,35 @@ export function internal_set(source, value) { return value; } +/** + * @template {number | bigint} T + * @param {Source} source + * @param {1 | -1} [d] + * @returns {T} + */ +export function update(source, d = 1) { + var value = get(source); + var result = d === 1 ? value++ : value--; + + set(source, value); + + // @ts-expect-error + return result; +} + +/** + * @template {number | bigint} T + * @param {Source} source + * @param {1 | -1} [d] + * @returns {T} + */ +export function update_pre(source, d = 1) { + var value = get(source); + + // @ts-expect-error + return set(source, d === 1 ? ++value : --value); +} + /** * @param {Value} signal * @param {number} status should be DIRTY or MAYBE_DIRTY diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 2c53ac8f7b..bf9d17fa23 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -26,7 +26,7 @@ import { BOUNDARY_EFFECT } from './constants.js'; import { flush_tasks } from './dom/task.js'; -import { internal_set, set } from './reactivity/sources.js'; +import { internal_set } from './reactivity/sources.js'; import { destroy_derived, destroy_derived_effects, @@ -35,11 +35,12 @@ import { } from './reactivity/deriveds.js'; import * as e from './errors.js'; import { FILENAME } from '../../constants.js'; -import { legacy_mode_flag, tracing_mode_flag } from '../flags/index.js'; +import { tracing_mode_flag } from '../flags/index.js'; import { tracing_expressions, get_stack } from './dev/tracing.js'; import { component_context, dev_current_component_function, + is_runes, set_component_context, set_dev_current_component_function } from './context.js'; @@ -161,11 +162,6 @@ export function increment_write_version() { return ++write_version; } -/** @returns {boolean} */ -export function is_runes() { - return !legacy_mode_flag || (component_context !== null && component_context.l === null); -} - /** * Determines whether a derived or effect is dirty. * If it is MAYBE_DIRTY, will set the status to CLEAN @@ -1095,35 +1091,6 @@ export function set_signal_status(signal, status) { signal.f = (signal.f & STATUS_MASK) | status; } -/** - * @template {number | bigint} T - * @param {Value} signal - * @param {1 | -1} [d] - * @returns {T} - */ -export function update(signal, d = 1) { - var value = get(signal); - var result = d === 1 ? value++ : value--; - - set(signal, value); - - // @ts-expect-error - return result; -} - -/** - * @template {number | bigint} T - * @param {Value} signal - * @param {1 | -1} [d] - * @returns {T} - */ -export function update_pre(signal, d = 1) { - var value = get(signal); - - // @ts-expect-error - return set(signal, d === 1 ? ++value : --value); -} - /** * @param {Record} obj * @param {string[]} keys @@ -1215,37 +1182,3 @@ export function deep_read(value, visited = new Set()) { } } } - -if (DEV) { - /** - * @param {string} rune - */ - function throw_rune_error(rune) { - if (!(rune in globalThis)) { - // TODO if people start adjusting the "this can contain runes" config through v-p-s more, adjust this message - /** @type {any} */ - let value; // let's hope noone modifies this global, but belts and braces - Object.defineProperty(globalThis, rune, { - configurable: true, - // eslint-disable-next-line getter-return - get: () => { - if (value !== undefined) { - return value; - } - - e.rune_outside_svelte(rune); - }, - set: (v) => { - value = v; - } - }); - } - } - - throw_rune_error('$state'); - throw_rune_error('$effect'); - throw_rune_error('$derived'); - throw_rune_error('$inspect'); - throw_rune_error('$props'); - throw_rune_error('$bindable'); -} diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 2ce624a777..bd9bc50ae3 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -8,7 +8,7 @@ import { render_effect, user_effect } from '../../src/internal/client/reactivity/effects'; -import { state, set } 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'; @@ -968,14 +968,14 @@ describe('signals', () => { return () => { const count = state(0n); - assert.doesNotThrow(() => $.update(count)); + assert.doesNotThrow(() => update(count)); assert.equal($.get(count), 1n); - assert.doesNotThrow(() => $.update(count, -1)); + assert.doesNotThrow(() => update(count, -1)); assert.equal($.get(count), 0n); - assert.doesNotThrow(() => $.update_pre(count)); + assert.doesNotThrow(() => update_pre(count)); assert.equal($.get(count), 1n); - assert.doesNotThrow(() => $.update_pre(count, -1)); + assert.doesNotThrow(() => update_pre(count, -1)); assert.equal($.get(count), 0n); }; }); From 522557559f22b47cbe2b1c782e5a363b72ea146a Mon Sep 17 00:00:00 2001 From: 7nik Date: Thu, 30 Jan 2025 16:51:25 +0200 Subject: [PATCH 25/67] fix: do not prune selectors like `:global(.foo):has(.scoped)` (#15140) Fixes #14910 The issue occurs only when :has() targets at a component's root element and because include_self is false. I came to the conclusion that this is the same case as :root:has(.scoped). --- .changeset/famous-bulldogs-tan.md | 5 +++++ .../src/compiler/phases/2-analyze/css/css-prune.js | 9 +++++++-- packages/svelte/tests/css/samples/has/_config.js | 14 ++++++++++++++ packages/svelte/tests/css/samples/has/expected.css | 7 +++++++ packages/svelte/tests/css/samples/has/input.svelte | 7 +++++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .changeset/famous-bulldogs-tan.md diff --git a/.changeset/famous-bulldogs-tan.md b/.changeset/famous-bulldogs-tan.md new file mode 100644 index 0000000000..a5cc14b7f2 --- /dev/null +++ b/.changeset/famous-bulldogs-tan.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: do not prune selectors like `:global(.foo):has(.scoped)` 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 109010e88c..e719895798 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 @@ -339,13 +339,18 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element) 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). + // 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) {} } const rules = get_parent_rules(rule); const include_self = rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) || rules[rules.length - 1].prelude.children.some((c) => c.children.some((r) => - r.selectors.some((s) => s.type === 'PseudoClassSelector' && s.name === 'root') + r.selectors.some( + (s) => + s.type === 'PseudoClassSelector' && + (s.name === 'root' || (s.name === 'global' && s.args)) + ) ) ); if (include_self) { diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js index 33bbe74949..8d89d98cbd 100644 --- a/packages/svelte/tests/css/samples/has/_config.js +++ b/packages/svelte/tests/css/samples/has/_config.js @@ -197,6 +197,20 @@ export default test({ column: 16, character: 1614 } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector ":global(.foo):has(.unused)"', + start: { + line: 155, + column: 1, + character: 1684 + }, + end: { + line: 155, + column: 27, + character: 1710 + } } ] }); diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 9627bf730c..b257370d61 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -136,3 +136,10 @@ color: red; }*/ } + + .foo:has(x.svelte-xyz) { + color: green; + } + /* (unused) :global(.foo):has(.unused) { + color: red; + }*/ diff --git a/packages/svelte/tests/css/samples/has/input.svelte b/packages/svelte/tests/css/samples/has/input.svelte index 946cf2df90..9b254996bf 100644 --- a/packages/svelte/tests/css/samples/has/input.svelte +++ b/packages/svelte/tests/css/samples/has/input.svelte @@ -148,4 +148,11 @@ color: red; } } + + :global(.foo):has(x) { + color: green; + } + :global(.foo):has(.unused) { + color: red; + } From f5406c952ed446c131eeef3da152c345bd952568 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:14:03 +0100 Subject: [PATCH 26/67] fix: widen ownership when calling setContext (#15153) Instead of doing ownership addition at each `getContext` call site, we do it once as the `setContext` call site and make the state basically global. - avoids potential false positives in edge cases - makes the whole thing more performant, especially around things like calling `getContext` inside a list item component - false negatives are unlikely from this fixes #15072 --- .changeset/unlucky-gorillas-hunt.md | 5 ++++ .../svelte/src/internal/client/context.js | 30 +++++++------------ .../src/internal/client/dev/ownership.js | 12 +++++--- 3 files changed, 24 insertions(+), 23 deletions(-) create mode 100644 .changeset/unlucky-gorillas-hunt.md diff --git a/.changeset/unlucky-gorillas-hunt.md b/.changeset/unlucky-gorillas-hunt.md new file mode 100644 index 0000000000..055fc120a6 --- /dev/null +++ b/.changeset/unlucky-gorillas-hunt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: widen ownership when calling setContext diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index e1088edf30..bd94d5ad8a 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -8,7 +8,8 @@ import { active_effect, active_reaction, set_active_effect, - set_active_reaction + set_active_reaction, + untrack } from './runtime.js'; import { effect } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; @@ -49,14 +50,6 @@ export function set_dev_current_component_function(fn) { export function getContext(key) { const context_map = get_or_init_context_map('getContext'); const result = /** @type {T} */ (context_map.get(key)); - - if (DEV) { - const fn = /** @type {ComponentContext} */ (component_context).function; - if (fn) { - add_owner(result, fn, true); - } - } - return result; } @@ -74,6 +67,15 @@ 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; } @@ -100,16 +102,6 @@ export function hasContext(key) { */ export function getAllContexts() { const context_map = get_or_init_context_map('getAllContexts'); - - if (DEV) { - const fn = component_context?.function; - if (fn) { - for (const value of context_map.values()) { - add_owner(value, fn, true); - } - } - } - return /** @type {T} */ (context_map); } diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index 70cfbb47f3..a9506cfdc0 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -109,7 +109,7 @@ export function mark_module_end(component) { /** * @param {any} object - * @param {any} owner + * @param {any | null} owner * @param {boolean} [global] * @param {boolean} [skip_warning] */ @@ -120,7 +120,7 @@ export function add_owner(object, owner, global = false, skip_warning = false) { if (metadata && !has_owner(metadata, component)) { let original = get_owner(metadata); - if (owner[FILENAME] !== component[FILENAME] && !skip_warning) { + if (owner && owner[FILENAME] !== component[FILENAME] && !skip_warning) { w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]); } } @@ -165,7 +165,7 @@ export function widen_ownership(from, to) { /** * @param {any} object - * @param {Function} owner + * @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) { @@ -174,7 +174,11 @@ function add_owner_to_object(object, owner, seen) { if (metadata) { // this is a state proxy, add owner directly, if not globally shared if ('owners' in metadata && metadata.owners != null) { - metadata.owners.add(owner); + if (owner) { + metadata.owners.add(owner); + } else { + metadata.owners = null; + } } } else if (object && typeof object === 'object') { if (seen.has(object)) return; From b8607f876577614b1a5f798165343526bbb74b0b Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:14:22 +0100 Subject: [PATCH 27/67] fix: silence a11y attribute warnings when spread attributes present (#15150) Fixes #15067 --- .changeset/odd-rules-hear.md | 5 +++ .../phases/2-analyze/visitors/shared/a11y.js | 27 +++++++++------ .../input.svelte | 1 + .../input.svelte | 18 +++++----- .../warnings.json | 34 +++---------------- 5 files changed, 36 insertions(+), 49 deletions(-) create mode 100644 .changeset/odd-rules-hear.md diff --git a/.changeset/odd-rules-hear.md b/.changeset/odd-rules-hear.md new file mode 100644 index 0000000000..325b8ddf96 --- /dev/null +++ b/.changeset/odd-rules-hear.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: silence a11y attribute warnings when spread attributes present diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js index a5ca8463a4..24a8e5122d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js @@ -756,7 +756,8 @@ export function check_element(node, context) { name === 'aria-activedescendant' && !is_dynamic_element && !is_interactive_element(node.name, attribute_map) && - !attribute_map.has('tabindex') + !attribute_map.has('tabindex') && + !has_spread ) { w.a11y_aria_activedescendant_has_tabindex(attribute); } @@ -810,9 +811,9 @@ export function check_element(node, context) { const role = roles_map.get(current_role); if (role) { const required_role_props = Object.keys(role.requiredProps); - const has_missing_props = required_role_props.some( - (prop) => !attributes.find((a) => a.name === prop) - ); + const has_missing_props = + !has_spread && + required_role_props.some((prop) => !attributes.find((a) => a.name === prop)); if (has_missing_props) { w.a11y_role_has_required_aria_props( attribute, @@ -828,6 +829,7 @@ export function check_element(node, context) { // interactive-supports-focus if ( + !has_spread && !has_disabled_attribute(attribute_map) && !is_hidden_from_screen_reader(node.name, attribute_map) && !is_presentation_role(current_role) && @@ -845,6 +847,7 @@ export function check_element(node, context) { // no-interactive-element-to-noninteractive-role if ( + !has_spread && is_interactive_element(node.name, attribute_map) && (is_non_interactive_roles(current_role) || is_presentation_role(current_role)) ) { @@ -853,6 +856,7 @@ export function check_element(node, context) { // no-noninteractive-element-to-interactive-role if ( + !has_spread && is_non_interactive_element(node.name, attribute_map) && is_interactive_roles(current_role) && !a11y_non_interactive_element_to_interactive_role_exceptions[node.name]?.includes( @@ -947,6 +951,7 @@ export function check_element(node, context) { // no-noninteractive-element-interactions if ( + !has_spread && !has_contenteditable_attr && !is_hidden_from_screen_reader(node.name, attribute_map) && !is_presentation_role(role_static_value) && @@ -964,6 +969,7 @@ export function check_element(node, context) { // no-static-element-interactions if ( + !has_spread && (!role || role_static_value !== null) && !is_hidden_from_screen_reader(node.name, attribute_map) && !is_presentation_role(role_static_value) && @@ -981,11 +987,11 @@ export function check_element(node, context) { } } - if (handlers.has('mouseover') && !handlers.has('focus')) { + if (!has_spread && handlers.has('mouseover') && !handlers.has('focus')) { w.a11y_mouse_events_have_key_events(node, 'mouseover', 'focus'); } - if (handlers.has('mouseout') && !handlers.has('blur')) { + if (!has_spread && handlers.has('mouseout') && !handlers.has('blur')) { w.a11y_mouse_events_have_key_events(node, 'mouseout', 'blur'); } @@ -995,7 +1001,7 @@ export function check_element(node, context) { if (node.name === 'a' || node.name === 'button') { const is_hidden = get_static_value(attribute_map.get('aria-hidden')) === 'true'; - if (!is_hidden && !is_labelled && !has_content(node)) { + if (!has_spread && !is_hidden && !is_labelled && !has_content(node)) { w.a11y_consider_explicit_label(node); } } @@ -1054,7 +1060,7 @@ export function check_element(node, context) { if (node.name === 'img') { const alt_attribute = get_static_text_value(attribute_map.get('alt')); const aria_hidden = get_static_value(attribute_map.get('aria-hidden')); - if (alt_attribute && !aria_hidden) { + if (alt_attribute && !aria_hidden && !has_spread) { if (/\b(image|picture|photo)\b/i.test(alt_attribute)) { w.a11y_img_redundant_alt(node); } @@ -1087,7 +1093,7 @@ export function check_element(node, context) { ); return has; }; - if (!attribute_map.has('for') && !has_input_child(node)) { + if (!has_spread && !attribute_map.has('for') && !has_input_child(node)) { w.a11y_label_has_associated_control(node); } } @@ -1095,7 +1101,7 @@ export function check_element(node, context) { if (node.name === 'video') { const aria_hidden_attribute = attribute_map.get('aria-hidden'); const aria_hidden_exist = aria_hidden_attribute && get_static_value(aria_hidden_attribute); - if (attribute_map.has('muted') || aria_hidden_exist === 'true') { + if (attribute_map.has('muted') || aria_hidden_exist === 'true' || has_spread) { return; } let has_caption = false; @@ -1141,6 +1147,7 @@ export function check_element(node, context) { // Check content if ( + !has_spread && !is_labelled && !has_contenteditable_binding && a11y_required_content.includes(node.name) && diff --git a/packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte b/packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte index 124888c089..f47743b33b 100644 --- a/packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte @@ -10,3 +10,4 @@ G + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/input.svelte b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/input.svelte index 613b80e6d9..f9fe4f15c1 100644 --- a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/input.svelte @@ -1,21 +1,19 @@ -
          void 0}>
          +
          {}}>
          -
          void 0} on:focus={() => void 0}>
          +
          {}} onfocus={() => {}}>
          -
          void 0} {...otherProps}>
          +
          {}} {...otherProps}>
          -
          void 0}>
          +
          {}}>
          -
          void 0} on:blur={() => void 0}>
          +
          {}} onblur={() => {}}>
          -
          void 0} {...otherProps}>
          +
          {}} {...otherProps}>
          diff --git a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json index 574b019e0f..3dee4e9673 100644 --- a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json @@ -2,49 +2,25 @@ { "code": "a11y_mouse_events_have_key_events", "end": { - "column": 39, - "line": 11 + "column": 34, + "line": 9 }, "message": "'mouseover' event must be accompanied by 'focus' event", "start": { "column": 0, - "line": 11 + "line": 9 } }, { "code": "a11y_mouse_events_have_key_events", "end": { - "column": 55, + "column": 33, "line": 15 }, - "message": "'mouseover' event must be accompanied by 'focus' event", - "start": { - "column": 0, - "line": 15 - } - }, - { - "code": "a11y_mouse_events_have_key_events", - "end": { - "column": 38, - "line": 17 - }, "message": "'mouseout' event must be accompanied by 'blur' event", "start": { "column": 0, - "line": 17 - } - }, - { - "code": "a11y_mouse_events_have_key_events", - "end": { - "column": 54, - "line": 21 - }, - "message": "'mouseout' event must be accompanied by 'blur' event", - "start": { - "column": 0, - "line": 21 + "line": 15 } } ] From 83f00ebbd6e3400bf059690d132b31afa177c85a Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:14:46 +0100 Subject: [PATCH 28/67] fix: don't error on slot prop inside block inside other component (#15148) `slot` is treated as a regular prop if it is not used directly inside another component, but we were running validations on such regular props that should only be run on real slots. Fixes #15125 --- .changeset/good-rocks-talk.md | 5 +++ .../2-analyze/visitors/shared/attribute.js | 44 ++++++++++--------- .../slot-attribute-component/input.svelte | 6 +++ 3 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 .changeset/good-rocks-talk.md diff --git a/.changeset/good-rocks-talk.md b/.changeset/good-rocks-talk.md new file mode 100644 index 0000000000..59af86c686 --- /dev/null +++ b/.changeset/good-rocks-talk.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't error on slot prop inside block inside other component diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js index 198e464ac7..19bd7b6e54 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js @@ -80,40 +80,42 @@ export function validate_slot_attribute(context, attribute, is_component = false } if (owner) { - if (!is_text_attribute(attribute)) { - e.slot_attribute_invalid(attribute); - } - if ( owner.type === 'Component' || owner.type === 'SvelteComponent' || owner.type === 'SvelteSelf' ) { if (owner !== parent) { - e.slot_attribute_invalid_placement(attribute); - } + if (!is_component) { + e.slot_attribute_invalid_placement(attribute); + } + } else { + if (!is_text_attribute(attribute)) { + e.slot_attribute_invalid(attribute); + } - const name = attribute.value[0].data; + const name = attribute.value[0].data; - if (context.state.component_slots.has(name)) { - e.slot_attribute_duplicate(attribute, name, owner.name); - } - - context.state.component_slots.add(name); + if (context.state.component_slots.has(name)) { + e.slot_attribute_duplicate(attribute, name, owner.name); + } - if (name === 'default') { - for (const node of owner.fragment.nodes) { - if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) { - continue; - } + context.state.component_slots.add(name); - if (node.type === 'RegularElement' || node.type === 'SvelteFragment') { - if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) { + if (name === 'default') { + for (const node of owner.fragment.nodes) { + if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) { continue; } - } - e.slot_default_duplicate(node); + if (node.type === 'RegularElement' || node.type === 'SvelteFragment') { + if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) { + continue; + } + } + + e.slot_default_duplicate(node); + } } } } diff --git a/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte b/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte index 5acb14e409..5d559e614e 100644 --- a/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte +++ b/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte @@ -1,2 +1,8 @@ valid valid + + {#if true} + valid + valid + {/if} + \ No newline at end of file From 970aa7cfaa70707da1b87c3ca0af264c7589db9d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:15:04 +0100 Subject: [PATCH 29/67] fix: prevent false-positive ownership validations due to hot reload (#15154) The component identity could change due to HMR, so we fall back to checking the filenames as well fixes #14746 --- .changeset/ten-cougars-look.md | 5 +++++ packages/svelte/src/internal/client/dev/ownership.js | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .changeset/ten-cougars-look.md diff --git a/.changeset/ten-cougars-look.md b/.changeset/ten-cougars-look.md new file mode 100644 index 0000000000..fe20d057dd --- /dev/null +++ b/.changeset/ten-cougars-look.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent false-positive ownership validations due to hot reload diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index a9506cfdc0..2a2527803a 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -220,6 +220,10 @@ function has_owner(metadata, component) { return ( metadata.owners.has(component) || + // This helps avoid false positives when using HMR, where the component function is replaced + [...metadata.owners].some( + (owner) => /** @type {any} */ (owner)[FILENAME] === /** @type {any} */ (component)?.[FILENAME] + ) || (metadata.parent !== null && has_owner(metadata.parent, component)) ); } From 7bef5963bd3c5eb05f7722e09b4e6b806c2da337 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 30 Jan 2025 18:17:27 +0000 Subject: [PATCH 30/67] fix: ensure reactions are correctly attached for unowned deriveds (#15158) * fix: ensure reactions are correctly attached for unowned deriveds * tune --- .changeset/loud-cars-scream.md | 5 +++++ packages/svelte/src/internal/client/runtime.js | 13 +++++++++---- .../samples/derived-unowned-11/Child.svelte | 5 +++++ .../samples/derived-unowned-11/Child2.svelte | 5 +++++ .../samples/derived-unowned-11/_config.js | 16 ++++++++++++++++ .../samples/derived-unowned-11/main.svelte | 11 +++++++++++ 6 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 .changeset/loud-cars-scream.md create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte diff --git a/.changeset/loud-cars-scream.md b/.changeset/loud-cars-scream.md new file mode 100644 index 0000000000..2b61bc4453 --- /dev/null +++ b/.changeset/loud-cars-scream.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure reactions are correctly attached for unowned deriveds diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index bf9d17fa23..a572e27bf4 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -802,12 +802,19 @@ function process_effects(effect, collected_effects) { if (is_branch) { current_effect.f ^= CLEAN; } else { + // Ensure we set the effect to be the active reaction + // to ensure that unowned deriveds are correctly tracked + // because we're flushing the current effect + var previous_active_reaction = active_reaction; try { + active_reaction = current_effect; if (check_dirtiness(current_effect)) { update_effect(current_effect); } } catch (error) { handle_error(error, current_effect, null, current_effect.ctx); + } finally { + active_reaction = previous_active_reaction; } } @@ -952,13 +959,11 @@ export function get(signal) { var derived = /** @type {Derived} */ (signal); var parent = derived.parent; - if (parent !== null) { + if (parent !== null && (parent.f & UNOWNED) === 0) { // If the derived is owned by another derived then mark it as unowned // as the derived value might have been referenced in a different context // since and thus its parent might not be its true owner anymore - if ((parent.f & UNOWNED) === 0) { - derived.f ^= UNOWNED; - } + derived.f ^= UNOWNED; } } diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte new file mode 100644 index 0000000000..cd215304a3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte new file mode 100644 index 0000000000..a1d9f93bec --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte @@ -0,0 +1,5 @@ + + +{disabled} diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js new file mode 100644 index 0000000000..9948f91966 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + let [btn1, btn2] = target.querySelectorAll('button'); + + btn1?.click(); + flushSync(); + + btn2?.click(); + flushSync(); + + assert.htmlEqual(target.innerHTML, `\nfalse`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte new file mode 100644 index 0000000000..0219acdf7f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte @@ -0,0 +1,11 @@ + + + + + + From 6df59055e7f67328b3b3c3495120a14db4d8544b Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:46:53 +0100 Subject: [PATCH 31/67] docs: more examples on what is outside the rendering process (#15157) closes #15151 --- documentation/docs/05-special-elements/01-svelte-boundary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/05-special-elements/01-svelte-boundary.md b/documentation/docs/05-special-elements/01-svelte-boundary.md index 15f249a771..f5439b4b83 100644 --- a/documentation/docs/05-special-elements/01-svelte-boundary.md +++ b/documentation/docs/05-special-elements/01-svelte-boundary.md @@ -13,7 +13,7 @@ Boundaries allow you to guard against errors in part of your app from breaking t If an error occurs while rendering or updating the children of a ``, or running any [`$effect`]($effect) functions contained therein, the contents will be removed. -Errors occurring outside the rendering process (for example, in event handlers) are _not_ caught by error boundaries. +Errors occurring outside the rendering process (for example, in event handlers or after a `setTimeout` or async work) are _not_ caught by error boundaries. ## Properties From 674f81b5ceaa1968263b74547b7b2887f74f02fd Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Fri, 31 Jan 2025 02:48:35 +0800 Subject: [PATCH 32/67] [docs] clarify that `$effect` analyzes functions deeply (#15144) * Update 04-$effect.md * Update documentation/docs/02-runes/04-$effect.md --------- Co-authored-by: Rich Harris --- documentation/docs/02-runes/04-$effect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md index 1ea960de70..da24084d4d 100644 --- a/documentation/docs/02-runes/04-$effect.md +++ b/documentation/docs/02-runes/04-$effect.md @@ -66,7 +66,7 @@ You can return a function from `$effect`, which will run immediately before the ### Understanding dependencies -`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body and registers them as dependencies. When those dependencies change, the `$effect` schedules a rerun. +`$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 rerun. 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=)): From 2be3823e3aaaad753d65d21a160e342bd9b1c514 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Jan 2025 13:49:01 -0500 Subject: [PATCH 33/67] chore: remove inert check from each block reconciliation (#15143) --- .../src/internal/client/dom/blocks/each.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 040e585215..3baa03a917 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -219,17 +219,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f } if (!hydrating) { - var effect = /** @type {Effect} */ (active_reaction); - reconcile( - array, - state, - anchor, - render_fn, - flags, - (effect.f & INERT) !== 0, - get_key, - get_collection - ); + reconcile(array, state, anchor, render_fn, flags, get_key, get_collection); } if (fallback_fn !== null) { @@ -273,12 +263,11 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f * @param {Element | Comment | Text} anchor * @param {(anchor: Node, item: MaybeSource, index: number | Source, collection: () => V[]) => void} render_fn * @param {number} flags - * @param {boolean} is_inert * @param {(value: V, index: number) => any} get_key * @param {() => V[]} get_collection * @returns {void} */ -function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, get_collection) { +function reconcile(array, state, anchor, render_fn, flags, get_key, get_collection) { var is_animated = (flags & EACH_IS_ANIMATED) !== 0; var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; @@ -420,7 +409,7 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, ge while (current !== null && current.k !== key) { // If the each block isn't inert and an item has an effect that is already inert, // skip over adding it to our seen Set as the item is already being handled - if (is_inert || (current.e.f & INERT) === 0) { + if ((current.e.f & INERT) === 0) { (seen ??= new Set()).add(current); } stashed.push(current); @@ -444,7 +433,7 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, ge while (current !== null) { // If the each block isn't inert, then inert effects are currently outroing and will be removed once the transition is finished - if (is_inert || (current.e.f & INERT) === 0) { + if ((current.e.f & INERT) === 0) { to_destroy.push(current); } current = current.next; From 04addca428411088045b5a1774511593b5acc040 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:50:00 -0500 Subject: [PATCH 34/67] Version Packages (#15156) Co-authored-by: github-actions[bot] --- .changeset/famous-bulldogs-tan.md | 5 ----- .changeset/good-rocks-talk.md | 5 ----- .changeset/loud-cars-scream.md | 5 ----- .changeset/odd-rules-hear.md | 5 ----- .changeset/ten-cougars-look.md | 5 ----- .changeset/unlucky-gorillas-hunt.md | 5 ----- packages/svelte/CHANGELOG.md | 16 ++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 9 files changed, 18 insertions(+), 32 deletions(-) delete mode 100644 .changeset/famous-bulldogs-tan.md delete mode 100644 .changeset/good-rocks-talk.md delete mode 100644 .changeset/loud-cars-scream.md delete mode 100644 .changeset/odd-rules-hear.md delete mode 100644 .changeset/ten-cougars-look.md delete mode 100644 .changeset/unlucky-gorillas-hunt.md diff --git a/.changeset/famous-bulldogs-tan.md b/.changeset/famous-bulldogs-tan.md deleted file mode 100644 index a5cc14b7f2..0000000000 --- a/.changeset/famous-bulldogs-tan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: do not prune selectors like `:global(.foo):has(.scoped)` diff --git a/.changeset/good-rocks-talk.md b/.changeset/good-rocks-talk.md deleted file mode 100644 index 59af86c686..0000000000 --- a/.changeset/good-rocks-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't error on slot prop inside block inside other component diff --git a/.changeset/loud-cars-scream.md b/.changeset/loud-cars-scream.md deleted file mode 100644 index 2b61bc4453..0000000000 --- a/.changeset/loud-cars-scream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure reactions are correctly attached for unowned deriveds diff --git a/.changeset/odd-rules-hear.md b/.changeset/odd-rules-hear.md deleted file mode 100644 index 325b8ddf96..0000000000 --- a/.changeset/odd-rules-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: silence a11y attribute warnings when spread attributes present diff --git a/.changeset/ten-cougars-look.md b/.changeset/ten-cougars-look.md deleted file mode 100644 index fe20d057dd..0000000000 --- a/.changeset/ten-cougars-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prevent false-positive ownership validations due to hot reload diff --git a/.changeset/unlucky-gorillas-hunt.md b/.changeset/unlucky-gorillas-hunt.md deleted file mode 100644 index 055fc120a6..0000000000 --- a/.changeset/unlucky-gorillas-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: widen ownership when calling setContext diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index bb48391e0e..0e8073cc5d 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,21 @@ # svelte +## 5.19.6 + +### Patch Changes + +- fix: do not prune selectors like `:global(.foo):has(.scoped)` ([#15140](https://github.com/sveltejs/svelte/pull/15140)) + +- fix: don't error on slot prop inside block inside other component ([#15148](https://github.com/sveltejs/svelte/pull/15148)) + +- fix: ensure reactions are correctly attached for unowned deriveds ([#15158](https://github.com/sveltejs/svelte/pull/15158)) + +- fix: silence a11y attribute warnings when spread attributes present ([#15150](https://github.com/sveltejs/svelte/pull/15150)) + +- fix: prevent false-positive ownership validations due to hot reload ([#15154](https://github.com/sveltejs/svelte/pull/15154)) + +- fix: widen ownership when calling setContext ([#15153](https://github.com/sveltejs/svelte/pull/15153)) + ## 5.19.5 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a44ba43364..a286fa119d 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.19.5", + "version": "5.19.6", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index a509ef3fec..03ab37b583 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.19.5'; +export const VERSION = '5.19.6'; export const PUBLIC_VERSION = '5'; From e83ab1c0382199b50b0d2255668cd3f13cc603a2 Mon Sep 17 00:00:00 2001 From: tomoam <29677552+tomoam@users.noreply.github.com> Date: Fri, 31 Jan 2025 05:12:31 +0900 Subject: [PATCH 36/67] docs: fix typos and a link (#15135) --- documentation/docs/03-template-syntax/09-@const.md | 2 +- documentation/docs/03-template-syntax/11-bind.md | 2 +- documentation/docs/04-styling/01-scoped-styles.md | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/documentation/docs/03-template-syntax/09-@const.md b/documentation/docs/03-template-syntax/09-@const.md index c42d3560fd..2a587b7a3d 100644 --- a/documentation/docs/03-template-syntax/09-@const.md +++ b/documentation/docs/03-template-syntax/09-@const.md @@ -11,4 +11,4 @@ The `{@const ...}` tag defines a local constant. {/each} ``` -`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `` or a `` or a ``. diff --git a/documentation/docs/03-template-syntax/11-bind.md b/documentation/docs/03-template-syntax/11-bind.md index 90046c8c45..e56c2b4f77 100644 --- a/documentation/docs/03-template-syntax/11-bind.md +++ b/documentation/docs/03-template-syntax/11-bind.md @@ -235,7 +235,7 @@ You can give the `` a default value by adding a `selected` attribute to - [`volume`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/volume) - [`muted`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/muted) -...and seven readonly ones: +...and six readonly ones: - [`duration`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/duration) - [`buffered`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered) -- [`paused`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/paused) - [`seekable`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seekable) - [`seeking`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seeking_event) - [`ended`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended) From 64f86ee9d470089f378749d64826f6ef0d24d800 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 12:30:41 -0500 Subject: [PATCH 42/67] Version Packages (#15190) Co-authored-by: github-actions[bot] --- .changeset/blue-sheep-joke.md | 5 ----- .changeset/sweet-mails-clean.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/blue-sheep-joke.md delete mode 100644 .changeset/sweet-mails-clean.md diff --git a/.changeset/blue-sheep-joke.md b/.changeset/blue-sheep-joke.md deleted file mode 100644 index 1d9ff973c5..0000000000 --- a/.changeset/blue-sheep-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: remove unused code from signal logic diff --git a/.changeset/sweet-mails-clean.md b/.changeset/sweet-mails-clean.md deleted file mode 100644 index f82913abf7..0000000000 --- a/.changeset/sweet-mails-clean.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: encounter svelte:element in blocks as sibling during pruning css diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 0e8073cc5d..9ebd37aadf 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.19.7 + +### Patch Changes + +- chore: remove unused code from signal logic ([#15195](https://github.com/sveltejs/svelte/pull/15195)) + +- fix: encounter svelte:element in blocks as sibling during pruning css ([#15165](https://github.com/sveltejs/svelte/pull/15165)) + ## 5.19.6 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a286fa119d..8ea495af99 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.19.6", + "version": "5.19.7", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 03ab37b583..d3c6e6b321 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.19.6'; +export const VERSION = '5.19.7'; export const PUBLIC_VERSION = '5'; From f878736f3825e1832fa7344306358e877e20bd7f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Feb 2025 13:23:46 -0500 Subject: [PATCH 43/67] try this (#15196) --- .github/workflows/pkg.pr.new.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml index 4292ec900a..99f8153517 100644 --- a/.github/workflows/pkg.pr.new.yml +++ b/.github/workflows/pkg.pr.new.yml @@ -9,6 +9,9 @@ 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: From 7867e7584bf120a309811f63cb87527ee83242b1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Feb 2025 13:36:00 -0500 Subject: [PATCH 44/67] bump vite (#15198) --- playgrounds/sandbox/package.json | 2 +- pnpm-lock.yaml | 75 +++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json index 654a517c9f..09a84cadc9 100644 --- a/playgrounds/sandbox/package.json +++ b/playgrounds/sandbox/package.json @@ -16,7 +16,7 @@ "polka": "^1.0.0-next.25", "svelte": "workspace:*", "tiny-glob": "^0.2.9", - "vite": "^5.4.6", + "vite": "^5.4.14", "vite-plugin-inspect": "^0.8.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1979ce7b22..8288f229cb 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.6(@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.14(@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.9 version: 0.2.9 vite: - specifier: ^5.4.6 - version: 5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + 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) vite-plugin-inspect: specifier: ^0.8.4 - version: 0.8.4(rollup@4.22.4)(vite@5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + 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)) packages: @@ -2144,6 +2144,37 @@ packages: '@nuxt/kit': optional: true + vite@5.4.14: + resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} + 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 + vite@5.4.6: resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2757,25 +2788,25 @@ snapshots: typescript: 5.5.4 typescript-eslint: 8.2.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.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.6(@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.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))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.6(@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.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) debug: 4.3.6 svelte: link:packages/svelte - vite: 5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.14(@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.6(@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.14(@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.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.6(@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.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)) debug: 4.3.6 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 svelte: link:packages/svelte - vite: 5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) - vitefu: 0.2.5(vite@5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + 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)) transitivePeerDependencies: - supports-color @@ -4254,7 +4285,7 @@ snapshots: debug: 4.3.6 pathe: 1.1.2 tinyrainbow: 1.2.0 - vite: 5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - '@types/node' - less @@ -4266,7 +4297,7 @@ snapshots: - supports-color - terser - vite-plugin-inspect@0.8.4(rollup@4.22.4)(vite@5.4.6(@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.22.4)(vite@5.4.14(@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) @@ -4277,11 +4308,23 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.1.0 sirv: 2.0.4 - vite: 5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - rollup - supports-color + 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.4.47 + rollup: 4.22.4 + optionalDependencies: + '@types/node': 20.12.7 + fsevents: 2.3.3 + lightningcss: 1.23.0 + sass: 1.70.0 + terser: 5.27.0 + vite@5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: esbuild: 0.21.5 @@ -4294,9 +4337,9 @@ snapshots: sass: 1.70.0 terser: 5.27.0 - vitefu@0.2.5(vite@5.4.6(@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)): optionalDependencies: - vite: 5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) vitest@2.0.5(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: From 502b585ed233adaa097e5eb72e45b9936219b60d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 3 Feb 2025 19:37:49 +0100 Subject: [PATCH 45/67] docs: enhance migration docs about accessors (#15138) * docs: enhance migration docs about accessors related to #15134 * more --- .../docs/07-misc/07-v5-migration-guide.md | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/documentation/docs/07-misc/07-v5-migration-guide.md b/documentation/docs/07-misc/07-v5-migration-guide.md index ce95bf6ac7..94ade6e887 100644 --- a/documentation/docs/07-misc/07-v5-migration-guide.md +++ b/documentation/docs/07-misc/07-v5-migration-guide.md @@ -722,7 +722,39 @@ If a bindable property has a default value (e.g. `let { foo = $bindable('bar') } ### `accessors` option is ignored -Setting the `accessors` option to `true` makes properties of a component directly accessible on the component instance. In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them. +Setting the `accessors` option to `true` makes properties of a component directly accessible on the component instance. + +```svelte + + + +``` + +In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them. + +```svelte + +``` + +Alternatively, if the place where they are instantiated is under your control, you can also make use of runes inside `.js/.ts` files by adjusting their ending to include `.svelte`, i.e. `.svelte.js` or `.svelte.ts`, and then use `$state`: + +```js ++++import { mount } from 'svelte';+++ +import App from './App.svelte' + +---const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } }); +app.foo = 'baz'--- ++++const props = $state({ foo: 'bar' }); +const app = mount(App, { target: document.getElementById("app"), props }); +props.foo = 'baz';+++ +``` ### `immutable` option is ignored From f67cf201af118af2091a91adb3082da10e72a170 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:51:08 -0500 Subject: [PATCH 46/67] chore(deps-dev): bump vite from 5.4.14 to 6.0.9 (#15199) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.14 to 6.0.9. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.0.9/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 | 713 +++++++++++++++++++++++++++---- 2 files changed, 627 insertions(+), 88 deletions(-) diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json index 09a84cadc9..d9c490ba86 100644 --- a/playgrounds/sandbox/package.json +++ b/playgrounds/sandbox/package.json @@ -16,7 +16,7 @@ "polka": "^1.0.0-next.25", "svelte": "workspace:*", "tiny-glob": "^0.2.9", - "vite": "^5.4.14", + "vite": "^6.0.9", "vite-plugin-inspect": "^0.8.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8288f229cb..31d1e4b816 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@6.0.9(@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.9 version: 0.2.9 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: ^6.0.9 + version: 6.0.9(@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.34.1)(vite@6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) packages: @@ -270,148 +270,308 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + 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.11.0': resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/config-array@0.18.0': resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -545,81 +705,176 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.34.1': + resolution: {integrity: sha512-kwctwVlswSEsr4ljpmxKrRKp1eG1v2NAhlzFzDf1x1OdYaMjBYjDCbHkzWm57ZXzTwqn8stMXgROrnMw8dJK3w==} + 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.34.1': + resolution: {integrity: sha512-4H5ZtZitBPlbPsTv6HBB8zh1g5d0T8TzCmpndQdqq20Ugle/nroOyDMf9p7f88Gsu8vBLU78/cuh8FYHZqdXxw==} + 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.34.1': + resolution: {integrity: sha512-f2AJ7Qwx9z25hikXvg+asco8Sfuc5NCLg8rmqQBIOUoWys5sb/ZX9RkMZDPdnnDevXAMJA5AWLnRBmgdXGEUiA==} + 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.34.1': + resolution: {integrity: sha512-+/2JBrRfISCsWE4aEFXxd+7k9nWGXA8+wh7ZUHn/u8UDXOU9LN+QYKKhd57sIn6WRcorOnlqPMYFIwie/OHXWw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.34.1': + resolution: {integrity: sha512-SUeB0pYjIXwT2vfAMQ7E4ERPq9VGRrPR7Z+S4AMssah5EHIilYqjWQoTn5dkDtuIJUSTs8H+C9dwoEcg3b0sCA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.1': + resolution: {integrity: sha512-L3T66wAZiB/ooiPbxz0s6JEX6Sr2+HfgPSK+LMuZkaGZFAFCQAHiP3dbyqovYdNaiUXcl9TlgnIbcsIicAnOZg==} + 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.34.1': + resolution: {integrity: sha512-UBXdQ4+ATARuFgsFrQ+tAsKvBi/Hly99aSVdeCUiHV9dRTTpMU7OrM3WXGys1l40wKVNiOl0QYY6cZQJ2xhKlQ==} + 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.34.1': + resolution: {integrity: sha512-m/yfZ25HGdcCSwmopEJm00GP7xAUyVcBPjttGLRAqZ60X/bB4Qn6gP7XTwCIU6bITeKmIhhwZ4AMh2XLro+4+w==} + 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.34.1': + resolution: {integrity: sha512-Wy+cUmFuvziNL9qWRRzboNprqSQ/n38orbjRvd6byYWridp5TJ3CD+0+HUsbcWVSNz9bxkDUkyASGP0zS7GAvg==} + 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.34.1': + resolution: {integrity: sha512-CQ3MAGgiFmQW5XJX5W3wnxOBxKwFlUAgSXFA2SwgVRjrIiVt5LHfcQLeNSHKq5OEZwv+VCBwlD1+YKCjDG8cpg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.34.1': + resolution: {integrity: sha512-rSzb1TsY4lSwH811cYC3OC2O2mzNMhM13vcnA7/0T6Mtreqr3/qs6WMDriMRs8yvHDI54qxHgOk8EV5YRAHFbw==} + 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.34.1': + resolution: {integrity: sha512-fwr0n6NS0pG3QxxlqVYpfiY64Fd1Dqd8Cecje4ILAV01ROMp4aEdCj5ssHjRY3UwU7RJmeWd5fi89DBqMaTawg==} + 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.34.1': + resolution: {integrity: sha512-4uJb9qz7+Z/yUp5RPxDGGGUcoh0PnKF33QyWgEZ3X/GocpWb6Mb+skDh59FEt5d8+Skxqs9mng6Swa6B2AmQZg==} + 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.34.1': + resolution: {integrity: sha512-QlIo8ndocWBEnfmkYqj8vVtIUpIqJjfqKggjy7IdUncnt8BGixte1wDON7NJEvLg3Kzvqxtbo8tk+U1acYEBlw==} + 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.34.1': + resolution: {integrity: sha512-hzpleiKtq14GWjz3ahWvJXgU1DQC9DteiwcsY4HgqUJUGxZThlL66MotdUEK9zEo0PK/2ADeZGM9LIondE302A==} + 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.34.1': + resolution: {integrity: sha512-jqtKrO715hDlvUcEsPn55tZt2TEiBvBtCMkUuU0R6fO/WPT7lO9AONjPbd8II7/asSiNVQHCMn4OLGigSuxVQA==} + 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.34.1': + resolution: {integrity: sha512-RnHy7yFf2Wz8Jj1+h8klB93N0NHNHXFhNwAmiy9zJdpY7DE01VbEVtPdrK1kkILeIbHGRJjvfBDBhnxBr8kD4g==} + 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.34.1': + resolution: {integrity: sha512-i7aT5HdiZIcd7quhzvwQ2oAuX7zPYrYfkrd1QFfs28Po/i0q6kas/oRrzGlDhAEyug+1UfUtkWdmoVlLJj5x9Q==} + 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.34.1': + resolution: {integrity: sha512-k3MVFD9Oq+laHkw2N2v7ILgoa9017ZMF/inTtHzyTVZjYs9cSH18sdyAf6spBAJIGwJ5UaC7et2ZH1WCdlhkMw==} + 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} @@ -778,6 +1033,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} @@ -841,8 +1101,8 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} brace-expansion@1.1.11: @@ -855,6 +1115,10 @@ packages: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -885,8 +1149,8 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} - chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} ci-info@3.9.0: @@ -949,6 +1213,15 @@ packages: supports-color: optional: true + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -1011,8 +1284,8 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enhanced-resolve@5.17.1: - resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + enhanced-resolve@5.18.0: + resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} engines: {node: '>=10.13.0'} enquirer@2.4.1: @@ -1031,6 +1304,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1182,6 +1460,10 @@ packages: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -1237,8 +1519,8 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - get-tsconfig@4.7.6: - resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -1256,6 +1538,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@15.14.0: + resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + engines: {node: '>=18'} + globals@15.9.0: resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==} engines: {node: '>=18'} @@ -1318,8 +1604,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immutable@4.3.4: - resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} + immutable@4.3.7: + resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -1605,8 +1891,11 @@ packages: ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -1729,6 +2018,9 @@ packages: picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -1783,8 +2075,8 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss@8.4.47: - resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + postcss@8.5.1: + resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -1859,6 +2151,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.34.1: + resolution: {integrity: sha512-iYZ/+PcdLYSGfH3S+dGahlW/RWmsqDhLgj1BT9DH/xXJ0ggZN7xkdP9wipPNjjNLczI+fmMLmTB9pye+d2r4GQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + rrweb-cssom@0.7.1: resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} @@ -1893,6 +2190,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.0: + resolution: {integrity: sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==} + engines: {node: '>=10'} + hasBin: true + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} @@ -1996,11 +2298,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-eslint-parser@0.41.0: - resolution: {integrity: sha512-L6f4hOL+AbgfBIB52Z310pg1d2QjRqm7wy3kI1W6hhdhX5bvu7+f0R6w4ykp5HoDdzq+vGhIJmsisaiJDGmVfA==} + svelte-eslint-parser@0.43.0: + resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.191 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: svelte: optional: true @@ -2144,8 +2446,8 @@ packages: '@nuxt/kit': optional: true - vite@5.4.14: - resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} + vite@5.4.6: + resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2175,22 +2477,27 @@ packages: terser: optional: true - vite@5.4.6: - resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} - engines: {node: ^18.0.0 || >=20.0.0} + vite@6.0.9: + resolution: {integrity: sha512-MSgUxHcaXLtnBPktkbUSoQUANApKYuxZ6DrbVENlIorbhL2dZydTLaZ01tjUoE3szeFzlFk9ANOKk0xurh4MKA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' less: '*' lightningcss: ^1.21.0 sass: '*' sass-embedded: '*' stylus: '*' sugarss: '*' - terser: ^5.4.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + jiti: + optional: true less: optional: true lightningcss: @@ -2205,6 +2512,10 @@ packages: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true vitefu@0.2.5: resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} @@ -2505,79 +2816,161 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/aix-ppc64@0.24.2': + optional: true + '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm64@0.24.2': + optional: true + '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-arm@0.24.2': + optional: true + '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/android-x64@0.24.2': + optional: true + '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.24.2': + optional: true + '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/darwin-x64@0.24.2': + optional: true + '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.24.2': + optional: true + '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.24.2': + optional: true + '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm64@0.24.2': + optional: true + '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-arm@0.24.2': + optional: true + '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-ia32@0.24.2': + optional: true + '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-loong64@0.24.2': + optional: true + '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-mips64el@0.24.2': + optional: true + '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-ppc64@0.24.2': + optional: true + '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.24.2': + optional: true + '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-s390x@0.24.2': + optional: true + '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.24.2': + optional: true + '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.24.2': + optional: true + '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-arm64@0.24.2': + optional: true + '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-ia32@0.24.2': + optional: true + '@esbuild/win32-x64@0.21.5': optional: true + '@esbuild/win32-x64@0.24.2': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@9.9.1)': dependencies: eslint: 9.9.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.4.1(eslint@9.9.1)': + dependencies: + eslint: 9.9.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.11.0': {} + '@eslint-community/regexpp@4.12.1': {} + '@eslint/config-array@0.18.0': dependencies: '@eslint/object-schema': 2.1.4 @@ -2720,58 +3113,123 @@ snapshots: optionalDependencies: rollup: 4.22.4 + '@rollup/pluginutils@5.1.0(rollup@4.34.1)': + dependencies: + '@types/estree': 1.0.6 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 4.34.1 + '@rollup/rollup-android-arm-eabi@4.22.4': optional: true + '@rollup/rollup-android-arm-eabi@4.34.1': + optional: true + '@rollup/rollup-android-arm64@4.22.4': optional: true + '@rollup/rollup-android-arm64@4.34.1': + optional: true + '@rollup/rollup-darwin-arm64@4.22.4': optional: true + '@rollup/rollup-darwin-arm64@4.34.1': + optional: true + '@rollup/rollup-darwin-x64@4.22.4': optional: true + '@rollup/rollup-darwin-x64@4.34.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.1': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.22.4': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.34.1': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.22.4': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.34.1': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.22.4': optional: true + '@rollup/rollup-linux-arm64-gnu@4.34.1': + optional: true + '@rollup/rollup-linux-arm64-musl@4.22.4': optional: true + '@rollup/rollup-linux-arm64-musl@4.34.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.1': + optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.22.4': optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.34.1': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.22.4': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.34.1': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.22.4': optional: true + '@rollup/rollup-linux-s390x-gnu@4.34.1': + optional: true + '@rollup/rollup-linux-x64-gnu@4.22.4': optional: true + '@rollup/rollup-linux-x64-gnu@4.34.1': + optional: true + '@rollup/rollup-linux-x64-musl@4.22.4': optional: true + '@rollup/rollup-linux-x64-musl@4.34.1': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.22.4': optional: true + '@rollup/rollup-win32-arm64-msvc@4.34.1': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.22.4': optional: true + '@rollup/rollup-win32-ia32-msvc@4.34.1': + optional: true + '@rollup/rollup-win32-x64-msvc@4.22.4': optional: true + '@rollup/rollup-win32-x64-msvc@4.34.1': + optional: true + '@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1)': dependencies: '@types/eslint': 8.56.12 - acorn: 8.12.1 + acorn: 8.14.0 escape-string-regexp: 4.0.0 eslint: 9.9.1 eslint-visitor-keys: 3.4.3 @@ -2788,25 +3246,25 @@ snapshots: typescript: 5.5.4 typescript-eslint: 8.2.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@6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@6.0.9(@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@6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) debug: 4.3.6 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: 6.0.9(@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@6.0.9(@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@6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) debug: 4.3.6 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 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: 6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vitefu: 0.2.5(vite@6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) transitivePeerDependencies: - supports-color @@ -2976,12 +3434,18 @@ snapshots: dependencies: acorn: 8.12.1 + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + acorn-typescript@1.4.13(acorn@8.12.1): dependencies: acorn: 8.12.1 acorn@8.12.1: {} + acorn@8.14.0: {} + agent-base@7.1.1: dependencies: debug: 4.3.6 @@ -3035,7 +3499,7 @@ snapshots: dependencies: is-windows: 1.0.2 - binary-extensions@2.2.0: + binary-extensions@2.3.0: optional: true brace-expansion@1.1.11: @@ -3051,6 +3515,11 @@ snapshots: dependencies: fill-range: 7.0.1 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + optional: true + buffer-from@1.1.2: {} bundle-name@4.1.0: @@ -3078,10 +3547,10 @@ snapshots: check-error@2.1.1: {} - chokidar@3.5.3: + chokidar@3.6.0: dependencies: anymatch: 3.1.3 - braces: 3.0.2 + braces: 3.0.3 glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.3 @@ -3140,6 +3609,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.4.0: + dependencies: + ms: 2.1.3 + decimal.js@10.4.3: {} deep-eql@5.0.2: {} @@ -3189,7 +3662,7 @@ snapshots: emoji-regex@9.2.2: {} - enhanced-resolve@5.17.1: + enhanced-resolve@5.18.0: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 @@ -3229,12 +3702,40 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + escape-string-regexp@4.0.0: {} eslint-compat-utils@0.5.1(eslint@9.9.1): dependencies: eslint: 9.9.1 - semver: 7.6.3 + semver: 7.7.0 eslint-config-prettier@9.1.0(eslint@9.9.1): dependencies: @@ -3242,8 +3743,8 @@ snapshots: eslint-plugin-es-x@7.8.0(eslint@9.9.1): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/eslint-utils': 4.4.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) @@ -3251,31 +3752,31 @@ snapshots: eslint-plugin-n@17.9.0(eslint@9.9.1): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) - enhanced-resolve: 5.17.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.9.1) + enhanced-resolve: 5.18.0 eslint: 9.9.1 eslint-plugin-es-x: 7.8.0(eslint@9.9.1) - get-tsconfig: 4.7.6 - globals: 15.9.0 + get-tsconfig: 4.10.0 + globals: 15.14.0 ignore: 5.3.2 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.0 eslint-plugin-svelte@2.38.0(eslint@9.9.1)(svelte@packages+svelte): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.9.1) '@jridgewell/sourcemap-codec': 1.5.0 - debug: 4.3.6 + 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.4.47 - postcss-load-config: 3.1.4(postcss@8.4.47) - postcss-safe-parser: 6.0.0(postcss@8.4.47) + 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-selector-parser: 6.1.2 - semver: 7.6.3 - svelte-eslint-parser: 0.41.0(svelte@packages+svelte) + semver: 7.7.0 + svelte-eslint-parser: 0.43.0(svelte@packages+svelte) optionalDependencies: svelte: link:packages/svelte transitivePeerDependencies: @@ -3345,8 +3846,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -3423,6 +3924,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + optional: true + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -3481,7 +3987,7 @@ snapshots: get-stream@8.0.1: {} - get-tsconfig@4.7.6: + get-tsconfig@4.10.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -3504,6 +4010,8 @@ snapshots: globals@14.0.0: {} + globals@15.14.0: {} + globals@15.9.0: {} globalyzer@0.1.0: {} @@ -3563,7 +4071,7 @@ snapshots: ignore@5.3.2: {} - immutable@4.3.4: + immutable@4.3.7: optional: true import-fresh@3.3.0: @@ -3575,7 +4083,7 @@ snapshots: is-binary-path@2.1.0: dependencies: - binary-extensions: 2.2.0 + binary-extensions: 2.3.0 optional: true is-core-module@2.13.1: @@ -3835,7 +4343,9 @@ snapshots: ms@2.1.2: {} - nanoid@3.3.7: {} + ms@2.1.3: {} + + nanoid@3.3.8: {} natural-compare@1.4.0: {} @@ -3935,6 +4445,8 @@ snapshots: picocolors@1.1.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} picomatch@4.0.2: {} @@ -3954,30 +4466,30 @@ snapshots: '@polka/url': 1.0.0-next.25 trouter: 4.0.0 - postcss-load-config@3.1.4(postcss@8.4.47): + postcss-load-config@3.1.4(postcss@8.5.1): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.47 + postcss: 8.5.1 - postcss-safe-parser@6.0.0(postcss@8.4.47): + postcss-safe-parser@6.0.0(postcss@8.5.1): dependencies: - postcss: 8.4.47 + postcss: 8.5.1 - postcss-scss@4.0.9(postcss@8.4.47): + postcss-scss@4.0.9(postcss@8.5.1): dependencies: - postcss: 8.4.47 + postcss: 8.5.1 postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss@8.4.47: + postcss@8.5.1: dependencies: - nanoid: 3.3.7 - picocolors: 1.1.0 + nanoid: 3.3.8 + picocolors: 1.1.1 source-map-js: 1.2.1 prelude-ls@1.2.1: {} @@ -4053,6 +4565,31 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.22.4 fsevents: 2.3.3 + rollup@4.34.1: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.34.1 + '@rollup/rollup-android-arm64': 4.34.1 + '@rollup/rollup-darwin-arm64': 4.34.1 + '@rollup/rollup-darwin-x64': 4.34.1 + '@rollup/rollup-freebsd-arm64': 4.34.1 + '@rollup/rollup-freebsd-x64': 4.34.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.1 + '@rollup/rollup-linux-arm-musleabihf': 4.34.1 + '@rollup/rollup-linux-arm64-gnu': 4.34.1 + '@rollup/rollup-linux-arm64-musl': 4.34.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.1 + '@rollup/rollup-linux-riscv64-gnu': 4.34.1 + '@rollup/rollup-linux-s390x-gnu': 4.34.1 + '@rollup/rollup-linux-x64-gnu': 4.34.1 + '@rollup/rollup-linux-x64-musl': 4.34.1 + '@rollup/rollup-win32-arm64-msvc': 4.34.1 + '@rollup/rollup-win32-ia32-msvc': 4.34.1 + '@rollup/rollup-win32-x64-msvc': 4.34.1 + fsevents: 2.3.3 + rrweb-cssom@0.7.1: {} run-applescript@7.0.0: {} @@ -4071,8 +4608,8 @@ snapshots: sass@1.70.0: dependencies: - chokidar: 3.5.3 - immutable: 4.3.4 + chokidar: 3.6.0 + immutable: 4.3.7 source-map-js: 1.2.1 optional: true @@ -4082,6 +4619,8 @@ snapshots: semver@7.6.3: {} + semver@7.7.0: {} + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 @@ -4168,13 +4707,13 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-eslint-parser@0.41.0(svelte@packages+svelte): + svelte-eslint-parser@0.43.0(svelte@packages+svelte): dependencies: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - postcss: 8.4.47 - postcss-scss: 4.0.9(postcss@8.4.47) + postcss: 8.5.1 + postcss-scss: 4.0.9(postcss@8.5.1) optionalDependencies: svelte: link:packages/svelte @@ -4285,7 +4824,7 @@ snapshots: debug: 4.3.6 pathe: 1.1.2 tinyrainbow: 1.2.0 - vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - '@types/node' - less @@ -4297,10 +4836,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.34.1)(vite@6.0.9(@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.34.1) debug: 4.3.6 error-stack-parser-es: 0.1.1 fs-extra: 11.2.0 @@ -4308,15 +4847,15 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.1.0 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: 6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - rollup - supports-color - vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): + vite@5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: esbuild: 0.21.5 - postcss: 8.4.47 + postcss: 8.5.1 rollup: 4.22.4 optionalDependencies: '@types/node': 20.12.7 @@ -4325,11 +4864,11 @@ snapshots: sass: 1.70.0 terser: 5.27.0 - vite@5.4.6(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): + vite@6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: - esbuild: 0.21.5 - postcss: 8.4.47 - rollup: 4.22.4 + esbuild: 0.24.2 + postcss: 8.5.1 + rollup: 4.34.1 optionalDependencies: '@types/node': 20.12.7 fsevents: 2.3.3 @@ -4337,9 +4876,9 @@ 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)): + vitefu@0.2.5(vite@6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): optionalDependencies: - vite: 5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 6.0.9(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) vitest@2.0.5(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: From d39d8c0675fd1ab13fa752b6a57e471ae864f7eb Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Feb 2025 12:28:18 +0000 Subject: [PATCH 47/67] fix: ensure tracking returns true, even if in unowned (#15214) * fix: ensure tracking returns true, even if in unowned * fix: ensure tracking returns true, even if in unowned * Update packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/long-moles-join.md | 5 +++++ .../src/internal/client/reactivity/effects.js | 8 +------- .../samples/effect-tracking-unowned/_config.js | 16 ++++++++++++++++ .../samples/effect-tracking-unowned/main.svelte | 12 ++++++++++++ 4 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 .changeset/long-moles-join.md create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte diff --git a/.changeset/long-moles-join.md b/.changeset/long-moles-join.md new file mode 100644 index 0000000000..92c3d7bf9d --- /dev/null +++ b/.changeset/long-moles-join.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure tracking returns true, even if in unowned diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index eab6c767f8..9d7b5e9de6 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -164,13 +164,7 @@ function create_effect(type, fn, sync, push = true) { * @returns {boolean} */ export function effect_tracking() { - if (active_reaction === null || untracking) { - return false; - } - - // If it's skipped, that's because we're inside an unowned - // that is not being tracked by another reaction - return !skip_reaction; + return active_reaction !== null && !untracking; } /** diff --git a/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js new file mode 100644 index 0000000000..749b9997c2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const b1 = target.querySelector('button'); + + b1?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `Store: new

          Text: new message

          ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte new file mode 100644 index 0000000000..3c16e3c036 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte @@ -0,0 +1,12 @@ + + +Store: {$store} +

          Text: {text}

          + From ed1cf75b79f0bffbb5122f88a570b91deb744708 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Feb 2025 11:42:24 -0500 Subject: [PATCH 48/67] chore: add download/hash scripts for sandbox (#15218) * chore: add download/hash scripts for sandbox * remove logging --- playgrounds/sandbox/index.html | 2 +- playgrounds/sandbox/package.json | 4 +- playgrounds/sandbox/scripts/download.js | 52 +++++++++++++++++++++++++ playgrounds/sandbox/scripts/hash.js | 45 +++++++++++++++++++++ playgrounds/sandbox/ssr-dev.js | 2 +- playgrounds/sandbox/ssr-prod.js | 2 +- 6 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 playgrounds/sandbox/scripts/download.js create mode 100644 playgrounds/sandbox/scripts/hash.js diff --git a/playgrounds/sandbox/index.html b/playgrounds/sandbox/index.html index 512b5426a9..845538abf0 100644 --- a/playgrounds/sandbox/index.html +++ b/playgrounds/sandbox/index.html @@ -12,7 +12,7 @@ + + + + From 3b7066f5c611a9913ab62a5bf927443fc110d5f2 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 5 Feb 2025 20:22:58 +0100 Subject: [PATCH 53/67] fix: ensure custom element updates don't run in hydration mode (#15217) * fix: ensure custom element updates don't run in hydration mode When a custom element is created before Svelte hydration kicks in, it will scaffold itself, using the properties given via attributes. Now when a custom element property is set during Svelte's hydration, the Svelte custom element component could run logic like updating an each block. Without turning off hydration mode during that time, the update would try to pick up existing element nodes (because it thinks they must be there because of hydration mode), and crash. No test because it would require a setup where we can ensure the element is scaffolded before hydration runs. Fixes #15213 * changeset --------- Co-authored-by: Rich Harris --- .changeset/light-ligers-switch.md | 5 +++++ .../client/dom/elements/attributes.js | 22 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 .changeset/light-ligers-switch.md diff --git a/.changeset/light-ligers-switch.md b/.changeset/light-ligers-switch.md new file mode 100644 index 0000000000..51b3271047 --- /dev/null +++ b/.changeset/light-ligers-switch.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: ensure custom element updates don't run in hydration mode diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 8c86fe7e02..308f23d340 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -1,5 +1,5 @@ import { DEV } from 'esm-env'; -import { hydrating } from '../hydration.js'; +import { hydrating, set_hydrating } from '../hydration.js'; import { get_descriptors, get_prototype_of } from '../../../shared/utils.js'; import { create_event, delegate } from './events.js'; import { add_form_reset_listener, autofocus } from './misc.js'; @@ -213,6 +213,12 @@ 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; + if (hydrating) { + set_hydrating(false); + } set_active_reaction(null); set_active_effect(null); @@ -239,6 +245,9 @@ export function set_custom_element_data(node, prop, value) { } finally { set_active_reaction(previous_reaction); set_active_effect(previous_effect); + if (was_hydrating) { + set_hydrating(true); + } } } @@ -262,6 +271,13 @@ export function set_attributes( is_custom_element = false, skip_warning = false ) { + // 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; + if (is_hydrating_custom_element) { + set_hydrating(false); + } + var current = prev || {}; var is_option_element = element.tagName === 'OPTION'; @@ -416,6 +432,10 @@ export function set_attributes( } } + if (is_hydrating_custom_element) { + set_hydrating(true); + } + return current; } From b0c4fa52468fa00af5fe77c4caaf0f2e11a5b65c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:37:41 -0500 Subject: [PATCH 54/67] Version Packages (#15216) Co-authored-by: github-actions[bot] --- .changeset/great-planes-swim.md | 5 ----- .changeset/light-ligers-switch.md | 5 ----- .changeset/long-moles-join.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/great-planes-swim.md delete mode 100644 .changeset/light-ligers-switch.md delete mode 100644 .changeset/long-moles-join.md diff --git a/.changeset/great-planes-swim.md b/.changeset/great-planes-swim.md deleted file mode 100644 index ecf8fa17aa..0000000000 --- a/.changeset/great-planes-swim.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: properly set `value` property of custom elements diff --git a/.changeset/light-ligers-switch.md b/.changeset/light-ligers-switch.md deleted file mode 100644 index 51b3271047..0000000000 --- a/.changeset/light-ligers-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"svelte": patch ---- - -fix: ensure custom element updates don't run in hydration mode diff --git a/.changeset/long-moles-join.md b/.changeset/long-moles-join.md deleted file mode 100644 index 92c3d7bf9d..0000000000 --- a/.changeset/long-moles-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure tracking returns true, even if in unowned diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 9ebd37aadf..837697e70a 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.19.8 + +### Patch Changes + +- fix: properly set `value` property of custom elements ([#15206](https://github.com/sveltejs/svelte/pull/15206)) + +- fix: ensure custom element updates don't run in hydration mode ([#15217](https://github.com/sveltejs/svelte/pull/15217)) + +- fix: ensure tracking returns true, even if in unowned ([#15214](https://github.com/sveltejs/svelte/pull/15214)) + ## 5.19.7 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a9d42379cc..ddbae00601 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.19.7", + "version": "5.19.8", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index d3c6e6b321..70be2b216f 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.19.7'; +export const VERSION = '5.19.8'; export const PUBLIC_VERSION = '5'; From f2c83e5db708ea5d320248ee832ddcbef355a8b7 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 6 Feb 2025 21:25:04 +0000 Subject: [PATCH 55/67] =?UTF-8?q?fix:=20ensure=20unowned=20derived=20depen?= =?UTF-8?q?dencies=20are=20not=20duplicated=20when=20reac=E2=80=A6=20(#152?= =?UTF-8?q?32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: ensure unowned derived dependencies are not duplicated when reactions are skipped * added test case * added test case * add comment --- .changeset/stale-keys-taste.md | 5 +++++ .../svelte/src/internal/client/runtime.js | 16 ++++++-------- packages/svelte/tests/signals/test.ts | 22 +++++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 .changeset/stale-keys-taste.md diff --git a/.changeset/stale-keys-taste.md b/.changeset/stale-keys-taste.md new file mode 100644 index 0000000000..b1b02323bb --- /dev/null +++ b/.changeset/stale-keys-taste.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure unowned derived dependencies are not duplicated when reactions are skipped diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 57cefccc01..30f14b7356 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -188,7 +188,8 @@ export function check_dirtiness(reaction) { dependency = dependencies[i]; // We always re-add all reactions (even duplicates) if the derived was - // previously disconnected + // previously disconnected, however we don't if it was unowned as we + // de-duplicate dependencies in that case if (is_disconnected || !dependency?.reactions?.includes(reaction)) { (dependency.reactions ??= []).push(reaction); } @@ -404,15 +405,9 @@ export function update_reaction(reaction) { skipped_deps = 0; untracked_writes = null; active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; - // prettier-ignore skip_reaction = (flags & UNOWNED) !== 0 && - (!is_flushing_effect || - // If we were previously not in a reactive context and we're reading an unowned derived - // that was created inside another reaction, then we don't fully know the real owner and thus - // we need to skip adding any reactions for this unowned - ((previous_reaction === null || previous_untracking) && - /** @type {Derived} */ (reaction).parent !== null)); + (!is_flushing_effect || previous_reaction === null || previous_untracking); derived_sources = null; set_component_context(reaction.ctx); @@ -933,7 +928,10 @@ export function get(signal) { skipped_deps++; } else if (new_deps === null) { new_deps = [signal]; - } else { + } 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); } } diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index bd9bc50ae3..46209ede81 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -818,6 +818,28 @@ describe('signals', () => { }; }); + test('unowned deriveds dependencies are correctly de-duped', () => { + return () => { + let a = state(0); + let b = state(true); + let c = derived(() => $.get(a)); + let d = derived(() => ($.get(b) ? 1 : $.get(a) + $.get(c) + $.get(a))); + + $.get(d); + + assert.equal(d.deps?.length, 1); + + $.get(d); + + set(a, 1); + set(b, false); + + $.get(d); + + assert.equal(d.deps?.length, 3); + }; + }); + test('unowned deriveds correctly update', () => { return () => { const arr1 = proxy<{ a: number }[]>([]); From 4883fd2c83e92226eb17c9a686712351163c482f Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 6 Feb 2025 22:26:03 +0100 Subject: [PATCH 56/67] fix: hydrate `href` that is part of spread attributes (#15226) We had some duplicated and buggy code, removing it fixes #15120 --- .changeset/wicked-apes-sin.md | 5 +++++ .../svelte/src/internal/client/dom/elements/attributes.js | 6 +----- .../samples/repair-mismatched-a-href/_expected.html | 2 +- .../hydration/samples/repair-mismatched-a-href/main.svelte | 1 + 4 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 .changeset/wicked-apes-sin.md diff --git a/.changeset/wicked-apes-sin.md b/.changeset/wicked-apes-sin.md new file mode 100644 index 0000000000..f40294ddff --- /dev/null +++ b/.changeset/wicked-apes-sin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: hydrate `href` that is part of spread attributes diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 308f23d340..4a0f0cea0e 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -419,11 +419,7 @@ export function set_attributes( // @ts-ignore element[name] = value; } else if (typeof value !== 'function') { - if (hydrating && (name === 'src' || name === 'href' || name === 'srcset')) { - if (!skip_warning) check_src_in_dev_hydration(element, name, value ?? ''); - } else { - set_attribute(element, name, value); - } + set_attribute(element, name, value); } } if (key === 'style' && '__styles' in element) { diff --git a/packages/svelte/tests/hydration/samples/repair-mismatched-a-href/_expected.html b/packages/svelte/tests/hydration/samples/repair-mismatched-a-href/_expected.html index 2f5b652fac..e1076af2ec 100644 --- a/packages/svelte/tests/hydration/samples/repair-mismatched-a-href/_expected.html +++ b/packages/svelte/tests/hydration/samples/repair-mismatched-a-href/_expected.html @@ -1 +1 @@ -foo +foo foo diff --git a/packages/svelte/tests/hydration/samples/repair-mismatched-a-href/main.svelte b/packages/svelte/tests/hydration/samples/repair-mismatched-a-href/main.svelte index be01d05f8e..3f0c988016 100644 --- a/packages/svelte/tests/hydration/samples/repair-mismatched-a-href/main.svelte +++ b/packages/svelte/tests/hydration/samples/repair-mismatched-a-href/main.svelte @@ -3,3 +3,4 @@ foo +foo From c4d4349d0a89c9588e06c8ccc142ef2063582f55 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 21:29:58 +0000 Subject: [PATCH 57/67] Version Packages (#15234) Co-authored-by: github-actions[bot] --- .changeset/stale-keys-taste.md | 5 ----- .changeset/wicked-apes-sin.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/stale-keys-taste.md delete mode 100644 .changeset/wicked-apes-sin.md diff --git a/.changeset/stale-keys-taste.md b/.changeset/stale-keys-taste.md deleted file mode 100644 index b1b02323bb..0000000000 --- a/.changeset/stale-keys-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure unowned derived dependencies are not duplicated when reactions are skipped diff --git a/.changeset/wicked-apes-sin.md b/.changeset/wicked-apes-sin.md deleted file mode 100644 index f40294ddff..0000000000 --- a/.changeset/wicked-apes-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: hydrate `href` that is part of spread attributes diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 837697e70a..cf2f90b204 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.19.9 + +### Patch Changes + +- fix: ensure unowned derived dependencies are not duplicated when reactions are skipped ([#15232](https://github.com/sveltejs/svelte/pull/15232)) + +- fix: hydrate `href` that is part of spread attributes ([#15226](https://github.com/sveltejs/svelte/pull/15226)) + ## 5.19.8 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index ddbae00601..ec5e8cd6fc 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.19.8", + "version": "5.19.9", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 70be2b216f..aa5c7db036 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.19.8'; +export const VERSION = '5.19.9'; export const PUBLIC_VERSION = '5'; From 7ab18bba50e70d1d8c04bbcfe081b0a26e229a0a Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:14:23 -0800 Subject: [PATCH 58/67] chore: allow pnpm 10 (#15258) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index efbee35c08..2fe545b361 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "license": "MIT", "packageManager": "pnpm@9.4.0", "engines": { - "pnpm": "^9.0.0" + "pnpm": ">=9.0.0" }, "repository": { "type": "git", From 02788f8e620aa91e98f895ede544ab76b559b00f Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 11 Feb 2025 09:43:49 +0100 Subject: [PATCH 59/67] fix: allow mutation of private derived state (#15228) Also disallow reassignment of private derived state Fixes #15227 --- .changeset/rare-carpets-wave.md | 5 ++++ .../src/compiler/phases/2-analyze/index.js | 4 +-- .../src/compiler/phases/2-analyze/types.d.ts | 2 +- .../phases/2-analyze/visitors/ClassBody.js | 8 +++--- .../phases/2-analyze/visitors/shared/utils.js | 25 ++++++++----------- .../mutate-derived-private-field/errors.json | 1 + .../mutate-derived-private-field/input.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 +++++++ 13 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 .changeset/rare-carpets-wave.md create mode 100644 packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json create mode 100644 packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte create mode 100644 packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json create mode 100644 packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte create mode 100644 packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json create mode 100644 packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte create mode 100644 packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json create mode 100644 packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte diff --git a/.changeset/rare-carpets-wave.md b/.changeset/rare-carpets-wave.md new file mode 100644 index 0000000000..cfd90cad6a --- /dev/null +++ b/.changeset/rare-carpets-wave.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow mutation of private derived state diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index bce6672505..ad9db24e1e 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -605,7 +605,7 @@ export function analyze_component(root, source, options) { has_props_rune: false, component_slots: new Set(), expression: null, - private_derived_state: [], + derived_state: [], function_depth: scope.function_depth, instance_scope: instance.scope, reactive_statement: null, @@ -676,7 +676,7 @@ export function analyze_component(root, source, options) { reactive_statements: analysis.reactive_statements, component_slots: new Set(), expression: null, - private_derived_state: [], + derived_state: [], function_depth: scope.function_depth }; diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index 1e71accb9f..14b14f9c84 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -19,7 +19,7 @@ export interface AnalysisState { component_slots: Set; /** Information about the current expression/directive/block value */ expression: ExpressionMetadata | null; - private_derived_state: string[]; + derived_state: string[]; function_depth: number; // legacy stuff diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js index d445af0ebf..ed397258f8 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js @@ -8,20 +8,20 @@ import { get_rune } from '../../scope.js'; */ export function ClassBody(node, context) { /** @type {string[]} */ - const private_derived_state = []; + const derived_state = []; for (const definition of node.body) { if ( definition.type === 'PropertyDefinition' && - definition.key.type === 'PrivateIdentifier' && + (definition.key.type === 'PrivateIdentifier' || definition.key.type === 'Identifier') && definition.value?.type === 'CallExpression' ) { const rune = get_rune(definition.value, context.state.scope); if (rune === '$derived' || rune === '$derived.by') { - private_derived_state.push(definition.key.name); + derived_state.push(definition.key.name); } } } - context.next({ ...context.state, private_derived_state }); + context.next({ ...context.state, derived_state }); } 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 e265637c40..5fe2a8f24e 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 @@ -35,20 +35,17 @@ export function validate_assignment(node, argument, state) { } } - let object = /** @type {Expression | Super} */ (argument); - - /** @type {Expression | PrivateIdentifier | null} */ - let property = null; - - while (object.type === 'MemberExpression') { - property = object.property; - object = object.object; - } - - if (object.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') { - if (state.private_derived_state.includes(property.name)) { - e.constant_assignment(node, 'derived state'); - } + if ( + argument.type === 'MemberExpression' && + argument.object.type === 'ThisExpression' && + (((argument.property.type === 'PrivateIdentifier' || argument.property.type === 'Identifier') && + state.derived_state.includes(argument.property.name)) || + (argument.property.type === 'Literal' && + argument.property.value && + typeof argument.property.value === 'string' && + state.derived_state.includes(argument.property.value))) + ) { + e.constant_assignment(node, 'derived state'); } } diff --git a/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json b/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte b/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte new file mode 100644 index 0000000000..0a3df4bc1b --- /dev/null +++ b/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json new file mode 100644 index 0000000000..8681d84ab2 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json @@ -0,0 +1,14 @@ +[ + { + "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 new file mode 100644 index 0000000000..8f109c9e1f --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte @@ -0,0 +1,9 @@ + \ 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 new file mode 100644 index 0000000000..c211aa4608 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json @@ -0,0 +1,14 @@ +[ + { + "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 new file mode 100644 index 0000000000..62e2317e03 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte @@ -0,0 +1,9 @@ + \ 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 new file mode 100644 index 0000000000..98837589ac --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json @@ -0,0 +1,14 @@ +[ + { + "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 new file mode 100644 index 0000000000..e2c4693e86 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte @@ -0,0 +1,9 @@ + \ No newline at end of file From b602c59a2201f3be0d0e2ec357a59047fd9ee50d Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 11 Feb 2025 13:02:08 +0000 Subject: [PATCH 60/67] fix: when re-connecting unowned deriveds, remove their unowned flag (#15255) * fix: when an unowned derived is tracked again, remove unowned flag * fix: when an unowned derived is tracked again, remove unowned flag * test case * test case * cleanup unused logic * simplify * simplify * simplify * simplify * add test * fix other issue * back to original plan * Apply suggestions from code review --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/nasty-pigs-lay.md | 5 + .../internal/client/reactivity/deriveds.js | 11 +- .../src/internal/client/reactivity/props.js | 60 +++---- .../svelte/src/internal/client/runtime.js | 15 +- .../samples/derived-unowned-12/_config.js | 25 +++ .../samples/derived-unowned-12/main.svelte | 18 +++ packages/svelte/tests/signals/test.ts | 147 +++++++++++++++--- 7 files changed, 208 insertions(+), 73 deletions(-) create mode 100644 .changeset/nasty-pigs-lay.md create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-12/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-12/main.svelte diff --git a/.changeset/nasty-pigs-lay.md b/.changeset/nasty-pigs-lay.md new file mode 100644 index 0000000000..64ef72103a --- /dev/null +++ b/.changeset/nasty-pigs-lay.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: when re-connecting unowned deriveds, remove their unowned flag diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 60b55970e6..59a7ed0f16 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -1,18 +1,9 @@ /** @import { Derived, Effect } from '#client' */ import { DEV } from 'esm-env'; -import { - CLEAN, - DERIVED, - DESTROYED, - DIRTY, - EFFECT_HAS_DERIVED, - MAYBE_DIRTY, - UNOWNED -} from '../constants.js'; +import { CLEAN, DERIVED, DIRTY, EFFECT_HAS_DERIVED, MAYBE_DIRTY, UNOWNED } from '../constants.js'; import { active_reaction, active_effect, - remove_reactions, set_signal_status, skip_reaction, update_reaction, diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index d157642d5d..5a3b30281f 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -10,7 +10,15 @@ 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 } from '../runtime.js'; +import { + active_effect, + get, + captured_signals, + set_active_effect, + untrack, + active_reaction, + set_active_reaction +} from '../runtime.js'; import { safe_equals } from './equality.js'; import * as e from '../errors.js'; import { @@ -241,26 +249,6 @@ export function spread_props(...props) { return new Proxy({ props }, spread_props_handler); } -/** - * @template T - * @param {() => T} fn - * @returns {T} - */ -function with_parent_branch(fn) { - var effect = active_effect; - var previous_effect = active_effect; - - while (effect !== null && (effect.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0) { - effect = effect.parent; - } - try { - set_active_effect(effect); - return fn(); - } finally { - set_active_effect(previous_effect); - } -} - /** * 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. @@ -335,8 +323,8 @@ export function prop(props, key, flags, fallback) { } else { // Svelte 4 did not trigger updates when a primitive value was updated to the same value. // Replicate that behavior through using a derived - var derived_getter = with_parent_branch(() => - (immutable ? derived : derived_safe_equal)(() => /** @type {V} */ (props[key])) + var derived_getter = (immutable ? derived : derived_safe_equal)( + () => /** @type {V} */ (props[key]) ); derived_getter.f |= LEGACY_DERIVED_PROP; getter = () => { @@ -380,21 +368,19 @@ export function prop(props, key, flags, fallback) { // The derived returns the current value. The underlying mutable // source is written to from various places to persist this value. var inner_current_value = mutable_source(prop_value); - var current_value = with_parent_branch(() => - derived(() => { - var parent_value = getter(); - var child_value = get(inner_current_value); - - if (from_child) { - from_child = false; - was_from_child = true; - return child_value; - } + var current_value = derived(() => { + var parent_value = getter(); + var child_value = get(inner_current_value); + + if (from_child) { + from_child = false; + was_from_child = true; + return child_value; + } - was_from_child = false; - return (inner_current_value.v = parent_value); - }) - ); + was_from_child = false; + return (inner_current_value.v = parent_value); + }); if (!immutable) current_value.equals = safe_equals; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 30f14b7356..8b0f84268c 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -184,19 +184,28 @@ export function check_dirtiness(reaction) { // If we are working with a disconnected or an unowned signal that is now connected (due to an active effect) // then we need to re-connect the reaction to the dependency if (is_disconnected || is_unowned_connected) { + var derived = /** @type {Derived} */ (reaction); + var parent = derived.parent; + for (i = 0; i < length; i++) { dependency = dependencies[i]; // We always re-add all reactions (even duplicates) if the derived was // previously disconnected, however we don't if it was unowned as we // de-duplicate dependencies in that case - if (is_disconnected || !dependency?.reactions?.includes(reaction)) { - (dependency.reactions ??= []).push(reaction); + if (is_disconnected || !dependency?.reactions?.includes(derived)) { + (dependency.reactions ??= []).push(derived); } } if (is_disconnected) { - reaction.f ^= DISCONNECTED; + derived.f ^= DISCONNECTED; + } + // If the unowned derived is now fully connected to the graph again (it's unowned and reconnected, has a parent + // and the parent is not unowned), then we can mark it as connected again, removing the need for the unowned + // flag + if (is_unowned_connected && parent !== null && (parent.f & UNOWNED) === 0) { + derived.f ^= UNOWNED; } } diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/_config.js new file mode 100644 index 0000000000..8cd4af0548 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/_config.js @@ -0,0 +1,25 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + let [btn1, btn2] = target.querySelectorAll('button'); + + btn1?.click(); + flushSync(); + + btn2?.click(); + flushSync(); + + btn1?.click(); + flushSync(); + + btn1?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `\n3\n\n1` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/main.svelte new file mode 100644 index 0000000000..48d4f5fd0b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/main.svelte @@ -0,0 +1,18 @@ + + + {linked.current} + {count} diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 46209ede81..046f833e0e 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -224,20 +224,75 @@ describe('signals', () => { }; }); - test('effects correctly handle unowned derived values that do not change', () => { - const log: number[] = []; + test('https://perf.js.hyoo.ru/#!bench=9h2as6_u0mfnn #2', () => { + let res: number[] = []; - let count = state(0); - const read = () => { - const x = derived(() => ({ count: $.get(count) })); - return $.get(x); + const numbers = Array.from({ length: 2 }, (_, i) => i); + const fib = (n: number): number => (n < 2 ? 1 : fib(n - 1) + fib(n - 2)); + const hard = (n: number, l: string) => n + fib(16); + + const A = state(0); + const B = state(0); + + return () => { + const C = derived(() => ($.get(A) % 2) + ($.get(B) % 2)); + const D = derived(() => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2))); + const E = derived(() => hard($.get(C) + $.get(A) + $.get(D)[0]!, 'E')); + const F = derived(() => hard($.get(D)[0]! && $.get(B), 'F')); + const G = derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0]! + $.get(F)); + + const destroy = effect_root(() => { + effect(() => { + res.push(hard($.get(G), 'H')); + }); + effect(() => { + res.push($.get(G)); + }); + effect(() => { + res.push(hard($.get(F), 'J')); + }); + }); + + flushSync(); + + let i = 2; + while (--i) { + res.length = 0; + set(B, 1); + set(A, 1 + i * 2); + flushSync(); + + set(A, 2 + i * 2); + set(B, 2); + flushSync(); + + assert.equal(res.length, 4); + assert.deepEqual(res, [3198, 1601, 3195, 1598]); + } + + destroy(); + assert(A.reactions === null); + assert(B.reactions === null); }; - const derivedCount = derived(() => read().count); - user_effect(() => { - log.push($.get(derivedCount)); - }); + }); + + test('effects correctly handle unowned derived values that do not change', () => { + const log: number[] = []; return () => { + let count = state(0); + const read = () => { + const x = derived(() => ({ count: $.get(count) })); + return $.get(x); + }; + const derivedCount = derived(() => read().count); + + const destroy = effect_root(() => { + user_effect(() => { + log.push($.get(derivedCount)); + }); + }); + flushSync(() => set(count, 1)); // Ensure we're not leaking consumers assert.deepEqual(count.reactions?.length, 1); @@ -248,6 +303,8 @@ describe('signals', () => { // Ensure we're not leaking consumers assert.deepEqual(count.reactions?.length, 1); assert.deepEqual(log, [0, 1, 2, 3]); + + destroy(); }; }); @@ -343,25 +400,69 @@ describe('signals', () => { }; }); - let some_state = state({}); - let some_deps = derived(() => { - return [$.get(some_state)]; - }); - test('two effects with an unowned derived that has some dependencies', () => { const log: Array> = []; - render_effect(() => { - log.push($.get(some_deps)); - }); + return () => { + let some_state = state({}); + let some_deps = derived(() => { + return [$.get(some_state)]; + }); + let destroy2: any; - render_effect(() => { - log.push($.get(some_deps)); - }); + const destroy = effect_root(() => { + render_effect(() => { + $.untrack(() => { + log.push($.get(some_deps)); + }); + }); - return () => { + destroy2 = effect_root(() => { + render_effect(() => { + log.push($.get(some_deps)); + }); + + render_effect(() => { + log.push($.get(some_deps)); + }); + }); + }); + + set(some_state, {}); + flushSync(); + + assert.deepEqual(log, [[{}], [{}], [{}], [{}], [{}]]); + + destroy2(); + + set(some_state, {}); flushSync(); - assert.deepEqual(log, [[{}], [{}]]); + + assert.deepEqual(log, [[{}], [{}], [{}], [{}], [{}]]); + + log.length = 0; + + const destroy3 = effect_root(() => { + render_effect(() => { + $.untrack(() => { + log.push($.get(some_deps)); + }); + log.push($.get(some_deps)); + }); + }); + + set(some_state, {}); + flushSync(); + + assert.deepEqual(log, [[{}], [{}], [{}], [{}]]); + + destroy3(); + + assert(some_state.reactions === null); + + destroy(); + + assert(some_state.reactions === null); }; }); From 280d8c74cc9d5cad9b200ce0f9d987633349cfae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:03:30 +0000 Subject: [PATCH 61/67] Version Packages (#15263) Co-authored-by: github-actions[bot] --- .changeset/nasty-pigs-lay.md | 5 ----- .changeset/rare-carpets-wave.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/nasty-pigs-lay.md delete mode 100644 .changeset/rare-carpets-wave.md diff --git a/.changeset/nasty-pigs-lay.md b/.changeset/nasty-pigs-lay.md deleted file mode 100644 index 64ef72103a..0000000000 --- a/.changeset/nasty-pigs-lay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: when re-connecting unowned deriveds, remove their unowned flag diff --git a/.changeset/rare-carpets-wave.md b/.changeset/rare-carpets-wave.md deleted file mode 100644 index cfd90cad6a..0000000000 --- a/.changeset/rare-carpets-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: allow mutation of private derived state diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index cf2f90b204..e112bf6209 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.19.10 + +### Patch Changes + +- fix: when re-connecting unowned deriveds, remove their unowned flag ([#15255](https://github.com/sveltejs/svelte/pull/15255)) + +- fix: allow mutation of private derived state ([#15228](https://github.com/sveltejs/svelte/pull/15228)) + ## 5.19.9 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index ec5e8cd6fc..a4594b2a5c 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.19.9", + "version": "5.19.10", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index aa5c7db036..ada6f9019d 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.19.9'; +export const VERSION = '5.19.10'; export const PUBLIC_VERSION = '5'; From 73220b86676d59322fb6ff0e6cf13ea72a666e04 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 11 Feb 2025 15:11:40 -0500 Subject: [PATCH 62/67] chore: simplify process_effects (#15270) * chore: simplify process_effects * return effects --- .../svelte/src/internal/client/runtime.js | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8b0f84268c..d5d01c9b6d 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -679,10 +679,7 @@ function flush_queued_root_effects(root_effects) { effect.f ^= CLEAN; } - /** @type {Effect[]} */ - var collected_effects = []; - - process_effects(effect, collected_effects); + var collected_effects = process_effects(effect); flush_queued_effects(collected_effects); } } finally { @@ -783,13 +780,14 @@ export function schedule_effect(signal) { * effects to be flushed. * * @param {Effect} effect - * @param {Effect[]} collected_effects - * @returns {void} + * @returns {Effect[]} */ -function process_effects(effect, collected_effects) { - var current_effect = effect.first; +function process_effects(effect) { + /** @type {Effect[]} */ var effects = []; + var current_effect = effect.first; + main_loop: while (current_effect !== null) { var flags = current_effect.f; var is_branch = (flags & BRANCH_EFFECT) !== 0; @@ -797,34 +795,32 @@ function process_effects(effect, collected_effects) { var sibling = current_effect.next; if (!is_skippable_branch && (flags & INERT) === 0) { - if ((flags & RENDER_EFFECT) !== 0) { - if (is_branch) { - current_effect.f ^= CLEAN; - } else { - // Ensure we set the effect to be the active reaction - // to ensure that unowned deriveds are correctly tracked - // because we're flushing the current effect - var previous_active_reaction = active_reaction; - try { - active_reaction = current_effect; - if (check_dirtiness(current_effect)) { - update_effect(current_effect); - } - } catch (error) { - handle_error(error, current_effect, null, current_effect.ctx); - } finally { - active_reaction = previous_active_reaction; + if ((flags & EFFECT) !== 0) { + effects.push(current_effect); + } else if (is_branch) { + current_effect.f ^= CLEAN; + } else { + // Ensure we set the effect to be the active reaction + // to ensure that unowned deriveds are correctly tracked + // because we're flushing the current effect + var previous_active_reaction = active_reaction; + try { + active_reaction = current_effect; + if (check_dirtiness(current_effect)) { + update_effect(current_effect); } + } catch (error) { + handle_error(error, current_effect, null, current_effect.ctx); + } finally { + active_reaction = previous_active_reaction; } + } - var child = current_effect.first; + var child = current_effect.first; - if (child !== null) { - current_effect = child; - continue; - } - } else if ((flags & EFFECT) !== 0) { - effects.push(current_effect); + if (child !== null) { + current_effect = child; + continue; } } @@ -847,13 +843,7 @@ function process_effects(effect, collected_effects) { current_effect = sibling; } - // We might be dealing with many effects here, far more than can be spread into - // an array push call (callstack overflow). So let's deal with each effect in a loop. - for (var i = 0; i < effects.length; i++) { - child = effects[i]; - collected_effects.push(child); - process_effects(child, collected_effects); - } + return effects; } /** From 85f83ec435ef0baa797f195f5b016395fc70be13 Mon Sep 17 00:00:00 2001 From: adiGuba Date: Tue, 11 Feb 2025 21:12:58 +0100 Subject: [PATCH 63/67] feat: $props.id(), a SSR-safe ID generation (#15185) * first impl of $$uid * fix * $props.id() * fix errors * rename $.create_uid() into $.props_id() * fix message * relax const requirement, validate assignments instead * oops * simplify * non-constants should be lowercased * ditto * start at 1 * add docs * changeset * add test * add docs * doc : add code example * fix type reported by bennymi --------- Co-authored-by: Rich Harris --- .changeset/hip-singers-vanish.md | 5 ++ documentation/docs/02-runes/05-$props.md | 21 +++++++ .../98-reference/.generated/compile-errors.md | 8 ++- .../svelte/messages/compile-errors/script.md | 6 +- packages/svelte/src/ambient.d.ts | 9 +++ packages/svelte/src/compiler/errors.js | 16 ++++- .../src/compiler/phases/2-analyze/index.js | 1 + .../2-analyze/visitors/CallExpression.js | 28 ++++++++- .../phases/2-analyze/visitors/shared/utils.js | 4 ++ .../3-transform/client/transform-client.js | 5 ++ .../client/visitors/VariableDeclaration.js | 5 ++ .../client/visitors/shared/utils.js | 6 ++ .../3-transform/server/transform-server.js | 7 +++ .../server/visitors/VariableDeclaration.js | 9 +++ .../svelte/src/compiler/phases/types.d.ts | 2 + .../src/internal/client/dom/template.js | 20 ++++++ packages/svelte/src/internal/client/index.js | 3 +- packages/svelte/src/internal/server/index.js | 31 ++++++++-- .../svelte/src/internal/server/types.d.ts | 2 + packages/svelte/src/utils.js | 1 + .../samples/props-id/Child.svelte | 5 ++ .../runtime-runes/samples/props-id/_config.js | 61 +++++++++++++++++++ .../samples/props-id/main.svelte | 19 ++++++ packages/svelte/types/index.d.ts | 9 +++ 24 files changed, 272 insertions(+), 11 deletions(-) create mode 100644 .changeset/hip-singers-vanish.md create mode 100644 packages/svelte/tests/runtime-runes/samples/props-id/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/props-id/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/props-id/main.svelte diff --git a/.changeset/hip-singers-vanish.md b/.changeset/hip-singers-vanish.md new file mode 100644 index 0000000000..9dce4d98a8 --- /dev/null +++ b/.changeset/hip-singers-vanish.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: SSR-safe ID generation with `$props.id()` diff --git a/documentation/docs/02-runes/05-$props.md b/documentation/docs/02-runes/05-$props.md index 4b1775bf5a..f300fb239d 100644 --- a/documentation/docs/02-runes/05-$props.md +++ b/documentation/docs/02-runes/05-$props.md @@ -199,3 +199,24 @@ You can, of course, separate the type declaration from the annotation: > [!NOTE] Interfaces for native DOM elements are provided in the `svelte/elements` module (see [Typing wrapper components](typescript#Typing-wrapper-components)) Adding types is recommended, as it ensures that people using your component can easily discover which props they should provide. + + +## `$props.id()` + +This rune, added in version 5.20.0, generates an ID that is unique to the current component instance. When hydrating a server-rendered component, the value will be consistent between server and client. + +This is useful for linking elements via attributes like `for` and `aria-labelledby`. + +```svelte + + +
          + + + + + +
          +``` \ No newline at end of file diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index 2fef3bd45d..a4ecbb31d5 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -573,7 +573,13 @@ Unrecognised compiler option %keypath% ### props_duplicate ``` -Cannot use `$props()` more than once +Cannot use `%rune%()` more than once +``` + +### props_id_invalid_placement + +``` +`$props.id()` can only be used at the top level of components as a variable declaration initializer ``` ### props_illegal_name diff --git a/packages/svelte/messages/compile-errors/script.md b/packages/svelte/messages/compile-errors/script.md index 0aa6fbed90..795c0b007d 100644 --- a/packages/svelte/messages/compile-errors/script.md +++ b/packages/svelte/messages/compile-errors/script.md @@ -120,7 +120,11 @@ This turned out to be buggy and unpredictable, particularly when working with de ## props_duplicate -> Cannot use `$props()` more than once +> Cannot use `%rune%()` more than once + +## props_id_invalid_placement + +> `$props.id()` can only be used at the top level of components as a variable declaration initializer ## props_illegal_name diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index fbcecba8e4..a1484718cc 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -339,6 +339,15 @@ declare namespace $effect { declare function $props(): any; declare namespace $props { + /** + * Generates an ID that is unique to the current component instance. When hydrating a server-rendered component, + * the value will be consistent between server and client. + * + * This is useful for linking elements via attributes like `for` and `aria-labelledby`. + * @since 5.20.0 + */ + export function id(): string; + // prevent intellisense from being unhelpful /** @deprecated */ export const apply: never; diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 53a6ac6849..93eeee539c 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -279,12 +279,22 @@ export function module_illegal_default_export(node) { } /** - * Cannot use `$props()` more than once + * Cannot use `%rune%()` more than once + * @param {null | number | NodeLike} node + * @param {string} rune + * @returns {never} + */ +export function props_duplicate(node, rune) { + e(node, 'props_duplicate', `Cannot use \`${rune}()\` more than once\nhttps://svelte.dev/e/props_duplicate`); +} + +/** + * `$props.id()` can only be used at the top level of components as a variable declaration initializer * @param {null | number | NodeLike} node * @returns {never} */ -export function props_duplicate(node) { - e(node, 'props_duplicate', `Cannot use \`$props()\` more than once\nhttps://svelte.dev/e/props_duplicate`); +export function props_id_invalid_placement(node) { + e(node, 'props_id_invalid_placement', `\`$props.id()\` can only be used at the top level of components as a variable declaration initializer\nhttps://svelte.dev/e/props_id_invalid_placement`); } /** diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index ad9db24e1e..846abcf7df 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -416,6 +416,7 @@ export function analyze_component(root, source, options) { immutable: runes || options.immutable, exports: [], uses_props: false, + props_id: null, uses_rest_props: false, uses_slots: false, uses_component_bindings: false, 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 0a6b3f3ee5..ce520cc980 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -55,7 +55,7 @@ export function CallExpression(node, context) { case '$props': if (context.state.has_props_rune) { - e.props_duplicate(node); + e.props_duplicate(node, rune); } context.state.has_props_rune = true; @@ -74,6 +74,32 @@ export function CallExpression(node, context) { break; + case '$props.id': { + const grand_parent = get_parent(context.path, -2); + + if (context.state.analysis.props_id) { + e.props_duplicate(node, rune); + } + + if ( + parent.type !== 'VariableDeclarator' || + parent.id.type !== 'Identifier' || + context.state.ast_type !== 'instance' || + context.state.scope !== context.state.analysis.instance.scope || + grand_parent.type !== 'VariableDeclaration' + ) { + e.props_id_invalid_placement(node); + } + + if (node.arguments.length > 0) { + e.rune_invalid_arguments(node, rune); + } + + context.state.analysis.props_id = parent.id; + + break; + } + case '$state': case '$state.raw': case '$derived': 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 5fe2a8f24e..2d90c85364 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 @@ -25,6 +25,10 @@ export function validate_assignment(node, argument, state) { e.constant_assignment(node, 'derived state'); } + if (binding?.node === state.analysis.props_id) { + e.constant_assignment(node, '$props.id()'); + } + if (binding?.kind === 'each') { e.each_item_invalid_assignment(node); } 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 f4a6c9a414..2e6307a4b7 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 @@ -562,6 +562,11 @@ export function client_component(analysis, options) { component_block.body.unshift(b.stmt(b.call('$.check_target', b.id('new.target')))); } + if (analysis.props_id) { + // need to be placed on first line of the component for hydration + component_block.body.unshift(b.const(analysis.props_id, b.call('$.props_id'))); + } + if (state.events.size > 0) { body.push( b.stmt(b.call('$.delegate', b.array(Array.from(state.events).map((name) => b.literal(name))))) 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 afb90bbec7..31e712cdcc 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 @@ -42,6 +42,11 @@ export function VariableDeclaration(node, context) { continue; } + if (rune === '$props.id') { + // skip + continue; + } + if (rune === '$props') { /** @type {string[]} */ const seen = ['$$slots', '$$events', '$$legacy']; 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 00634f229e..9214a13c94 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 @@ -129,6 +129,12 @@ export function build_template_chunk( if (value.right.value === null) { value = { ...value, right: b.literal('') }; } + } else if ( + state.analysis.props_id && + value.type === 'Identifier' && + value.name === state.analysis.props_id.name + ) { + // do nothing ($props.id() is never null/undefined) } else { value = b.logical('??', value, b.literal('')); } 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 982b75e12f..df3d831d3c 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 @@ -244,6 +244,13 @@ export function server_component(analysis, options) { .../** @type {Statement[]} */ (template.body) ]); + if (analysis.props_id) { + // need to be placed on first line of the component for hydration + component_block.body.unshift( + b.const(analysis.props_id, b.call('$.props_id', b.id('$$payload'))) + ); + } + let should_inject_context = dev || analysis.needs_context; if (should_inject_context) { 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 31de811ac7..c4c31d7eb3 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 @@ -24,6 +24,11 @@ export function VariableDeclaration(node, context) { continue; } + if (rune === '$props.id') { + // skip + continue; + } + if (rune === '$props') { let has_rest = false; // remove $bindable() from props declaration @@ -156,6 +161,10 @@ export function VariableDeclaration(node, context) { } } + if (declarations.length === 0) { + return b.empty; + } + return { ...node, declarations diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index fe32dbba3e..abe2b115de 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -44,6 +44,8 @@ export interface ComponentAnalysis extends Analysis { exports: Array<{ name: string; alias: string | null }>; /** Whether the component uses `$$props` */ uses_props: boolean; + /** The component ID variable name, if any */ + props_id: Identifier | null; /** Whether the component uses `$$restProps` */ uses_rest_props: boolean; /** Whether the component uses `$$slots` */ diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index bcbae393ec..3e4f45aba8 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -249,3 +249,23 @@ export function append(anchor, dom) { anchor.before(/** @type {Node} */ (dom)); } + +let uid = 1; + +/** + * Create (or hydrate) an unique UID for the component instance. + */ +export function props_id() { + if ( + hydrating && + hydrate_node && + hydrate_node.nodeType === 8 && + hydrate_node.textContent?.startsWith('#s') + ) { + const id = hydrate_node.textContent.substring(1); + hydrate_next(); + return id; + } + + return 'c' + uid++; +} diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 3f3b29b029..8eaa5d66e1 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -96,7 +96,8 @@ export { mathml_template, template, template_with_script, - text + text, + props_id } from './dom/template.js'; export { derived, derived_safe_equal } from './reactivity/deriveds.js'; export { diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 89b3c33df8..c4e5d318dc 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -28,14 +28,15 @@ const INVALID_ATTR_NAME_CHAR_REGEX = * @param {Payload} to_copy * @returns {Payload} */ -export function copy_payload({ out, css, head }) { +export function copy_payload({ out, css, head, uid }) { return { out, css: new Set(css), head: { title: head.title, out: head.out - } + }, + uid }; } @@ -48,6 +49,7 @@ export function copy_payload({ out, css, head }) { export function assign_payload(p1, p2) { p1.out = p2.out; p1.head = p2.head; + p1.uid = p2.uid; } /** @@ -83,17 +85,27 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop) */ export let on_destroy = []; +function props_id_generator() { + let uid = 1; + return () => 's' + uid++; +} + /** * Only available on the server and when compiling with the `server` option. * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. * @template {Record} Props * @param {import('svelte').Component | ComponentType>} component - * @param {{ props?: Omit; context?: Map }} [options] + * @param {{ props?: Omit; context?: Map, uid?: () => string }} [options] * @returns {RenderOutput} */ export function render(component, options = {}) { /** @type {Payload} */ - const payload = { out: '', css: new Set(), head: { title: '', out: '' } }; + const payload = { + out: '', + css: new Set(), + head: { title: '', out: '' }, + uid: options.uid ?? props_id_generator() + }; const prev_on_destroy = on_destroy; on_destroy = []; @@ -526,6 +538,17 @@ export function once(get_value) { }; } +/** + * Create an unique ID + * @param {Payload} payload + * @returns {string} + */ +export function props_id(payload) { + const uid = payload.uid(); + payload.out += ''; + return uid; +} + export { attr, clsx }; export { html } from './blocks/html.js'; diff --git a/packages/svelte/src/internal/server/types.d.ts b/packages/svelte/src/internal/server/types.d.ts index e6c235147b..8a241deecd 100644 --- a/packages/svelte/src/internal/server/types.d.ts +++ b/packages/svelte/src/internal/server/types.d.ts @@ -18,6 +18,8 @@ export interface Payload { title: string; out: string; }; + /** Function that generates a unique ID */ + uid: () => string; } export interface RenderOutput { diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index e8e1bc224c..d4d106d56d 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -433,6 +433,7 @@ const RUNES = /** @type {const} */ ([ '$state.raw', '$state.snapshot', '$props', + '$props.id', '$bindable', '$derived', '$derived.by', diff --git a/packages/svelte/tests/runtime-runes/samples/props-id/Child.svelte b/packages/svelte/tests/runtime-runes/samples/props-id/Child.svelte new file mode 100644 index 0000000000..ad8bbd6f01 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id/Child.svelte @@ -0,0 +1,5 @@ + + +

          {id}

          diff --git a/packages/svelte/tests/runtime-runes/samples/props-id/_config.js b/packages/svelte/tests/runtime-runes/samples/props-id/_config.js new file mode 100644 index 0000000000..9d91b98e0f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id/_config.js @@ -0,0 +1,61 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target, variant }) { + if (variant === 'dom') { + assert.htmlEqual( + target.innerHTML, + ` + +

          c1

          +

          c2

          +

          c3

          +

          c4

          + ` + ); + } else { + assert.htmlEqual( + target.innerHTML, + ` + +

          s1

          +

          s2

          +

          s3

          +

          s4

          + ` + ); + } + + let button = target.querySelector('button'); + flushSync(() => button?.click()); + + if (variant === 'dom') { + assert.htmlEqual( + target.innerHTML, + ` + +

          c1

          +

          c2

          +

          c3

          +

          c4

          +

          c5

          + ` + ); + } else { + // `c6` because this runs after the `dom` tests + // (slightly brittle but good enough for now) + assert.htmlEqual( + target.innerHTML, + ` + +

          s1

          +

          s2

          +

          s3

          +

          s4

          +

          c6

          + ` + ); + } + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-id/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-id/main.svelte new file mode 100644 index 0000000000..646bb2ebde --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id/main.svelte @@ -0,0 +1,19 @@ + + + + +

          {id}

          + + + + + +{#if show} + +{/if} diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index eb3e93e4b5..77d78477ee 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2995,6 +2995,15 @@ declare namespace $effect { declare function $props(): any; declare namespace $props { + /** + * Generates an ID that is unique to the current component instance. When hydrating a server-rendered component, + * the value will be consistent between server and client. + * + * This is useful for linking elements via attributes like `for` and `aria-labelledby`. + * @since 5.20.0 + */ + export function id(): string; + // prevent intellisense from being unhelpful /** @deprecated */ export const apply: never; From afae274587b30bb505508e1302ae149fd9c8593c Mon Sep 17 00:00:00 2001 From: adiGuba Date: Tue, 11 Feb 2025 21:36:38 +0100 Subject: [PATCH 64/67] fix: value/checked not correctly set using spread (#15239) * set value/checked by JS * test * changeset * fix test form-default-value-spread --- .changeset/fuzzy-zoos-repeat.md | 5 +++ .../client/dom/elements/attributes.js | 13 +++++--- .../samples/attribute-spread-input/_config.js | 33 +++++++++++++++++++ .../attribute-spread-input/main.svelte | 22 +++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 .changeset/fuzzy-zoos-repeat.md create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-spread-input/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-spread-input/main.svelte diff --git a/.changeset/fuzzy-zoos-repeat.md b/.changeset/fuzzy-zoos-repeat.md new file mode 100644 index 0000000000..3fb3f0502e --- /dev/null +++ b/.changeset/fuzzy-zoos-repeat.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: value/checked not correctly set using spread diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 4a0f0cea0e..987d1f2086 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -399,15 +399,18 @@ export function set_attributes( if (name === 'value' || name === 'checked') { // removing value/checked also removes defaultValue/defaultChecked — preserve let input = /** @type {HTMLInputElement} */ (element); - + const use_default = prev === undefined; if (name === 'value') { - let prev = input.defaultValue; + let previous = input.defaultValue; input.removeAttribute(name); - input.defaultValue = prev; + input.defaultValue = previous; + // @ts-ignore + input.value = input.__value = use_default ? previous : null; } else { - let prev = input.defaultChecked; + let previous = input.defaultChecked; input.removeAttribute(name); - input.defaultChecked = prev; + input.defaultChecked = previous; + input.checked = use_default ? previous : false; } } else { element.removeAttribute(key); diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/_config.js b/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/_config.js new file mode 100644 index 0000000000..ab94125503 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/_config.js @@ -0,0 +1,33 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ target, assert }) { + // Test for https://github.com/sveltejs/svelte/issues/15237 + const [setValues, clearValue] = target.querySelectorAll('button'); + const [text1, text2, check1, check2] = target.querySelectorAll('input'); + + assert.equal(text1.value, ''); + assert.equal(text2.value, ''); + assert.equal(check1.checked, false); + assert.equal(check2.checked, false); + + flushSync(() => { + setValues.click(); + }); + + assert.equal(text1.value, 'message'); + assert.equal(text2.value, 'message'); + assert.equal(check1.checked, true); + assert.equal(check2.checked, true); + + flushSync(() => { + clearValue.click(); + }); + + assert.equal(text1.value, ''); + assert.equal(text2.value, ''); + assert.equal(check1.checked, false); + assert.equal(check2.checked, false); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/main.svelte b/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/main.svelte new file mode 100644 index 0000000000..4bb4365ee2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/main.svelte @@ -0,0 +1,22 @@ + + + + + + + + + + From a3e49b611094559a2c4f3830dec2cc75680140ab Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 11 Feb 2025 23:07:17 +0100 Subject: [PATCH 65/67] fix: recurse into `$derived` for ownership validation (#15166) - `$derived` can contain `$state` declarations so we cannot ignore them, so this reverts #14533 - instead, we add equality checks to not do this expensive work unnecessarily - this also adds a render effect similar to the class ownership addition when it detects a getter on a POJO during ownership addition fixes #15164 --- .changeset/thick-carrots-arrive.md | 5 ++ .../3-transform/client/visitors/ClassBody.js | 29 ++++++------ .../client/visitors/shared/component.js | 41 +++++------------ .../src/internal/client/dev/ownership.js | 42 +++++++++++++++-- packages/svelte/src/internal/client/index.js | 1 + .../CounterBinding.svelte | 7 +++ .../CounterContext.svelte | 13 ++++++ .../_config.js | 34 ++++++++++++++ .../main.svelte | 46 +++++++++++++++++++ 9 files changed, 168 insertions(+), 50 deletions(-) create mode 100644 .changeset/thick-carrots-arrive.md create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte diff --git a/.changeset/thick-carrots-arrive.md b/.changeset/thick-carrots-arrive.md new file mode 100644 index 0000000000..582cf5e6e1 --- /dev/null +++ b/.changeset/thick-carrots-arrive.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: recurse into `$derived` for ownership validation 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 7b3a9a4d0e..ed800e5226 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 @@ -190,22 +190,21 @@ export function ClassBody(node, context) { 'method', b.id('$.ADD_OWNER'), [b.id('owner')], - Array.from(public_state) - // Only run ownership addition on $state fields. - // Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`, - // but that feels so much of an edge case that it doesn't warrant a perf hit for the common case. - .filter(([_, { kind }]) => kind === 'state') - .map(([name]) => - b.stmt( - b.call( - '$.add_owner', - b.call('$.get', b.member(b.this, b.private_id(name))), - b.id('owner'), - b.literal(false), - is_ignored(node, 'ownership_invalid_binding') && b.true - ) + [ + 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 ) ); 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 15e4f68e9e..2bae4486dc 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 @@ -180,37 +180,18 @@ export function build_component(node, component_name, context, anchor = context. const expression = /** @type {Expression} */ (context.visit(attribute.expression)); if (dev && attribute.name !== 'this') { - let should_add_owner = true; - - if (attribute.expression.type !== 'SequenceExpression') { - const left = object(attribute.expression); - - if (left?.type === 'Identifier') { - const binding = context.state.scope.get(left.name); - - // Only run ownership addition on $state fields. - // Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`, - // but that feels so much of an edge case that it doesn't warrant a perf hit for the common case. - if (binding?.kind === 'derived' || binding?.kind === 'raw_state') { - should_add_owner = false; - } - } - } - - if (should_add_owner) { - 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 - ) + 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 (expression.type === 'SequenceExpression') { diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index 2a2527803a..62119b36db 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -6,7 +6,7 @@ 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 * as w from '../warnings.js'; -import { FILENAME } from '../../../constants.js'; +import { FILENAME, UNINITIALIZED } from '../../../constants.js'; /** @type {Record>} */ const boundaries = {}; @@ -140,6 +140,25 @@ export function add_owner_effect(get_object, Component, skip_warning = false) { }); } +/** + * @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 @@ -196,7 +215,19 @@ function add_owner_to_object(object, owner, seen) { if (proto === Object.prototype) { // recurse until we find a state proxy for (const key in object) { - add_owner_to_object(object[key], owner, seen); + 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 @@ -221,9 +252,10 @@ function has_owner(metadata, component) { return ( metadata.owners.has(component) || // This helps avoid false positives when using HMR, where the component function is replaced - [...metadata.owners].some( - (owner) => /** @type {any} */ (owner)[FILENAME] === /** @type {any} */ (component)?.[FILENAME] - ) || + (FILENAME in component && + [...metadata.owners].some( + (owner) => /** @type {any} */ (owner)[FILENAME] === component[FILENAME] + )) || (metadata.parent !== null && has_owner(metadata.parent, component)) ); } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 8eaa5d66e1..0d64b60496 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -10,6 +10,7 @@ export { mark_module_start, mark_module_end, add_owner_effect, + add_owner_to_class, skip_ownership_validation } from './dev/ownership.js'; export { check_target, legacy_api } from './dev/legacy.js'; 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 new file mode 100644 index 0000000000..d6da559fb1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterBinding.svelte @@ -0,0 +1,7 @@ + + +

          Binding

          + + 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 new file mode 100644 index 0000000000..b935f0a472 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/CounterContext.svelte @@ -0,0 +1,13 @@ + + +

          Context

          + + 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 new file mode 100644 index 0000000000..d6d12d01cd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/_config.js @@ -0,0 +1,34 @@ +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 new file mode 100644 index 0000000000..aaade26e16 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-8/main.svelte @@ -0,0 +1,46 @@ + + +

          Parent

          + + + + From 5fe027286801043050b795169082ed5251e31dd1 Mon Sep 17 00:00:00 2001 From: Kid <44045911+kidonng@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:02:40 +0800 Subject: [PATCH 66/67] docs: remove duplicate `onDestroy` description (#15274) --- documentation/docs/06-runtime/03-lifecycle-hooks.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/documentation/docs/06-runtime/03-lifecycle-hooks.md b/documentation/docs/06-runtime/03-lifecycle-hooks.md index a3dbe04b00..2b97ca796f 100644 --- a/documentation/docs/06-runtime/03-lifecycle-hooks.md +++ b/documentation/docs/06-runtime/03-lifecycle-hooks.md @@ -45,8 +45,6 @@ If a function is returned from `onMount`, it will be called when the component i ## `onDestroy` -> EXPORT_SNIPPET: svelte#onDestroy - Schedules a callback to run immediately before the component is unmounted. Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the only one that runs inside a server-side component. From 18481386f3667a13007b5dc40cb40a95382a696b Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Wed, 12 Feb 2025 14:05:25 +0100 Subject: [PATCH 67/67] fix: use `importNode` to clone templates for Firefox (#15272) * fix: use `importNode` to clone templates for Firefox * fix: move `is_firefox` check to line 28 * fix: revert using `is_firefox` too soon --- .changeset/slow-meals-wait.md | 5 ++++ .../phases/2-analyze/visitors/Attribute.js | 5 ---- .../client/visitors/RegularElement.js | 5 ---- .../client/dom/elements/attributes.js | 25 ------------------- .../src/internal/client/dom/operations.js | 4 +++ .../src/internal/client/dom/template.js | 4 +-- packages/svelte/src/internal/client/index.js | 1 - .../svelte/src/internal/client/runtime.js | 3 ++- .../_expected/client/index.svelte.js | 6 +---- 9 files changed, 14 insertions(+), 44 deletions(-) create mode 100644 .changeset/slow-meals-wait.md diff --git a/.changeset/slow-meals-wait.md b/.changeset/slow-meals-wait.md new file mode 100644 index 0000000000..e1408e3849 --- /dev/null +++ b/.changeset/slow-meals-wait.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: use `importNode` to clone templates for Firefox 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 41144fc74c..42e4498969 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -23,11 +23,6 @@ export function Attribute(node, context) { if (node.name === 'value' && parent.name === 'option') { mark_subtree_dynamic(context.path); } - - // special case - if (node.name === 'loading' && parent.name === 'img') { - mark_subtree_dynamic(context.path); - } } if (is_event_attribute(node)) { 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 c7e218d521..98036aa9b6 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 @@ -300,11 +300,6 @@ export function RegularElement(node, context) { build_class_directives(class_directives, node_id, context, is_attributes_reactive); build_style_directives(style_directives, node_id, context, is_attributes_reactive); - // Apply the src and loading attributes for elements after the element is appended to the document - if (node.name === 'img' && (has_spread || lookup.has('loading'))) { - context.state.after_update.push(b.stmt(b.call('$.handle_lazy_img', node_id))); - } - if ( is_load_error_element(node.name) && (has_spread || has_use || lookup.has('onload') || lookup.has('onerror')) diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 987d1f2086..2dba2d797a 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -523,28 +523,3 @@ function srcset_url_equal(element, srcset) { ) ); } - -/** - * @param {HTMLImageElement} element - * @returns {void} - */ -export function handle_lazy_img(element) { - // If we're using an image that has a lazy loading attribute, we need to apply - // the loading and src after the img element has been appended to the document. - // Otherwise the lazy behaviour will not work due to our cloneNode heuristic for - // templates. - if (!hydrating && element.loading === 'lazy') { - var src = element.src; - // @ts-expect-error - element[LOADING_ATTR_SYMBOL] = null; - element.loading = 'eager'; - element.removeAttribute('src'); - requestAnimationFrame(() => { - // @ts-expect-error - if (element[LOADING_ATTR_SYMBOL] !== 'eager') { - element.loading = 'lazy'; - } - element.src = src; - }); - } -} diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 627bf917ee..83565d17ae 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -11,6 +11,9 @@ export var $window; /** @type {Document} */ export var $document; +/** @type {boolean} */ +export var is_firefox; + /** @type {() => Node | null} */ var first_child_getter; /** @type {() => Node | null} */ @@ -27,6 +30,7 @@ export function init_operations() { $window = window; $document = document; + is_firefox = /Firefox/.test(navigator.userAgent); var element_prototype = Element.prototype; var node_prototype = Node.prototype; diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 3e4f45aba8..6ff3b0fa19 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -1,6 +1,6 @@ /** @import { Effect, TemplateNode } from '#client' */ import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js'; -import { create_text, get_first_child } from './operations.js'; +import { create_text, get_first_child, is_firefox } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { active_effect } from '../runtime.js'; import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js'; @@ -48,7 +48,7 @@ export function template(content, flags) { } var clone = /** @type {TemplateNode} */ ( - use_import_node ? document.importNode(node, true) : node.cloneNode(true) + use_import_node || is_firefox ? document.importNode(node, true) : node.cloneNode(true) ); if (is_fragment) { diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 0d64b60496..d78f6d452e 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -35,7 +35,6 @@ export { set_attributes, set_custom_element_data, set_xlink_attribute, - handle_lazy_img, set_value, set_checked, set_selected, diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index d5d01c9b6d..75d45d9db9 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -39,6 +39,7 @@ import { set_component_context, set_dev_current_component_function } from './context.js'; +import { is_firefox } from './dom/operations.js'; const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; @@ -333,7 +334,7 @@ export function handle_error(error, effect, previous_effect, component_context) current_context = current_context.p; } - const indent = /Firefox/.test(navigator.userAgent) ? ' ' : '\t'; + const indent = is_firefox ? ' ' : '\t'; define_property(error, 'message', { value: error.message + `\n${component_stack.map((name) => `\n${indent}in ${name}`).join('')}\n` }); diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js index 9b203b97e8..46d376aca2 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js @@ -42,12 +42,8 @@ export default function Skip_static_subtree($$anchor, $$props) { $.reset(select); var img = $.sibling(select, 2); - var div_2 = $.sibling(img, 2); - var img_1 = $.child(div_2); - $.reset(div_2); + $.next(2); $.template_effect(() => $.set_text(text, $$props.title)); - $.handle_lazy_img(img); - $.handle_lazy_img(img_1); $.append($$anchor, fragment); } \ No newline at end of file