diff --git a/documentation/docs/07-misc/01-best-practices.md b/documentation/docs/07-misc/01-best-practices.md
index ac275e4d9a..66f7da2613 100644
--- a/documentation/docs/07-misc/01-best-practices.md
+++ b/documentation/docs/07-misc/01-best-practices.md
@@ -1,5 +1,6 @@
---
title: Best practices
+skill: true
name: svelte-core-bestpractices
description: Guidance on writing fast, robust, modern Svelte code. Load this skill whenever in a Svelte project and asked to write/edit or analyze a Svelte component or module. Covers reactivity, event handling, styling, integration with libraries and more.
---
diff --git a/documentation/docs/07-misc/07-v5-migration-guide.md b/documentation/docs/07-misc/07-v5-migration-guide.md
index 580dbec6d4..9b1f2dec63 100644
--- a/documentation/docs/07-misc/07-v5-migration-guide.md
+++ b/documentation/docs/07-misc/07-v5-migration-guide.md
@@ -324,7 +324,7 @@ When spreading props, local event handlers must go _after_ the spread, or they r
>
> It was always possible to use component callback props, but because you had to listen to DOM events using `on:`, it made sense to use `createEventDispatcher` for component events due to syntactical consistency. Now that we have event attributes (`onclick`), it's the other way around: Callback props are now the more sensible thing to do.
>
-> The removal of event modifiers is arguably one of the changes that seems like a step back for those who've liked the shorthand syntax of event modifiers. Given that they are not used that frequently, we traded a smaller surface area for more explicitness. Modifiers also were inconsistent, because most of them were only useable on DOM elements.
+> The removal of event modifiers is arguably one of the changes that seems like a step back for those who've liked the shorthand syntax of event modifiers. Given that they are not used that frequently, we traded a smaller surface area for more explicitness. Modifiers also were inconsistent, because most of them were only usable on DOM elements.
>
> Multiple listeners for the same event are also no longer possible, but it was something of an anti-pattern anyway, since it impedes readability: if there are many attributes, it becomes harder to spot that there are two handlers unless they are right next to each other. It also implies that the two handlers are independent, when in fact something like `event.stopImmediatePropagation()` inside `one` would prevent `two` from being called.
>
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 5f6bd25143..f09032294a 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,43 @@
# svelte
+## 5.53.9
+
+### Patch Changes
+
+- fix: better `bind:this` cleanup timing ([#17885](https://github.com/sveltejs/svelte/pull/17885))
+
+## 5.53.8
+
+### Patch Changes
+
+- fix: `{@html}` no longer duplicates content inside `contenteditable` elements ([#17853](https://github.com/sveltejs/svelte/pull/17853))
+
+- fix: don't access inert block effects ([#17882](https://github.com/sveltejs/svelte/pull/17882))
+
+- fix: handle asnyc updates within pending boundary ([#17873](https://github.com/sveltejs/svelte/pull/17873))
+
+- perf: avoid re-traversing the effect tree after `$:` assignments ([#17848](https://github.com/sveltejs/svelte/pull/17848))
+
+- chore: simplify scheduling logic ([#17805](https://github.com/sveltejs/svelte/pull/17805))
+
+## 5.53.7
+
+### Patch Changes
+
+- fix: correctly add \_\_svelte_meta after else-if chains ([#17830](https://github.com/sveltejs/svelte/pull/17830))
+
+- perf: cache element interactivity and source line splitting in compiler ([#17839](https://github.com/sveltejs/svelte/pull/17839))
+
+- chore: avoid rescheduling effects during branch commit ([#17837](https://github.com/sveltejs/svelte/pull/17837))
+
+- perf: optimize CSS selector pruning ([#17846](https://github.com/sveltejs/svelte/pull/17846))
+
+- fix: preserve original boundary errors when keyed each rows are removed during async updates ([#17843](https://github.com/sveltejs/svelte/pull/17843))
+
+- perf: avoid O(n²) name scanning in scope `generate` and `unique` ([#17844](https://github.com/sveltejs/svelte/pull/17844))
+
+- fix: preserve each items that are needed by pending batches ([#17819](https://github.com/sveltejs/svelte/pull/17819))
+
## 5.53.6
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 5f7035cdd3..17fc4335d5 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.53.6",
+ "version": "5.53.9",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
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 24da276ed5..39f485a9f7 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
@@ -236,16 +236,36 @@ function truncate(node) {
* @param {Compiler.AST.CSS.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
* @param {Direction} direction
+ * @param {number} [from]
+ * @param {number} [to]
* @returns {boolean}
*/
-function apply_selector(relative_selectors, rule, element, direction) {
- const rest_selectors = relative_selectors.slice();
- const relative_selector = direction === FORWARD ? rest_selectors.shift() : rest_selectors.pop();
+function apply_selector(
+ relative_selectors,
+ rule,
+ element,
+ direction,
+ from = 0,
+ to = relative_selectors.length
+) {
+ if (from >= to) return false;
+
+ const selector_index = direction === FORWARD ? from : to - 1;
+ const relative_selector = relative_selectors[selector_index];
+ const rest_from = direction === FORWARD ? from + 1 : from;
+ const rest_to = direction === FORWARD ? to : to - 1;
const matched =
- !!relative_selector &&
relative_selector_might_apply_to_node(relative_selector, rule, element, direction) &&
- apply_combinator(relative_selector, rest_selectors, rule, element, direction);
+ apply_combinator(
+ relative_selector,
+ relative_selectors,
+ rest_from,
+ rest_to,
+ rule,
+ element,
+ direction
+ );
if (matched) {
if (!is_outer_global(relative_selector)) {
@@ -260,15 +280,21 @@ function apply_selector(relative_selectors, rule, element, direction) {
/**
* @param {Compiler.AST.CSS.RelativeSelector} relative_selector
- * @param {Compiler.AST.CSS.RelativeSelector[]} rest_selectors
+ * @param {Compiler.AST.CSS.RelativeSelector[]} relative_selectors
+ * @param {number} from
+ * @param {number} to
* @param {Compiler.AST.CSS.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
* @param {Direction} direction
* @returns {boolean}
*/
-function apply_combinator(relative_selector, rest_selectors, rule, node, direction) {
+function apply_combinator(relative_selector, relative_selectors, from, to, rule, node, direction) {
const combinator =
- direction == FORWARD ? rest_selectors[0]?.combinator : relative_selector.combinator;
+ direction == FORWARD
+ ? from < to
+ ? relative_selectors[from].combinator
+ : undefined
+ : relative_selector.combinator;
if (!combinator) return true;
switch (combinator.name) {
@@ -282,7 +308,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
let parent_matched = false;
for (const parent of parents) {
- if (apply_selector(rest_selectors, rule, parent, direction)) {
+ if (apply_selector(relative_selectors, rule, parent, direction, from, to)) {
parent_matched = true;
}
}
@@ -291,7 +317,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
parent_matched ||
(direction === BACKWARD &&
(!is_adjacent || parents.length === 0) &&
- rest_selectors.every((selector) => is_global(selector, rule)))
+ every_is_global(relative_selectors, from, to, rule))
);
}
@@ -308,10 +334,12 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
possible_sibling.type === 'Component'
) {
// `{@render foo()}
foo
` with `:global(.x) + p` is a match
- if (rest_selectors.length === 1 && rest_selectors[0].metadata.is_global) {
+ if (to - from === 1 && relative_selectors[from].metadata.is_global) {
sibling_matched = true;
}
- } else if (apply_selector(rest_selectors, rule, possible_sibling, direction)) {
+ } else if (
+ apply_selector(relative_selectors, rule, possible_sibling, direction, from, to)
+ ) {
sibling_matched = true;
}
}
@@ -320,7 +348,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
sibling_matched ||
(direction === BACKWARD &&
get_element_parent(node) === null &&
- rest_selectors.every((selector) => is_global(selector, rule)))
+ every_is_global(relative_selectors, from, to, rule))
);
}
@@ -330,6 +358,20 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
}
}
+/**
+ * @param {Compiler.AST.CSS.RelativeSelector[]} relative_selectors
+ * @param {number} from
+ * @param {number} to
+ * @param {Compiler.AST.CSS.Rule} rule
+ * @returns {boolean}
+ */
+function every_is_global(relative_selectors, from, to, rule) {
+ for (let i = from; i < to; i++) {
+ if (!is_global(relative_selectors[i], rule)) return false;
+ }
+ return true;
+}
+
/**
* Returns `true` if the relative selector is global, meaning
* it's a `:global(...)` or unscopeable selector, or
@@ -392,42 +434,37 @@ const regex_backslash_and_following_character = /\\(.)/g;
* @returns {boolean}
*/
function relative_selector_might_apply_to_node(relative_selector, rule, element, direction) {
- // Sort :has(...) selectors in one bucket and everything else into another
- const has_selectors = [];
- const other_selectors = [];
+ /** @type {boolean | undefined} */
+ let include_self;
for (const selector of relative_selector.selectors) {
+ // Handle :has(...) selectors inline to avoid allocating temporary arrays
if (selector.type === 'PseudoClassSelector' && selector.name === 'has' && selector.args) {
- has_selectors.push(selector);
- } else {
- other_selectors.push(selector);
- }
- }
-
- // If we're called recursively from a :has(...) selector, we're on the way of checking if the other selectors match.
- // In that case ignore this check (because we just came from this) to avoid an infinite loop.
- if (has_selectors.length > 0) {
- // If this is a :has inside a global selector, we gotta include the element itself, too,
- // because the global selector might be for an element that's outside the component,
- // e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} }
- 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' || (s.name === 'global' && s.args))
- )
- )
- );
+ // Lazy-compute include_self on first :has encounter
+ if (include_self === undefined) {
+ // If this is a :has inside a global selector, we gotta include the element itself, too,
+ // because the global selector might be for an element that's outside the component,
+ // e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} }
+ const rules = get_parent_rules(rule);
+ 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' || (s.name === 'global' && s.args))
+ )
+ )
+ );
+ }
- // :has(...) is special in that it means "look downwards in the CSS tree". Since our matching algorithm goes
- // upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the
- // selector in a way that is similar to ancestor matching. In a sense, we're treating `.x:has(.y)` as `.x .y`.
- for (const has_selector of has_selectors) {
- const complex_selectors = /** @type {Compiler.AST.CSS.SelectorList} */ (has_selector.args)
+ // :has(...) is special in that it means "look downwards in the CSS tree". Since our matching algorithm goes
+ // upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the
+ // selector in a way that is similar to ancestor matching. In a sense, we're treating `.x:has(.y)` as `.x .y`.
+ const complex_selectors = /** @type {Compiler.AST.CSS.SelectorList} */ (selector.args)
.children;
let matched = false;
@@ -465,13 +502,15 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
if (!matched) {
return false;
}
+
+ continue;
}
- }
- for (const selector of other_selectors) {
if (selector.type === 'Percentage' || selector.type === 'Nth') continue;
- const name = selector.name.replace(regex_backslash_and_following_character, '$1');
+ const name = selector.name.includes('\\')
+ ? selector.name.replace(regex_backslash_and_following_character, '$1')
+ : selector.name;
switch (selector.type) {
case 'PseudoClassSelector': {
@@ -672,11 +711,11 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
* @param {boolean} case_insensitive
*/
function attribute_matches(node, name, expected_value, operator, case_insensitive) {
+ const name_lower = name.toLowerCase();
+
for (const attribute of node.attributes) {
if (attribute.type === 'SpreadAttribute') return true;
if (attribute.type === 'BindDirective' && attribute.name === name) return true;
-
- const name_lower = name.toLowerCase();
// match attributes against the corresponding directive but bail out on exact matching
if (attribute.type === 'StyleDirective' && name_lower === 'style') return true;
if (attribute.type === 'ClassDirective' && name_lower === 'class') {
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js
index 45de8b10a1..be3af1e59f 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js
@@ -100,6 +100,11 @@ export function check_element(node, context) {
}
}
+ const interactivity = element_interactivity(node.name, attribute_map);
+ const is_interactive = interactivity === ElementInteractivity.Interactive;
+ const is_non_interactive = interactivity === ElementInteractivity.NonInteractive;
+ const is_static = interactivity === ElementInteractivity.Static;
+
for (const attribute of node.attributes) {
if (attribute.type !== 'Attribute') continue;
@@ -133,7 +138,7 @@ export function check_element(node, context) {
if (
name === 'aria-activedescendant' &&
!is_dynamic_element &&
- !is_interactive_element(node.name, attribute_map) &&
+ !is_interactive &&
!attribute_map.has('tabindex') &&
!has_spread
) {
@@ -215,7 +220,7 @@ export function check_element(node, context) {
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(current_role) &&
is_interactive_roles(current_role) &&
- is_static_element(node.name, attribute_map) &&
+ is_static &&
!attribute_map.get('tabindex')
) {
const has_interactive_handlers = [...handlers].some((handler) =>
@@ -229,7 +234,7 @@ export function check_element(node, context) {
// no-interactive-element-to-noninteractive-role
if (
!has_spread &&
- is_interactive_element(node.name, attribute_map) &&
+ is_interactive &&
(is_non_interactive_roles(current_role) || is_presentation_role(current_role))
) {
w.a11y_no_interactive_element_to_noninteractive_role(node, node.name, current_role);
@@ -238,7 +243,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_non_interactive &&
is_interactive_roles(current_role) &&
!a11y_non_interactive_element_to_interactive_role_exceptions[node.name]?.includes(
current_role
@@ -291,7 +296,7 @@ export function check_element(node, context) {
!is_dynamic_element &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
(!role || is_non_presentation_role) &&
- !is_interactive_element(node.name, attribute_map) &&
+ !is_interactive &&
!has_spread
) {
const has_key_event =
@@ -307,11 +312,7 @@ export function check_element(node, context) {
);
// no-noninteractive-tabindex
- if (
- !is_dynamic_element &&
- !is_interactive_element(node.name, attribute_map) &&
- !is_interactive_roles(role_static_value)
- ) {
+ if (!is_dynamic_element && !is_interactive && !is_interactive_roles(role_static_value)) {
const tab_index = attribute_map.get('tabindex');
const tab_index_value = get_static_text_value(tab_index);
if (tab_index && (tab_index_value === null || Number(tab_index_value) >= 0)) {
@@ -341,9 +342,8 @@ export function check_element(node, context) {
!has_contenteditable_attr &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) &&
- ((!is_interactive_element(node.name, attribute_map) &&
- is_non_interactive_roles(role_static_value)) ||
- (is_non_interactive_element(node.name, attribute_map) && !role))
+ ((!is_interactive && is_non_interactive_roles(role_static_value)) ||
+ (is_non_interactive && !role))
) {
const has_interactive_handlers = [...handlers].some((handler) =>
a11y_recommended_interactive_handlers.includes(handler)
@@ -359,9 +359,9 @@ export function check_element(node, context) {
(!role || role_static_value !== null) &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) &&
- !is_interactive_element(node.name, attribute_map) &&
+ !is_interactive &&
!is_interactive_roles(role_static_value) &&
- !is_non_interactive_element(node.name, attribute_map) &&
+ !is_non_interactive &&
!is_non_interactive_roles(role_static_value) &&
!is_abstract_role(role_static_value)
) {
@@ -643,33 +643,6 @@ function element_interactivity(tag_name, attribute_map) {
return ElementInteractivity.Static;
}
-/**
- * @param {string} tag_name
- * @param {Map} attribute_map
- * @returns {boolean}
- */
-function is_interactive_element(tag_name, attribute_map) {
- return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Interactive;
-}
-
-/**
- * @param {string} tag_name
- * @param {Map} attribute_map
- * @returns {boolean}
- */
-function is_non_interactive_element(tag_name, attribute_map) {
- return element_interactivity(tag_name, attribute_map) === ElementInteractivity.NonInteractive;
-}
-
-/**
- * @param {string} tag_name
- * @param {Map} attribute_map
- * @returns {boolean}
- */
-function is_static_element(tag_name, attribute_map) {
- return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Static;
-}
-
/**
* @param {ARIARoleDefinitionKey} role
* @param {string} tag_name
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js
index 2706cf7f0a..6c8b7c0354 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js
@@ -9,7 +9,11 @@ import { build_expression } from './shared/utils.js';
* @param {ComponentContext} context
*/
export function HtmlTag(node, context) {
- context.state.template.push_comment();
+ const is_controlled = node.metadata.is_controlled;
+
+ if (!is_controlled) {
+ context.state.template.push_comment();
+ }
const has_await = node.metadata.expression.has_await;
const has_blockers = node.metadata.expression.has_blockers();
@@ -17,14 +21,17 @@ export function HtmlTag(node, context) {
const expression = build_expression(context, node.expression, node.metadata.expression);
const html = has_await ? b.call('$.get', b.id('$$html')) : expression;
- const is_svg = context.state.metadata.namespace === 'svg';
- const is_mathml = context.state.metadata.namespace === 'mathml';
+ // When is_controlled, the parent node already provides the correct namespace,
+ // so is_svg/is_mathml are only needed for the non-controlled path's wrapper element
+ const is_svg = !is_controlled && context.state.metadata.namespace === 'svg';
+ const is_mathml = !is_controlled && context.state.metadata.namespace === 'mathml';
const statement = b.stmt(
b.call(
'$.html',
context.state.node,
b.thunk(html),
+ is_controlled && b.true,
is_svg && b.true,
is_mathml && b.true,
is_ignored(node, 'hydration_html_changed') && b.true
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
index 0d31c42d11..b0cd51ce3f 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
@@ -51,7 +51,7 @@ export function IfBlock(node, context) {
}
}
- const render_call = b.stmt(b.call('$$render', consequent_id, index > 0 && b.literal(index)));
+ const render_call = b.stmt(b.call('$$render', consequent_id, index !== 0 && b.literal(index)));
const new_if = b.if(test, render_call);
if (last_if) {
@@ -71,7 +71,7 @@ export function IfBlock(node, context) {
const alternate_id = b.id(context.state.scope.generate('alternate'));
statements.push(b.var(alternate_id, b.arrow([b.id('$$anchor')], alternate)));
- last_if.alternate = b.stmt(b.call('$$render', alternate_id, b.literal(false)));
+ last_if.alternate = b.stmt(b.call('$$render', alternate_id, b.literal(-1)));
}
// Build $.if() arguments
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 59b93f24ef..bd3e708662 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
@@ -109,6 +109,8 @@ export function process_children(nodes, initial, is_element, context) {
!node.metadata.expression.is_async()
) {
node.metadata.is_controlled = true;
+ } else if (node.type === 'HtmlTag' && nodes.length === 1 && is_element) {
+ node.metadata.is_controlled = true;
} else {
const id = flush_node(
false,
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js
index e3f5f88705..2d1a22605c 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js
@@ -1,8 +1,8 @@
-/** @import { BlockStatement, Expression, IfStatement, Statement } from 'estree' */
+/** @import { BlockStatement, Expression, IfStatement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
-import { block_close, block_open, block_open_else, create_child_block } from './shared/utils.js';
+import { block_close, create_child_block } from './shared/utils.js';
/**
* @param {AST.IfBlock} node
@@ -10,7 +10,7 @@ import { block_close, block_open, block_open_else, create_child_block } from './
*/
export function IfBlock(node, context) {
const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent));
- consequent.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), block_open)));
+ consequent.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), b.literal(``))));
/** @type {IfStatement} */
let if_statement = b.if(/** @type {Expression} */ (context.visit(node.test)), consequent);
@@ -34,7 +34,7 @@ export function IfBlock(node, context) {
// Handle final else (or remaining async chain)
const final_alternate = alt ? /** @type {BlockStatement} */ (context.visit(alt)) : b.block([]);
- final_alternate.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), block_open_else)));
+ final_alternate.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), b.literal(``))));
current_if.alternate = final_alternate;
context.state.template.push(
diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js
index 9d563375d7..e3560753b4 100644
--- a/packages/svelte/src/compiler/phases/scope.js
+++ b/packages/svelte/src/compiler/phases/scope.js
@@ -713,8 +713,18 @@ export class Scope {
}
preferred_name = preferred_name.replace(/[^a-zA-Z0-9_$]/g, '_').replace(/^[0-9]/, '_');
- let name = preferred_name;
- let n = 1;
+
+ // Use cached counter to skip names already known to be taken (avoids O(n²) scanning)
+ let n = this.root.next_counter(preferred_name);
+ let name;
+
+ if (n === 0) {
+ name = preferred_name;
+ n = 1;
+ } else {
+ name = `${preferred_name}_${n}`;
+ n++;
+ }
while (
this.references.has(name) ||
@@ -725,6 +735,7 @@ export class Scope {
name = `${preferred_name}_${n++}`;
}
+ this.root.set_counter(preferred_name, n);
this.references.set(name, []);
this.root.conflicts.add(name);
return name;
@@ -852,18 +863,49 @@ export class ScopeRoot {
/** @type {Set} */
conflicts = new Set();
+ /**
+ * Tracks the next suffix counter per name to avoid O(n) rescanning in generate/unique.
+ * @type {Map}
+ */
+ #name_counters = new Map();
+
+ /**
+ * @param {string} name
+ * @returns {number}
+ */
+ next_counter(name) {
+ return this.#name_counters.get(name) ?? 0;
+ }
+
+ /**
+ * @param {string} name
+ * @param {number} value
+ */
+ set_counter(name, value) {
+ this.#name_counters.set(name, value);
+ }
+
/**
* @param {string} preferred_name
*/
unique(preferred_name) {
preferred_name = preferred_name.replace(/[^a-zA-Z0-9_$]/g, '_');
- let final_name = preferred_name;
- let n = 1;
+ let n = this.#name_counters.get(preferred_name) ?? 0;
+ let final_name;
+
+ if (n === 0) {
+ final_name = preferred_name;
+ n = 1;
+ } else {
+ final_name = `${preferred_name}_${n}`;
+ n++;
+ }
while (this.conflicts.has(final_name)) {
final_name = `${preferred_name}_${n++}`;
}
+ this.#name_counters.set(preferred_name, n);
this.conflicts.add(final_name);
const id = b.id(final_name);
return id;
diff --git a/packages/svelte/src/compiler/state.js b/packages/svelte/src/compiler/state.js
index c380143f4f..5ae001ec50 100644
--- a/packages/svelte/src/compiler/state.js
+++ b/packages/svelte/src/compiler/state.js
@@ -32,6 +32,12 @@ export let component_name = '';
*/
export let source;
+/**
+ * The source code split into lines (set by `set_source`)
+ * @type {string[]}
+ */
+export let source_lines = [];
+
/**
* True if compiling with `dev: true`
* @type {boolean}
@@ -46,6 +52,7 @@ export let locator;
/** @param {string} value */
export function set_source(value) {
source = value;
+ source_lines = source.split('\n');
const l = getLocator(source, { offsetLine: 1 });
@@ -134,6 +141,7 @@ export function reset(state) {
runes = false;
component_name = UNKNOWN_FILENAME;
source = '';
+ source_lines = [];
filename = (state.filename ?? UNKNOWN_FILENAME).replace(/\\/g, '/');
warning_filter = state.warning ?? (() => true);
warnings = [];
diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts
index d44a31349a..3c1e3e772c 100644
--- a/packages/svelte/src/compiler/types/template.d.ts
+++ b/packages/svelte/src/compiler/types/template.d.ts
@@ -133,6 +133,8 @@ export namespace AST {
/** @internal */
metadata: {
expression: ExpressionMetadata;
+ /** If `true`, the `{@html}` block is the only child of its parent element and can use `parent.innerHTML` directly */
+ is_controlled?: boolean;
};
}
diff --git a/packages/svelte/src/compiler/utils/compile_diagnostic.js b/packages/svelte/src/compiler/utils/compile_diagnostic.js
index c5df49e01c..95d028ee35 100644
--- a/packages/svelte/src/compiler/utils/compile_diagnostic.js
+++ b/packages/svelte/src/compiler/utils/compile_diagnostic.js
@@ -11,12 +11,11 @@ function tabs_to_spaces(str) {
}
/**
- * @param {string} source
* @param {number} line
* @param {number} column
*/
-function get_code_frame(source, line, column) {
- const lines = source.split('\n');
+function get_code_frame(line, column) {
+ const lines = state.source_lines;
const frame_start = Math.max(0, line - 2);
const frame_end = Math.min(line + 3, lines.length);
const digits = String(frame_end + 1).length;
@@ -70,7 +69,7 @@ export class CompileDiagnostic {
this.start = state.locator(position[0]);
this.end = state.locator(position[1]);
if (this.start && this.end) {
- this.frame = get_code_frame(state.source, this.start.line - 1, this.end.column);
+ this.frame = get_code_frame(this.start.line - 1, this.end.column);
}
}
}
diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js
index 3525539f1d..df96f4899b 100644
--- a/packages/svelte/src/internal/client/constants.js
+++ b/packages/svelte/src/internal/client/constants.js
@@ -29,6 +29,8 @@ export const INERT = 1 << 13;
export const DESTROYED = 1 << 14;
/** Set once a reaction has run for the first time */
export const REACTION_RAN = 1 << 15;
+/** Effect is in the process of getting destroyed. Can be observed in child teardown functions */
+export const DESTROYING = 1 << 25;
// Flags exclusive to effects
/**
diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js
index 36c735272c..0baef5c63e 100644
--- a/packages/svelte/src/internal/client/context.js
+++ b/packages/svelte/src/internal/client/context.js
@@ -5,7 +5,7 @@ import { active_effect, active_reaction } from './runtime.js';
import { create_user_effect } from './reactivity/effects.js';
import { async_mode_flag, legacy_mode_flag } from '../flags/index.js';
import { FILENAME } from '../../constants.js';
-import { BRANCH_EFFECT, REACTION_RAN } from './constants.js';
+import { BRANCH_EFFECT } from './constants.js';
/** @type {ComponentContext | null} */
export let component_context = null;
@@ -182,6 +182,7 @@ export function push(props, runes = false, fn) {
e: null,
s: props,
x: null,
+ r: /** @type {Effect} */ (active_effect),
l: legacy_mode_flag && !runes ? { s: null, u: null, $: [] } : null
};
diff --git a/packages/svelte/src/internal/client/dev/debug.js b/packages/svelte/src/internal/client/dev/debug.js
index 22c4de1179..83cc510ae2 100644
--- a/packages/svelte/src/internal/client/dev/debug.js
+++ b/packages/svelte/src/internal/client/dev/debug.js
@@ -68,19 +68,35 @@ function effect_label(effect, append_effect = false) {
return label;
}
+
/**
- *
* @param {Effect} effect
+ * @param {Effect[]} highlighted
*/
-export function log_effect_tree(effect, depth = 0) {
+export function log_effect_tree(effect, highlighted = [], depth = 0, is_reachable = true) {
const flags = effect.f;
- const label = effect_label(effect);
+ let label = effect_label(effect);
let status =
(flags & CLEAN) !== 0 ? 'clean' : (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty';
+ let styles = [`font-weight: ${status === 'clean' ? 'normal' : 'bold'}`];
+
+ if (status !== 'clean' && !is_reachable) {
+ label = `⚠️ ${label}`;
+ styles.push(`color: red`);
+ }
+
+ if ((flags & INERT) !== 0) {
+ styles.push('font-style: italic');
+ }
+
+ if (highlighted.includes(effect)) {
+ styles.push('background-color: yellow');
+ }
+
// eslint-disable-next-line no-console
- console.group(`%c${label} (${status})`, `font-weight: ${status === 'clean' ? 'normal' : 'bold'}`);
+ console.group(`%c${label} (${status})`, styles.join('; '));
if (depth === 0) {
const callsite = new Error().stack
@@ -120,9 +136,11 @@ export function log_effect_tree(effect, depth = 0) {
}
}
+ var child_is_reachable = is_reachable && ((flags & BRANCH_EFFECT) === 0 || (flags & CLEAN) === 0);
+
let child = effect.first;
while (child !== null) {
- log_effect_tree(child, depth + 1);
+ log_effect_tree(child, highlighted, depth + 1, child_is_reachable);
child = child.next;
}
diff --git a/packages/svelte/src/internal/client/dev/elements.js b/packages/svelte/src/internal/client/dev/elements.js
index 8dd54e0a2a..df765607a0 100644
--- a/packages/svelte/src/internal/client/dev/elements.js
+++ b/packages/svelte/src/internal/client/dev/elements.js
@@ -1,6 +1,6 @@
/** @import { SourceLocation } from '#client' */
import { COMMENT_NODE, DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE } from '#client/constants';
-import { HYDRATION_END, HYDRATION_START, HYDRATION_START_ELSE } from '../../../constants.js';
+import { HYDRATION_END, HYDRATION_START } from '../../../constants.js';
import { hydrating } from '../dom/hydration.js';
import { dev_stack } from '../context.js';
@@ -50,7 +50,7 @@ function assign_locations(node, filename, locations) {
while (node && i < locations.length) {
if (hydrating && node.nodeType === COMMENT_NODE) {
var comment = /** @type {Comment} */ (node);
- if (comment.data === HYDRATION_START || comment.data === HYDRATION_START_ELSE) depth += 1;
+ if (comment.data[0] === HYDRATION_START) depth += 1;
else if (comment.data[0] === HYDRATION_END) depth -= 1;
}
diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js
index 09a3ec5ca4..d6430547b5 100644
--- a/packages/svelte/src/internal/client/dom/blocks/await.js
+++ b/packages/svelte/src/internal/client/dom/blocks/await.js
@@ -45,7 +45,14 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
var branches = new BranchManager(node);
block(() => {
+ var batch = /** @type {Batch} */ (current_batch);
+
+ // we null out `current_batch` because otherwise `save(...)` will incorrectly restore it —
+ // the batch will already have been committed by the time it resolves
+ batch.deactivate();
var input = get_input();
+ batch.activate();
+
var destroyed = false;
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js
index 429a2eb293..b38a3131ca 100644
--- a/packages/svelte/src/internal/client/dom/blocks/boundary.js
+++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js
@@ -35,7 +35,7 @@ import { queue_micro_task } from '../task.js';
import * as e from '../../errors.js';
import * as w from '../../warnings.js';
import { DEV } from 'esm-env';
-import { Batch, schedule_effect } from '../../reactivity/batch.js';
+import { Batch, current_batch, schedule_effect } from '../../reactivity/batch.js';
import { internal_set, source } from '../../reactivity/sources.js';
import { tag } from '../../dev/tracing.js';
import { createSubscriber } from '../../../../reactivity/create-subscriber.js';
@@ -218,6 +218,8 @@ export class Boundary {
this.is_pending = true;
this.#pending_effect = branch(() => pending(this.#anchor));
+ var batch = /** @type {Batch} */ (current_batch);
+
queue_micro_task(() => {
var fragment = (this.#offscreen_fragment = document.createDocumentFragment());
var anchor = create_text();
@@ -225,7 +227,6 @@ export class Boundary {
fragment.append(anchor);
this.#main_effect = this.#run(() => {
- Batch.ensure();
return branch(() => this.#children(anchor));
});
@@ -237,12 +238,14 @@ export class Boundary {
this.#pending_effect = null;
});
- this.#resolve();
+ this.#resolve(batch);
}
});
}
#render() {
+ var batch = /** @type {Batch} */ (current_batch);
+
try {
this.is_pending = this.has_pending_snippet();
this.#pending_count = 0;
@@ -259,14 +262,17 @@ export class Boundary {
const pending = /** @type {(anchor: Node) => void} */ (this.#props.pending);
this.#pending_effect = branch(() => pending(this.#anchor));
} else {
- this.#resolve();
+ this.#resolve(batch);
}
} catch (error) {
this.error(error);
}
}
- #resolve() {
+ /**
+ * @param {Batch} batch
+ */
+ #resolve(batch) {
this.is_pending = false;
// any effects that were previously deferred should be rescheduled —
@@ -274,12 +280,12 @@ export class Boundary {
// same update that brought us here) the effects will be flushed
for (const e of this.#dirty_effects) {
set_signal_status(e, DIRTY);
- schedule_effect(e);
+ batch.schedule(e);
}
for (const e of this.#maybe_dirty_effects) {
set_signal_status(e, MAYBE_DIRTY);
- schedule_effect(e);
+ batch.schedule(e);
}
this.#dirty_effects.clear();
@@ -320,6 +326,7 @@ export class Boundary {
set_component_context(this.#effect.ctx);
try {
+ Batch.ensure();
return fn();
} catch (e) {
handle_error(e);
@@ -335,11 +342,12 @@ export class Boundary {
* Updates the pending count associated with the currently visible pending snippet,
* if any, such that we can replace the snippet with content once work is done
* @param {1 | -1} d
+ * @param {Batch} batch
*/
- #update_pending_count(d) {
+ #update_pending_count(d, batch) {
if (!this.has_pending_snippet()) {
if (this.parent) {
- this.parent.#update_pending_count(d);
+ this.parent.#update_pending_count(d, batch);
}
// if there's no parent, we're in a scope with no pending snippet
@@ -349,7 +357,7 @@ export class Boundary {
this.#pending_count += d;
if (this.#pending_count === 0) {
- this.#resolve();
+ this.#resolve(batch);
if (this.#pending_effect) {
pause_effect(this.#pending_effect, () => {
@@ -369,9 +377,10 @@ export class Boundary {
* and controls when the current `pending` snippet (if any) is removed.
* Do not call from inside the class
* @param {1 | -1} d
+ * @param {Batch} batch
*/
- update_pending_count(d) {
- this.#update_pending_count(d);
+ update_pending_count(d, batch) {
+ this.#update_pending_count(d, batch);
this.#local_pending_count += d;
@@ -445,9 +454,6 @@ export class Boundary {
}
this.#run(() => {
- // If the failure happened while flushing effects, current_batch can be null
- Batch.ensure();
-
this.#render();
});
};
@@ -464,8 +470,6 @@ export class Boundary {
if (failed) {
this.#failed_effect = this.#run(() => {
- Batch.ensure();
-
try {
return branch(() => {
// errors in `failed` snippets cause the boundary to error again
diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js
index cac980aa9b..b248ce5544 100644
--- a/packages/svelte/src/internal/client/dom/blocks/each.js
+++ b/packages/svelte/src/internal/client/dom/blocks/each.js
@@ -35,7 +35,7 @@ import {
} from '../../reactivity/effects.js';
import { source, mutable_source, internal_set } from '../../reactivity/sources.js';
import { array_from, is_array } from '../../../shared/utils.js';
-import { BRANCH_EFFECT, COMMENT_NODE, EFFECT_OFFSCREEN, INERT } from '#client/constants';
+import { BRANCH_EFFECT, COMMENT_NODE, DESTROYED, EFFECT_OFFSCREEN, INERT } from '#client/constants';
import { queue_micro_task } from '../task.js';
import { get } from '../../runtime.js';
import { DEV } from 'esm-env';
@@ -217,6 +217,10 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
* @param {Batch} batch
*/
function commit(batch) {
+ if ((state.effect.f & DESTROYED) !== 0) {
+ return;
+ }
+
state.pending.delete(batch);
state.fallback = fallback;
diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js
index af66a04534..ffe947eb16 100644
--- a/packages/svelte/src/internal/client/dom/blocks/html.js
+++ b/packages/svelte/src/internal/client/dom/blocks/html.js
@@ -42,17 +42,33 @@ function check_hash(element, server_hash, value) {
/**
* @param {Element | Text | Comment} node
* @param {() => string | TrustedHTML} get_value
+ * @param {boolean} [is_controlled]
* @param {boolean} [svg]
* @param {boolean} [mathml]
* @param {boolean} [skip_warning]
* @returns {void}
*/
-export function html(node, get_value, svg = false, mathml = false, skip_warning = false) {
+export function html(
+ node,
+ get_value,
+ is_controlled = false,
+ svg = false,
+ mathml = false,
+ skip_warning = false
+) {
var anchor = node;
/** @type {string | TrustedHTML} */
var value = '';
+ if (is_controlled) {
+ var parent_node = /** @type {Element} */ (node);
+
+ if (hydrating) {
+ anchor = set_hydrate_node(get_first_child(parent_node));
+ }
+ }
+
template_effect(() => {
var effect = /** @type {Effect} */ (active_effect);
@@ -61,6 +77,22 @@ export function html(node, get_value, svg = false, mathml = false, skip_warning
return;
}
+ if (is_controlled && !hydrating) {
+ // When @html is the only child, use innerHTML directly.
+ // This also handles contenteditable, where the user may delete the anchor comment.
+ effect.nodes = null;
+ parent_node.innerHTML = /** @type {string} */ (value);
+
+ if (value !== '') {
+ assign_nodes(
+ /** @type {TemplateNode} */ (get_first_child(parent_node)),
+ /** @type {TemplateNode} */ (parent_node.lastChild)
+ );
+ }
+
+ return;
+ }
+
if (effect.nodes !== null) {
remove_effect_dom(effect.nodes.start, /** @type {TemplateNode} */ (effect.nodes.end));
effect.nodes = null;
diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js
index 44f5fdd1bc..7d3f9f7fbd 100644
--- a/packages/svelte/src/internal/client/dom/blocks/if.js
+++ b/packages/svelte/src/internal/client/dom/blocks/if.js
@@ -11,7 +11,6 @@ import {
} from '../hydration.js';
import { block } from '../../reactivity/effects.js';
import { BranchManager } from './branches.js';
-import { HYDRATION_START, HYDRATION_START_ELSE } from '../../../../constants.js';
/**
* @param {TemplateNode} node
@@ -37,21 +36,9 @@ export function if_block(node, fn, elseif = false) {
function update_branch(key, fn) {
if (hydrating) {
var data = read_hydration_instruction(/** @type {TemplateNode} */ (marker));
- /**
- * @type {number | false}
- * "[" = branch 0, "[1" = branch 1, "[2" = branch 2, ..., "[!" = else (false)
- */
- var hydrated_key;
- if (data === HYDRATION_START) {
- hydrated_key = 0;
- } else if (data === HYDRATION_START_ELSE) {
- hydrated_key = false;
- } else {
- hydrated_key = parseInt(data.substring(1)); // "[1", "[2", etc.
- }
-
- if (key !== hydrated_key) {
+ // "[n" = branch n, "[-1" = else
+ if (key !== parseInt(data.substring(1))) {
// Hydration mismatch: remove everything inside the anchor and start fresh.
// This could happen with `{#if browser}...{/if}`, for example
var anchor = skip_nodes();
@@ -79,7 +66,7 @@ export function if_block(node, fn, elseif = false) {
});
if (!has_branch) {
- update_branch(false, null);
+ update_branch(-1, null);
}
}, flags);
}
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 23ad6f5cdc..55e61c3774 100644
--- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js
+++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js
@@ -9,6 +9,7 @@ import { hydrating } from '../../hydration.js';
import { tick, untrack } from '../../../runtime.js';
import { is_runes } from '../../../context.js';
import { current_batch, previous_batch } from '../../../reactivity/batch.js';
+import { async_mode_flag } from '../../../../flags/index.js';
/**
* @param {HTMLInputElement} input
@@ -87,8 +88,9 @@ export function bind_value(input, get, set = get) {
var value = get();
if (input === document.activeElement) {
- // we need both, because in non-async mode, render effects run before previous_batch is set
- var batch = /** @type {Batch} */ (previous_batch ?? current_batch);
+ // In sync mode render effects are executed during tree traversal -> needs current_batch
+ // In async mode render effects are flushed once batch resolved, at which point current_batch is null -> needs previous_batch
+ var batch = /** @type {Batch} */ (async_mode_flag ? previous_batch : current_batch);
// Never rewrite the contents of a focused input. We can get here if, for example,
// an update is deferred because of async work depending on the input:
diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/select.js b/packages/svelte/src/internal/client/dom/elements/bindings/select.js
index 46e8f524f8..21be75ba61 100644
--- a/packages/svelte/src/internal/client/dom/elements/bindings/select.js
+++ b/packages/svelte/src/internal/client/dom/elements/bindings/select.js
@@ -4,6 +4,7 @@ import { is } from '../../../proxy.js';
import { is_array } from '../../../../shared/utils.js';
import * as w from '../../../warnings.js';
import { Batch, current_batch, previous_batch } from '../../../reactivity/batch.js';
+import { async_mode_flag } from '../../../../flags/index.js';
/**
* Selects the correct option(s) (depending on whether this is a multiple select)
@@ -115,8 +116,9 @@ export function bind_select_value(select, get, set = get) {
var value = get();
if (select === document.activeElement) {
- // we need both, because in non-async mode, render effects run before previous_batch is set
- var batch = /** @type {Batch} */ (previous_batch ?? current_batch);
+ // In sync mode render effects are executed during tree traversal -> needs current_batch
+ // In async mode render effects are flushed once batch resolved, at which point current_batch is null -> needs previous_batch
+ var batch = /** @type {Batch} */ (async_mode_flag ? previous_batch : current_batch);
// Don't update the `);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e5d857ee81..8693c466c1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1293,8 +1293,8 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
- enhanced-resolve@5.19.0:
- resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==}
+ enhanced-resolve@5.20.0:
+ resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==}
engines: {node: '>=10.13.0'}
enquirer@2.4.1:
@@ -1604,8 +1604,8 @@ packages:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
- immutable@4.3.7:
- resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
+ immutable@4.3.8:
+ resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
@@ -3589,7 +3589,7 @@ snapshots:
emoji-regex@9.2.2: {}
- enhanced-resolve@5.19.0:
+ enhanced-resolve@5.20.0:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.0
@@ -3685,7 +3685,7 @@ snapshots:
eslint-plugin-n@17.24.0(eslint@10.0.0)(typescript@5.5.4):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0)
- enhanced-resolve: 5.19.0
+ enhanced-resolve: 5.20.0
eslint: 10.0.0
eslint-plugin-es-x: 7.8.0(eslint@10.0.0)
get-tsconfig: 4.13.6
@@ -3974,7 +3974,7 @@ snapshots:
ignore@7.0.5: {}
- immutable@4.3.7:
+ immutable@4.3.8:
optional: true
imurmurhash@0.1.4: {}
@@ -4450,7 +4450,7 @@ snapshots:
sass@1.70.0:
dependencies:
chokidar: 3.6.0
- immutable: 4.3.7
+ immutable: 4.3.8
source-map-js: 1.2.1
optional: true