svelte 5.55.2 → 5.55.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "svelte",
3
3
  "description": "Cybernetically enhanced web apps",
4
4
  "license": "MIT",
5
- "version": "5.55.2",
5
+ "version": "5.55.4",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -1,6 +1,7 @@
1
1
  /** @import { AST } from '#compiler' */
2
2
  /** @import { Context } from '../types' */
3
3
  import * as e from '../../../errors.js';
4
+ import * as b from '#compiler/builders';
4
5
  import { validate_opening_tag } from './shared/utils.js';
5
6
 
6
7
  /**
@@ -42,4 +43,29 @@ export function ConstTag(node, context) {
42
43
  function_depth: context.state.function_depth + 1,
43
44
  derived_function_depth: context.state.function_depth + 1
44
45
  });
46
+
47
+ const has_await = node.metadata.expression.has_await;
48
+ const blockers = [...node.metadata.expression.dependencies]
49
+ .map((dep) => dep.blocker)
50
+ .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
51
+
52
+ if (has_await || context.state.async_consts || blockers.length > 0) {
53
+ const run = (context.state.async_consts ??= {
54
+ id: context.state.analysis.root.unique('promises'),
55
+ declaration_count: 0
56
+ });
57
+ node.metadata.promises_id = run.id;
58
+
59
+ const bindings = context.state.scope.get_bindings(declaration);
60
+
61
+ // keep the counter in sync with the number of thunks pushed in ConstTag in transform
62
+ // TODO 6.0 once non-async and non-runes mode is gone investigate making this more robust
63
+ // via something like the approach in https://github.com/sveltejs/svelte/pull/18032
64
+ const length = run.declaration_count + (blockers.length > 0 ? 1 : 0);
65
+ run.declaration_count += blockers.length > 0 ? 2 : 1;
66
+ const blocker = b.member(run.id, b.literal(length), true);
67
+ for (const binding of bindings) {
68
+ binding.blocker = blocker;
69
+ }
70
+ }
45
71
  }
@@ -6,5 +6,5 @@
6
6
  * @param {Context} context
7
7
  */
8
8
  export function Fragment(node, context) {
9
- context.next({ ...context.state, fragment: node });
9
+ context.next({ ...context.state, fragment: node, async_consts: undefined });
10
10
  }
@@ -1,7 +1,6 @@
1
- /** @import { Expression, Identifier, Pattern } from 'estree' */
1
+ /** @import { Expression, Identifier, Pattern, Statement } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types' */
4
- /** @import { ExpressionMetadata } from '../../../nodes.js' */
5
4
  import { dev } from '../../../../state.js';
6
5
  import { extract_identifiers } from '../../../../utils/ast.js';
7
6
  import * as b from '#compiler/builders';
@@ -27,13 +26,7 @@ export function ConstTag(node, context) {
27
26
 
28
27
  context.state.transform[declaration.id.name] = { read: get_value };
29
28
 
30
- add_const_declaration(
31
- context.state,
32
- declaration.id,
33
- expression,
34
- node.metadata.expression,
35
- context.state.scope.get_bindings(declaration)
36
- );
29
+ add_const_declaration(context.state, declaration.id, expression, node.metadata);
37
30
  } else {
38
31
  const identifiers = extract_identifiers(declaration.id);
39
32
  const tmp = b.id(context.state.scope.generate('computed_const'));
@@ -70,13 +63,7 @@ export function ConstTag(node, context) {
70
63
  expression = b.call('$.tag', expression, b.literal('[@const]'));
71
64
  }
72
65
 
73
- add_const_declaration(
74
- context.state,
75
- tmp,
76
- expression,
77
- node.metadata.expression,
78
- context.state.scope.get_bindings(declaration)
79
- );
66
+ add_const_declaration(context.state, tmp, expression, node.metadata);
80
67
 
81
68
  for (const node of identifiers) {
82
69
  context.state.transform[node.name] = {
@@ -90,43 +77,34 @@ export function ConstTag(node, context) {
90
77
  * @param {ComponentContext['state']} state
91
78
  * @param {Identifier} id
92
79
  * @param {Expression} expression
93
- * @param {ExpressionMetadata} metadata
94
- * @param {import('#compiler').Binding[]} bindings
80
+ * @param {AST.ConstTag['metadata']} metadata
95
81
  */
96
- function add_const_declaration(state, id, expression, metadata, bindings) {
82
+ function add_const_declaration(state, id, expression, metadata) {
97
83
  // we need to eagerly evaluate the expression in order to hit any
98
84
  // 'Cannot access x before initialization' errors
99
85
  const after = dev ? [b.stmt(b.call('$.get', id))] : [];
100
86
 
101
- const has_await = metadata.has_await;
102
- const blockers = [...metadata.dependencies]
87
+ const blockers = [...metadata.expression.dependencies]
103
88
  .map((dep) => dep.blocker)
104
89
  .filter((b) => b !== null && b.object !== state.async_consts?.id);
105
90
 
106
- if (has_await || state.async_consts || blockers.length > 0) {
91
+ if (metadata.promises_id) {
107
92
  const run = (state.async_consts ??= {
108
- id: b.id(state.scope.generate('promises')),
93
+ id: metadata.promises_id,
109
94
  thunks: []
110
95
  });
111
96
 
112
97
  state.consts.push(b.let(id));
113
98
 
114
- const assignment = b.assignment('=', id, expression);
115
- const body = after.length === 0 ? assignment : b.block([b.stmt(assignment), ...after]);
116
-
117
99
  if (blockers.length === 1) {
118
100
  run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
119
101
  } else if (blockers.length > 0) {
120
102
  run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
121
103
  }
122
104
 
123
- run.thunks.push(b.thunk(body, has_await));
124
-
125
- const blocker = b.member(run.id, b.literal(run.thunks.length - 1), true);
126
-
127
- for (const binding of bindings) {
128
- binding.blocker = blocker;
129
- }
105
+ // keep the number of thunks pushed in sync with ConstTag in analysis phase
106
+ const assignment = b.assignment('=', id, expression);
107
+ run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
130
108
  } else {
131
109
  state.consts.push(b.const(id, expression));
132
110
  state.consts.push(...after);
@@ -1,4 +1,4 @@
1
- /** @import { Expression, Pattern } from 'estree' */
1
+ /** @import { Expression, Pattern, Statement } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types.js' */
4
4
  import * as b from '#compiler/builders';
@@ -12,19 +12,17 @@ export function ConstTag(node, context) {
12
12
  const declaration = node.declaration.declarations[0];
13
13
  const id = /** @type {Pattern} */ (context.visit(declaration.id));
14
14
  const init = /** @type {Expression} */ (context.visit(declaration.init));
15
- const has_await = node.metadata.expression.has_await;
16
15
  const blockers = [...node.metadata.expression.dependencies]
17
16
  .map((dep) => dep.blocker)
18
17
  .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
19
18
 
20
- if (has_await || context.state.async_consts || blockers.length > 0) {
19
+ if (node.metadata.promises_id) {
21
20
  const run = (context.state.async_consts ??= {
22
- id: b.id(context.state.scope.generate('promises')),
21
+ id: node.metadata.promises_id,
23
22
  thunks: []
24
23
  });
25
24
 
26
25
  const identifiers = extract_identifiers(declaration.id);
27
- const bindings = context.state.scope.get_bindings(declaration);
28
26
 
29
27
  for (const identifier of identifiers) {
30
28
  context.state.init.push(b.let(identifier.name));
@@ -36,13 +34,9 @@ export function ConstTag(node, context) {
36
34
  run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
37
35
  }
38
36
 
37
+ // keep the number of thunks pushed in sync with ConstTag in analysis phase
39
38
  const assignment = b.assignment('=', id, init);
40
- run.thunks.push(b.thunk(b.block([b.stmt(assignment)]), has_await));
41
-
42
- const blocker = b.member(run.id, b.literal(run.thunks.length - 1), true);
43
- for (const binding of bindings) {
44
- binding.blocker = blocker;
45
- }
39
+ run.thunks.push(b.thunk(assignment, node.metadata.expression.has_await));
46
40
  } else {
47
41
  context.state.init.push(b.const(id, init));
48
42
  }
@@ -62,6 +62,8 @@ export const STATE_SYMBOL = Symbol('$state');
62
62
  export const LEGACY_PROPS = Symbol('legacy props');
63
63
  export const LOADING_ATTR_SYMBOL = Symbol('');
64
64
  export const PROXY_PATH_SYMBOL = Symbol('proxy path');
65
+ /** An anchor might change, via this symbol on the original anchor we can tell HMR about the updated anchor */
66
+ export const HMR_ANCHOR = Symbol('hmr anchor');
65
67
 
66
68
  /** allow users to ignore aborted signal errors if `reason.name === 'StaleReactionError` */
67
69
  export const STALE_REACTION = new (class StaleReactionError extends Error {
@@ -1,6 +1,6 @@
1
1
  /** @import { Effect, TemplateNode } from '#client' */
2
2
  import { FILENAME, HMR } from '../../../constants.js';
3
- import { EFFECT_TRANSPARENT } from '#client/constants';
3
+ import { EFFECT_TRANSPARENT, HMR_ANCHOR } from '#client/constants';
4
4
  import { hydrate_node, hydrating } from '../dom/hydration.js';
5
5
  import { block, branch, destroy_effect } from '../reactivity/effects.js';
6
6
  import { set, source } from '../reactivity/sources.js';
@@ -15,10 +15,10 @@ export function hmr(fn) {
15
15
  const current = source(fn);
16
16
 
17
17
  /**
18
- * @param {TemplateNode} anchor
18
+ * @param {TemplateNode} initial_anchor
19
19
  * @param {any} props
20
20
  */
21
- function wrapper(anchor, props) {
21
+ function wrapper(initial_anchor, props) {
22
22
  let component = {};
23
23
  let instance = {};
24
24
 
@@ -26,6 +26,7 @@ export function hmr(fn) {
26
26
  let effect;
27
27
 
28
28
  let ran = false;
29
+ let anchor = initial_anchor;
29
30
 
30
31
  block(() => {
31
32
  if (component === (component = get(current))) {
@@ -39,6 +40,8 @@ export function hmr(fn) {
39
40
  }
40
41
 
41
42
  effect = branch(() => {
43
+ anchor = /** @type {any} */ (anchor)[HMR_ANCHOR] ?? anchor;
44
+
42
45
  // when the component is invalidated, replace it without transitions
43
46
  if (ran) set_should_intro(false);
44
47
 
@@ -35,7 +35,7 @@ import { queue_micro_task } from '../task.js';
35
35
  import * as e from '../../errors.js';
36
36
  import * as w from '../../warnings.js';
37
37
  import { DEV } from 'esm-env';
38
- import { Batch, current_batch, schedule_effect } from '../../reactivity/batch.js';
38
+ import { Batch, current_batch, previous_batch, schedule_effect } from '../../reactivity/batch.js';
39
39
  import { internal_set, source } from '../../reactivity/sources.js';
40
40
  import { tag } from '../../dev/tracing.js';
41
41
  import { createSubscriber } from '../../../../reactivity/create-subscriber.js';
@@ -386,15 +386,29 @@ export class Boundary {
386
386
 
387
387
  /** @param {unknown} error */
388
388
  error(error) {
389
- var onerror = this.#props.onerror;
390
- let failed = this.#props.failed;
391
-
392
389
  // If we have nothing to capture the error, or if we hit an error while
393
390
  // rendering the fallback, re-throw for another boundary to handle
394
- if (!onerror && !failed) {
391
+ if (!this.#props.onerror && !this.#props.failed) {
395
392
  throw error;
396
393
  }
397
394
 
395
+ if (current_batch?.is_fork) {
396
+ if (this.#main_effect) current_batch.skip_effect(this.#main_effect);
397
+ if (this.#pending_effect) current_batch.skip_effect(this.#pending_effect);
398
+ if (this.#failed_effect) current_batch.skip_effect(this.#failed_effect);
399
+
400
+ current_batch.on_fork_commit(() => {
401
+ this.#handle_error(error);
402
+ });
403
+ } else {
404
+ this.#handle_error(error);
405
+ }
406
+ }
407
+
408
+ /**
409
+ * @param {unknown} error
410
+ */
411
+ #handle_error(error) {
398
412
  if (this.#main_effect) {
399
413
  destroy_effect(this.#main_effect);
400
414
  this.#main_effect = null;
@@ -416,6 +430,8 @@ export class Boundary {
416
430
  set_hydrate_node(skip_nodes());
417
431
  }
418
432
 
433
+ var onerror = this.#props.onerror;
434
+ let failed = this.#props.failed;
419
435
  var did_reset = false;
420
436
  var calling_on_error = false;
421
437
 
@@ -7,8 +7,10 @@ import {
7
7
  pause_effect,
8
8
  resume_effect
9
9
  } from '../../reactivity/effects.js';
10
+ import { HMR_ANCHOR } from '../../constants.js';
10
11
  import { hydrate_node, hydrating } from '../hydration.js';
11
12
  import { create_text, should_defer_append } from '../operations.js';
13
+ import { DEV } from 'esm-env';
12
14
 
13
15
  /**
14
16
  * @typedef {{ effect: Effect, fragment: DocumentFragment }} Branch
@@ -91,6 +93,12 @@ export class BranchManager {
91
93
  this.#onscreen.set(key, offscreen.effect);
92
94
  this.#offscreen.delete(key);
93
95
 
96
+ if (DEV) {
97
+ // Tell hmr.js about the anchor it should use for updates,
98
+ // since the initial one will be removed
99
+ /** @type {any} */ (offscreen.fragment.lastChild)[HMR_ANCHOR] = this.anchor;
100
+ }
101
+
94
102
  // remove the anchor...
95
103
  /** @type {TemplateNode} */ (offscreen.fragment.lastChild).remove();
96
104
 
@@ -164,10 +164,26 @@ export async function save(promise) {
164
164
  */
165
165
  export async function track_reactivity_loss(promise) {
166
166
  var previous_async_effect = reactivity_loss_tracker;
167
+ // Ensure that unrelated reads after an async operation is kicked off don't cause false positives
168
+ queueMicrotask(() => {
169
+ if (reactivity_loss_tracker === previous_async_effect) {
170
+ set_reactivity_loss_tracker(null);
171
+ }
172
+ });
173
+
167
174
  var value = await promise;
168
175
 
169
176
  return () => {
170
177
  set_reactivity_loss_tracker(previous_async_effect);
178
+ // While this can result in false negatives it also guards against the more important
179
+ // false positives that would occur if this is the last in a chain of async operations,
180
+ // and the reactivity_loss_tracker would then stay around until the next async operation happens.
181
+ queueMicrotask(() => {
182
+ if (reactivity_loss_tracker === previous_async_effect) {
183
+ set_reactivity_loss_tracker(null);
184
+ }
185
+ });
186
+
171
187
  return value;
172
188
  };
173
189
  }
@@ -206,7 +222,9 @@ export async function* for_await_track_reactivity_loss(iterable) {
206
222
  normal_completion = true;
207
223
  break;
208
224
  }
225
+ var prev = reactivity_loss_tracker;
209
226
  yield value;
227
+ set_reactivity_loss_tracker(prev);
210
228
  }
211
229
  } finally {
212
230
  // If the iterator had an abrupt completion and `return` is defined on the iterator, call it and return the value
@@ -265,6 +283,8 @@ export function run(thunks) {
265
283
  for (const fn of thunks.slice(1)) {
266
284
  promise = promise
267
285
  .then(() => {
286
+ restore();
287
+
268
288
  if (errored) {
269
289
  throw errored.error;
270
290
  }
@@ -273,7 +293,6 @@ export function run(thunks) {
273
293
  throw STALE_REACTION;
274
294
  }
275
295
 
276
- restore();
277
296
  return fn();
278
297
  })
279
298
  .catch(handle_error);
@@ -120,6 +120,12 @@ export class Batch {
120
120
  */
121
121
  #discard_callbacks = new Set();
122
122
 
123
+ /**
124
+ * Callbacks that should run only when a fork is committed.
125
+ * @type {Set<(batch: Batch) => void>}
126
+ */
127
+ #fork_commit_callbacks = new Set();
128
+
123
129
  /**
124
130
  * Async effects that are currently in flight
125
131
  * @type {Map<Effect, number>}
@@ -489,6 +495,7 @@ export class Batch {
489
495
  discard() {
490
496
  for (const fn of this.#discard_callbacks) fn(this);
491
497
  this.#discard_callbacks.clear();
498
+ this.#fork_commit_callbacks.clear();
492
499
 
493
500
  batches.delete(this);
494
501
  }
@@ -686,6 +693,16 @@ export class Batch {
686
693
  this.#discard_callbacks.add(fn);
687
694
  }
688
695
 
696
+ /** @param {(batch: Batch) => void} fn */
697
+ on_fork_commit(fn) {
698
+ this.#fork_commit_callbacks.add(fn);
699
+ }
700
+
701
+ run_fork_commit_callbacks() {
702
+ for (const fn of this.#fork_commit_callbacks) fn(this);
703
+ this.#fork_commit_callbacks.clear();
704
+ }
705
+
689
706
  settled() {
690
707
  return (this.#deferred ??= deferred()).promise;
691
708
  }
@@ -1057,15 +1074,13 @@ export function schedule_effect(effect) {
1057
1074
  let eager_versions = [];
1058
1075
 
1059
1076
  function eager_flush() {
1060
- try {
1061
- flushSync(() => {
1062
- for (const version of eager_versions) {
1063
- update(version);
1064
- }
1065
- });
1066
- } finally {
1077
+ flushSync(() => {
1078
+ const eager = eager_versions;
1067
1079
  eager_versions = [];
1068
- }
1080
+ for (const version of eager) {
1081
+ update(version);
1082
+ }
1083
+ });
1069
1084
  }
1070
1085
 
1071
1086
  /**
@@ -1212,6 +1227,10 @@ export function fork(fn) {
1212
1227
  source.wv = increment_write_version();
1213
1228
  }
1214
1229
 
1230
+ batch.activate();
1231
+ batch.run_fork_commit_callbacks();
1232
+ batch.deactivate();
1233
+
1215
1234
  // trigger any `$state.eager(...)` expressions with the new state.
1216
1235
  // eager effects don't get scheduled like other effects, so we
1217
1236
  // can't just encounter them during traversal, we need to
@@ -1,4 +1,4 @@
1
- /** @import { Derived, Effect, Source } from '#client' */
1
+ /** @import { Derived, Effect, Reaction, Source, Value } from '#client' */
2
2
  /** @import { Batch } from './batch.js'; */
3
3
  /** @import { Boundary } from '../dom/blocks/boundary.js'; */
4
4
  import { DEV } from 'esm-env';
@@ -12,7 +12,8 @@ import {
12
12
  WAS_MARKED,
13
13
  DESTROYED,
14
14
  CLEAN,
15
- REACTION_RAN
15
+ REACTION_RAN,
16
+ INERT
16
17
  } from '#client/constants';
17
18
  import {
18
19
  active_reaction,
@@ -23,7 +24,9 @@ import {
23
24
  push_reaction_value,
24
25
  is_destroying_effect,
25
26
  update_effect,
26
- remove_reactions
27
+ remove_reactions,
28
+ skipped_deps,
29
+ new_deps
27
30
  } from '../runtime.js';
28
31
  import { equals, safe_equals } from './equality.js';
29
32
  import * as e from '../errors.js';
@@ -48,11 +51,11 @@ import { set_signal_status, update_derived_status } from './status.js';
48
51
  /**
49
52
  * This allows us to track 'reactivity loss' that occurs when signals
50
53
  * are read after a non-context-restoring `await`. Dev-only
51
- * @type {{ effect: Effect, warned: boolean } | null}
54
+ * @type {{ effect: Effect, effect_deps: Set<Value>, warned: boolean } | null}
52
55
  */
53
56
  export let reactivity_loss_tracker = null;
54
57
 
55
- /** @param {{ effect: Effect, warned: boolean } | null} v */
58
+ /** @param {{ effect: Effect, effect_deps: Set<Value>, warned: boolean } | null} v */
56
59
  export function set_reactivity_loss_tracker(v) {
57
60
  reactivity_loss_tracker = v;
58
61
  }
@@ -67,10 +70,6 @@ export const recent_async_deriveds = new Set();
67
70
  /*#__NO_SIDE_EFFECTS__*/
68
71
  export function derived(fn) {
69
72
  var flags = DERIVED | DIRTY;
70
- var parent_derived =
71
- active_reaction !== null && (active_reaction.f & DERIVED) !== 0
72
- ? /** @type {Derived} */ (active_reaction)
73
- : null;
74
73
 
75
74
  if (active_effect !== null) {
76
75
  // Since deriveds are evaluated lazily, any effects created inside them are
@@ -90,7 +89,7 @@ export function derived(fn) {
90
89
  rv: 0,
91
90
  v: /** @type {V} */ (UNINITIALIZED),
92
91
  wv: 0,
93
- parent: parent_derived ?? active_effect,
92
+ parent: active_effect,
94
93
  ac: null
95
94
  };
96
95
 
@@ -128,15 +127,12 @@ export function async_derived(fn, label, location) {
128
127
  var deferreds = new Map();
129
128
 
130
129
  async_effect(() => {
130
+ var effect = /** @type {Effect} */ (active_effect);
131
+
131
132
  if (DEV) {
132
- reactivity_loss_tracker = {
133
- effect: /** @type {Effect} */ (active_effect),
134
- warned: false
135
- };
133
+ reactivity_loss_tracker = { effect, effect_deps: new Set(), warned: false };
136
134
  }
137
135
 
138
- var effect = /** @type {Effect} */ (active_effect);
139
-
140
136
  /** @type {ReturnType<typeof deferred<V>>} */
141
137
  var d = deferred();
142
138
  promise = d.promise;
@@ -152,6 +148,24 @@ export function async_derived(fn, label, location) {
152
148
  }
153
149
 
154
150
  if (DEV) {
151
+ if (reactivity_loss_tracker) {
152
+ // Reused deps from previous run (indices 0 to skipped_deps-1)
153
+ // We deliberately only track direct dependencies of the async expression to encourage
154
+ // dependencies being directly visible at the point of the expression
155
+ if (effect.deps !== null) {
156
+ for (let i = 0; i < skipped_deps; i += 1) {
157
+ reactivity_loss_tracker.effect_deps.add(effect.deps[i]);
158
+ }
159
+ }
160
+
161
+ // New deps discovered this run
162
+ if (new_deps !== null) {
163
+ for (let i = 0; i < new_deps.length; i += 1) {
164
+ reactivity_loss_tracker.effect_deps.add(new_deps[i]);
165
+ }
166
+ }
167
+ }
168
+
155
169
  reactivity_loss_tracker = null;
156
170
  }
157
171
 
@@ -320,23 +334,6 @@ export function destroy_derived_effects(derived) {
320
334
  */
321
335
  let stack = [];
322
336
 
323
- /**
324
- * @param {Derived} derived
325
- * @returns {Effect | null}
326
- */
327
- function get_derived_parent_effect(derived) {
328
- var parent = derived.parent;
329
- while (parent !== null) {
330
- if ((parent.f & DERIVED) === 0) {
331
- // The original parent effect might've been destroyed but the derived
332
- // is used elsewhere now - do not return the destroyed effect in that case
333
- return (parent.f & DESTROYED) === 0 ? /** @type {Effect} */ (parent) : null;
334
- }
335
- parent = parent.parent;
336
- }
337
- return null;
338
- }
339
-
340
337
  /**
341
338
  * @template T
342
339
  * @param {Derived} derived
@@ -345,8 +342,15 @@ function get_derived_parent_effect(derived) {
345
342
  export function execute_derived(derived) {
346
343
  var value;
347
344
  var prev_active_effect = active_effect;
345
+ var parent = derived.parent;
346
+
347
+ if (!is_destroying_effect && parent !== null && (parent.f & (DESTROYED | INERT)) !== 0) {
348
+ w.derived_inert();
349
+
350
+ return derived.v;
351
+ }
348
352
 
349
- set_active_effect(get_derived_parent_effect(derived));
353
+ set_active_effect(parent);
350
354
 
351
355
  if (DEV) {
352
356
  let prev_eager_effects = eager_effects;
@@ -654,16 +654,22 @@ function pause_children(effect, transitions, local) {
654
654
 
655
655
  while (child !== null) {
656
656
  var sibling = child.next;
657
- var transparent =
658
- (child.f & EFFECT_TRANSPARENT) !== 0 ||
659
- // If this is a branch effect without a block effect parent,
660
- // it means the parent block effect was pruned. In that case,
661
- // transparency information was transferred to the branch effect.
662
- ((child.f & BRANCH_EFFECT) !== 0 && (effect.f & BLOCK_EFFECT) !== 0);
663
- // TODO we don't need to call pause_children recursively with a linked list in place
664
- // it's slightly more involved though as we have to account for `transparent` changing
665
- // through the tree.
666
- pause_children(child, transitions, transparent ? local : false);
657
+
658
+ // If this child is a root effect, then it will become an independent root when its parent
659
+ // is destroyed, it should therefore not become inert nor partake in transitions.
660
+ if ((child.f & ROOT_EFFECT) === 0) {
661
+ var transparent =
662
+ (child.f & EFFECT_TRANSPARENT) !== 0 ||
663
+ // If this is a branch effect without a block effect parent,
664
+ // it means the parent block effect was pruned. In that case,
665
+ // transparency information was transferred to the branch effect.
666
+ ((child.f & BRANCH_EFFECT) !== 0 && (effect.f & BLOCK_EFFECT) !== 0);
667
+ // TODO we don't need to call pause_children recursively with a linked list in place
668
+ // it's slightly more involved though as we have to account for `transparent` changing
669
+ // through the tree.
670
+ pause_children(child, transitions, transparent ? local : false);
671
+ }
672
+
667
673
  child = sibling;
668
674
  }
669
675
  }
@@ -111,9 +111,9 @@ export function push_reaction_value(value) {
111
111
  * and until a new dependency is accessed — we track this via `skipped_deps`
112
112
  * @type {null | Value[]}
113
113
  */
114
- let new_deps = null;
114
+ export let new_deps = null;
115
115
 
116
- let skipped_deps = 0;
116
+ export let skipped_deps = 0;
117
117
 
118
118
  /**
119
119
  * Tracks writes that the effect it's executed in doesn't listen to yet,
@@ -580,7 +580,8 @@ export function get(signal) {
580
580
  !untracking &&
581
581
  reactivity_loss_tracker &&
582
582
  !reactivity_loss_tracker.warned &&
583
- (reactivity_loss_tracker.effect.f & REACTION_IS_UPDATING) === 0
583
+ (reactivity_loss_tracker.effect.f & REACTION_IS_UPDATING) === 0 &&
584
+ !reactivity_loss_tracker.effect_deps.has(signal)
584
585
  ) {
585
586
  reactivity_loss_tracker.warned = true;
586
587
 
@@ -74,6 +74,17 @@ export function console_log_state(method) {
74
74
  }
75
75
  }
76
76
 
77
+ /**
78
+ * Reading a derived belonging to a now-destroyed effect may result in stale values
79
+ */
80
+ export function derived_inert() {
81
+ if (DEV) {
82
+ console.warn(`%c[svelte] derived_inert\n%cReading a derived belonging to a now-destroyed effect may result in stale values\nhttps://svelte.dev/e/derived_inert`, bold, normal);
83
+ } else {
84
+ console.warn(`https://svelte.dev/e/derived_inert`);
85
+ }
86
+ }
87
+
77
88
  /**
78
89
  * %handler% should be a function. Did you mean to %suggestion%?
79
90
  * @param {string} handler
package/src/version.js CHANGED
@@ -4,5 +4,5 @@
4
4
  * The current version, as set in package.json.
5
5
  * @type {string}
6
6
  */
7
- export const VERSION = '5.55.2';
7
+ export const VERSION = '5.55.4';
8
8
  export const PUBLIC_VERSION = '5';