svelte 5.57.0 → 5.57.2

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.
Files changed (78) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +8 -8
  3. package/src/compiler/errors.js +10 -0
  4. package/src/compiler/migrate/index.js +15 -4
  5. package/src/compiler/phases/1-parse/acorn.js +36 -2
  6. package/src/compiler/phases/1-parse/index.js +23 -22
  7. package/src/compiler/phases/1-parse/read/context.js +3 -18
  8. package/src/compiler/phases/1-parse/read/expression.js +110 -2
  9. package/src/compiler/phases/1-parse/read/script.js +1 -1
  10. package/src/compiler/phases/1-parse/read/style.js +3 -5
  11. package/src/compiler/phases/1-parse/state/element.js +68 -40
  12. package/src/compiler/phases/1-parse/state/tag.js +8 -6
  13. package/src/compiler/phases/1-parse/utils/html.js +2 -0
  14. package/src/compiler/phases/1-parse/utils/whitespace.js +18 -0
  15. package/src/compiler/phases/2-analyze/css/css-prune.js +7 -6
  16. package/src/compiler/phases/2-analyze/index.js +1 -1
  17. package/src/compiler/phases/2-analyze/visitors/BindDirective.js +1 -1
  18. package/src/compiler/phases/2-analyze/visitors/ClassDeclaration.js +1 -1
  19. package/src/compiler/phases/2-analyze/visitors/FunctionDeclaration.js +1 -1
  20. package/src/compiler/phases/2-analyze/visitors/Identifier.js +1 -2
  21. package/src/compiler/phases/2-analyze/visitors/LetDirective.js +22 -0
  22. package/src/compiler/phases/2-analyze/visitors/SvelteBody.js +1 -1
  23. package/src/compiler/phases/2-analyze/visitors/SvelteDocument.js +1 -1
  24. package/src/compiler/phases/2-analyze/visitors/SvelteWindow.js +1 -1
  25. package/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js +2 -2
  26. package/src/compiler/phases/2-analyze/visitors/shared/a11y/constants.js +1 -1
  27. package/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +3 -3
  28. package/src/compiler/phases/2-analyze/visitors/shared/utils.js +0 -38
  29. package/src/compiler/phases/3-transform/client/utils.js +0 -38
  30. package/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +2 -2
  31. package/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js +2 -1
  32. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +1 -2
  33. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +3 -8
  34. package/src/compiler/phases/3-transform/client/visitors/shared/events.js +3 -2
  35. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +8 -0
  36. package/src/compiler/phases/3-transform/utils.js +1 -1
  37. package/src/compiler/phases/nodes.js +30 -0
  38. package/src/compiler/phases/scope.js +85 -2
  39. package/src/compiler/print/index.js +3 -1
  40. package/src/compiler/state.js +9 -5
  41. package/src/compiler/utils/ast.js +2 -5
  42. package/src/compiler/utils/compile_diagnostic.js +1 -1
  43. package/src/compiler/utils/extract_svelte_ignore.js +1 -1
  44. package/src/compiler/utils/mapped_code.js +13 -1
  45. package/src/index-client.js +1 -1
  46. package/src/internal/client/constants.js +0 -9
  47. package/src/internal/client/dev/debug.js +0 -2
  48. package/src/internal/client/dev/tracing.js +3 -1
  49. package/src/internal/client/dom/blocks/boundary.js +39 -8
  50. package/src/internal/client/dom/blocks/each.js +8 -6
  51. package/src/internal/client/dom/blocks/svelte-element.js +3 -1
  52. package/src/internal/client/dom/css.js +1 -1
  53. package/src/internal/client/dom/elements/attributes.js +168 -148
  54. package/src/internal/client/dom/elements/bindings/document.js +3 -2
  55. package/src/internal/client/dom/elements/events.js +6 -1
  56. package/src/internal/client/dom/elements/transitions.js +3 -3
  57. package/src/internal/client/error-handling.js +5 -1
  58. package/src/internal/client/errors.js +0 -16
  59. package/src/internal/client/proxy.js +12 -7
  60. package/src/internal/client/reactivity/async.js +5 -0
  61. package/src/internal/client/reactivity/batch.js +103 -75
  62. package/src/internal/client/reactivity/deriveds.js +0 -3
  63. package/src/internal/client/reactivity/sources.js +31 -19
  64. package/src/internal/client/reactivity/utils.js +2 -23
  65. package/src/internal/client/runtime.js +18 -25
  66. package/src/internal/server/context.js +9 -2
  67. package/src/internal/server/index.js +9 -2
  68. package/src/internal/server/renderer.js +101 -69
  69. package/src/internal/shared/errors.js +16 -0
  70. package/src/reactivity/date.js +2 -2
  71. package/src/reactivity/url-search-params.js +1 -1
  72. package/src/version.js +1 -1
  73. package/types/index.d.ts.map +1 -1
  74. package/src/compiler/phases/1-parse/utils/create.js +0 -16
  75. package/src/compiler/phases/2-analyze/visitors/shared/special-element.js +0 -16
  76. package/src/compiler/utils/push_array.js +0 -13
  77. /package/src/compiler/{phases/2-analyze/utils → utils}/check_graph_for_cycles.js +0 -0
  78. /package/src/compiler/{phases/1-parse/utils → utils}/fuzzymatch.js +0 -0
@@ -22,7 +22,6 @@ import {
22
22
  import { async_mode_flag } from '../../flags/index.js';
23
23
  import { deferred, define_property, includes } from '../../shared/utils.js';
24
24
  import {
25
- active_effect,
26
25
  active_reaction,
27
26
  get,
28
27
  increment_write_version,
@@ -38,7 +37,6 @@ import { eager_effect, teardown, unlink_effect } from './effects.js';
38
37
  import { defer_effect } from './utils.js';
39
38
  import { UNINITIALIZED } from '../../../constants.js';
40
39
  import { set_signal_status } from './status.js';
41
- import { legacy_is_updating_store } from './store.js';
42
40
  import { invariant } from '../../shared/dev.js';
43
41
  import { log_effect_tree } from '../dev/debug.js';
44
42
  import { OBSOLETE } from './deriveds.js';
@@ -160,10 +158,17 @@ export class Batch {
160
158
  #deferred = null;
161
159
 
162
160
  /**
163
- * The root effects that need to be flushed
161
+ * Effects that were scheduled in this batch but not yet 'resolved' into the
162
+ * root effects that need to be flushed. Resolving — the upwards traversal that
163
+ * marks the path to each effect on the shared effect tree (see #resolve) — is
164
+ * deferred until the batch is processed, so that the markers are created and
165
+ * consumed within a single traversal. Scheduling into other batches (which can
166
+ * happen concurrently, e.g. while a batch is committed) can therefore never
167
+ * observe (and be confused by) this batch's markers.
168
+ * May contain duplicates — deduplication happens during resolving
164
169
  * @type {Effect[]}
165
170
  */
166
- #roots = [];
171
+ #scheduled = [];
167
172
 
168
173
  /**
169
174
  * Effects created while this batch was active.
@@ -273,14 +278,57 @@ export class Batch {
273
278
  this.#unskipped_branches.add(effect);
274
279
  }
275
280
 
276
- #process() {
277
- this.#started = true;
281
+ /**
282
+ * Convert the effects that were scheduled in this batch into the root effects
283
+ * that need to be traversed, marking the path to each effect (by clearing the
284
+ * `CLEAN` flag on ancestor branches) so that the traversal can find them.
285
+ * This happens right before traversal rather than at scheduling time, so that
286
+ * the markers left on the (shared) effect tree are created and consumed within
287
+ * a single traversal — scheduling into other batches can never observe them
288
+ * @returns {Effect[]}
289
+ */
290
+ #resolve() {
291
+ /** @type {Effect[]} */
292
+ var roots = [];
278
293
 
279
- if (flush_count++ > 1000) {
280
- this.#unlink();
281
- infinite_loop_guard();
294
+ for (const effect of this.#scheduled) {
295
+ // skip effects that are destroyed, or that already ran (e.g. because
296
+ // they were reached by the traversal that preceded a drain iteration,
297
+ // or because they were scheduled twice)
298
+ if ((effect.f & DESTROYED) !== 0 || (effect.f & (DIRTY | MAYBE_DIRTY)) === 0) continue;
299
+
300
+ var e = effect;
301
+ var covered = false;
302
+
303
+ while (e.parent !== null) {
304
+ e = e.parent;
305
+ var flags = e.f;
306
+
307
+ if ((flags & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) {
308
+ if ((flags & CLEAN) === 0) {
309
+ // the path to the root was already marked, meaning the
310
+ // root was already collected — nothing left to do
311
+ covered = true;
312
+ break;
313
+ }
314
+
315
+ e.f ^= CLEAN;
316
+ }
317
+ }
318
+
319
+ if (!covered) {
320
+ roots.push(e);
321
+ }
282
322
  }
283
323
 
324
+ this.#scheduled = [];
325
+
326
+ return roots;
327
+ }
328
+
329
+ #process() {
330
+ this.#started = true;
331
+
284
332
  if (DEV) {
285
333
  // track all the values that were updated during this flush,
286
334
  // so that they can be reset afterwards
@@ -304,9 +352,6 @@ export class Batch {
304
352
  this.schedule(e);
305
353
  }
306
354
 
307
- const roots = this.#roots;
308
- this.#roots = [];
309
-
310
355
  this.apply();
311
356
 
312
357
  /** @type {Effect[]} */
@@ -321,18 +366,28 @@ export class Batch {
321
366
  */
322
367
  var updates = (legacy_updates = []);
323
368
 
324
- for (const root of roots) {
325
- try {
326
- this.#traverse(root, effects, render_effects);
327
- } catch (e) {
328
- reset_all(root);
329
- // If there's no async work left, this branch is now dead and needs
330
- // to be discarded to not become a zombie that is never cleaned up.
331
- // See https://github.com/sveltejs/svelte/issues/18221#issuecomment-4497918414
332
- // for a (non-minimal) reproduction that demonstrates a case where this is necessary
333
- // to not get follow-up false-positives via "batch has scheduled roots" invariant errors.
334
- if (!this.#is_deferred()) this.discard();
335
- throw e;
369
+ // Effects can be scheduled during traversal (e.g. because a parent each/await/etc
370
+ // block updated an internal source, or because an effect invalidated itself)
371
+ // hence we loop until there are no more scheduled effects.
372
+ while (this.#scheduled.length > 0) {
373
+ if (flush_count++ > 1000) {
374
+ this.#unlink();
375
+ infinite_loop_guard(); // TODO try to reset_all() here?
376
+ }
377
+
378
+ for (const root of this.#resolve()) {
379
+ try {
380
+ this.#traverse(root, effects, render_effects);
381
+ } catch (e) {
382
+ reset_all(root);
383
+ // If there's no async work left, this branch is now dead and needs
384
+ // to be discarded to not become a zombie that is never cleaned up.
385
+ // See https://github.com/sveltejs/svelte/issues/18221#issuecomment-4497918414
386
+ // for a (non-minimal) reproduction that demonstrates a case where this is necessary
387
+ // to not get follow-up false-positives via "batch has scheduled roots" invariant errors.
388
+ if (!this.#is_deferred()) this.discard();
389
+ throw e;
390
+ }
336
391
  }
337
392
  }
338
393
 
@@ -393,7 +448,7 @@ export class Batch {
393
448
 
394
449
  var next_batch = /** @type {Batch | null} */ (/** @type {unknown} */ (current_batch));
395
450
 
396
- if (this.#pending === 0 && (this.#roots.length === 0 || next_batch !== null)) {
451
+ if (this.#pending === 0 && (this.#scheduled.length === 0 || next_batch !== null)) {
397
452
  this.#unlink();
398
453
 
399
454
  // Order matters here - we need to commit and THEN continue flushing new batches, not the other way around,
@@ -408,12 +463,15 @@ export class Batch {
408
463
  }
409
464
 
410
465
  // Edge case: During traversal new branches might create effects that run immediately and set state,
411
- // causing an effect and therefore a root to be scheduled again. We need to traverse the current batch
466
+ // causing an effect to be scheduled again. We need to traverse the current batch
412
467
  // once more in that case - most of the time this will just clean up dirty branches.
413
- if (this.#roots.length > 0) {
468
+ if (this.#scheduled.length > 0) {
414
469
  if (next_batch !== null) {
415
- const batch = next_batch;
416
- batch.#roots.push(...this.#roots.filter((r) => !batch.#roots.includes(r)));
470
+ for (const e of this.#scheduled) {
471
+ next_batch.#scheduled.push(e);
472
+ }
473
+
474
+ this.#scheduled = [];
417
475
  } else {
418
476
  next_batch = this;
419
477
  }
@@ -712,7 +770,7 @@ export class Batch {
712
770
  // The microtask queue can contain the batch already scheduled to run right
713
771
  // after this one is finished, so throwing the invariant would be wrong here.
714
772
  if (DEV && !batch.#decrement_queued) {
715
- invariant(batch.#roots.length === 0, 'Batch has scheduled roots');
773
+ invariant(batch.#scheduled.length === 0, 'Batch has scheduled effects');
716
774
  }
717
775
 
718
776
  // A batch was unskipped in a later batch -> tell prior batches to unskip it, too
@@ -768,14 +826,12 @@ export class Batch {
768
826
 
769
827
  // Only apply and traverse when we know we triggered async work with marking the effects
770
828
  // and know this won't run anyway right afterwards
771
- if (batch.#roots.length > 0 && !batch.#decrement_queued) {
829
+ if (batch.#scheduled.length > 0 && !batch.#decrement_queued) {
772
830
  batch.apply();
773
831
 
774
- for (var root of batch.#roots) {
832
+ for (var root of batch.#resolve()) {
775
833
  batch.#traverse(root, [], []);
776
834
  }
777
-
778
- batch.#roots = [];
779
835
  }
780
836
 
781
837
  batch.deactivate();
@@ -939,43 +995,7 @@ export class Batch {
939
995
  return;
940
996
  }
941
997
 
942
- var e = effect;
943
-
944
- while (e.parent !== null) {
945
- e = e.parent;
946
- var flags = e.f;
947
-
948
- // if the effect is being scheduled because a parent (each/await/etc) block
949
- // updated an internal source, or because a branch is being unskipped,
950
- // bail out or we'll cause a second flush
951
- if (collected_effects !== null && e === active_effect) {
952
- if (async_mode_flag) return;
953
-
954
- // in sync mode, render effects run during traversal. in an extreme edge case
955
- // — namely that we're setting a value inside a derived read during traversal —
956
- // they can be made dirty after they have already been visited, in which
957
- // case we shouldn't bail out. we also shouldn't bail out if we're
958
- // updating a store inside a `$:`, since this might invalidate
959
- // effects that were already visited
960
- if (
961
- (active_reaction === null || (active_reaction.f & DERIVED) === 0) &&
962
- !legacy_is_updating_store
963
- ) {
964
- return;
965
- }
966
- }
967
-
968
- if ((flags & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) {
969
- if ((flags & CLEAN) === 0) {
970
- // branch is already dirty, bail
971
- return;
972
- }
973
-
974
- e.f ^= CLEAN;
975
- }
976
- }
977
-
978
- this.#roots.push(e);
998
+ this.#scheduled.push(effect);
979
999
  }
980
1000
 
981
1001
  #unlink() {
@@ -1012,15 +1032,15 @@ export class Batch {
1012
1032
  */
1013
1033
  export function flushSync(fn) {
1014
1034
  var was_flushing_sync = is_flushing_sync;
1035
+ var prev_previous_batch = previous_batch;
1036
+ previous_batch = null;
1015
1037
  is_flushing_sync = true;
1016
1038
 
1017
1039
  try {
1018
1040
  var result;
1019
1041
 
1020
1042
  if (fn) {
1021
- if (current_batch !== null && !current_batch.is_fork) {
1022
- current_batch.flush();
1023
- }
1043
+ flushSync(); // flush anything pending through the while loop below
1024
1044
 
1025
1045
  result = fn();
1026
1046
  }
@@ -1036,6 +1056,7 @@ export function flushSync(fn) {
1036
1056
  }
1037
1057
  } finally {
1038
1058
  is_flushing_sync = was_flushing_sync;
1059
+ previous_batch = prev_previous_batch;
1039
1060
  }
1040
1061
  }
1041
1062
 
@@ -1275,6 +1296,10 @@ export function eager(fn) {
1275
1296
  let version = version_map.get(parent) ?? source(0);
1276
1297
  version_map.set(parent, version);
1277
1298
 
1299
+ if (DEV) {
1300
+ version.label ??= '$state.eager version';
1301
+ }
1302
+
1278
1303
  teardown(() => {
1279
1304
  if (parent.f & DESTROYING) version_map.delete(parent);
1280
1305
  });
@@ -1382,6 +1407,8 @@ export function fork(fn) {
1382
1407
  e.fork_timing();
1383
1408
  }
1384
1409
 
1410
+ flushSync();
1411
+
1385
1412
  var batch = Batch.ensure();
1386
1413
  batch.is_fork = true;
1387
1414
  batch_values = new Map();
@@ -1389,7 +1416,8 @@ export function fork(fn) {
1389
1416
  var committed = false;
1390
1417
  var settled = batch.settled();
1391
1418
 
1392
- flushSync(fn);
1419
+ fn();
1420
+ flushSync();
1393
1421
 
1394
1422
  return {
1395
1423
  commit: async () => {
@@ -9,7 +9,6 @@ import {
9
9
  EFFECT_PRESERVED,
10
10
  STALE_REACTION,
11
11
  ASYNC,
12
- WAS_MARKED,
13
12
  DESTROYED,
14
13
  CLEAN,
15
14
  REACTION_RAN,
@@ -365,7 +364,6 @@ export function execute_derived(derived) {
365
364
 
366
365
  stack.push(derived);
367
366
 
368
- derived.f &= ~WAS_MARKED;
369
367
  destroy_derived_effects(derived);
370
368
  value = update_reaction(derived);
371
369
  } finally {
@@ -375,7 +373,6 @@ export function execute_derived(derived) {
375
373
  }
376
374
  } else {
377
375
  try {
378
- derived.f &= ~WAS_MARKED;
379
376
  destroy_derived_effects(derived);
380
377
  value = update_reaction(derived);
381
378
  } finally {
@@ -25,10 +25,7 @@ import {
25
25
  MAYBE_DIRTY,
26
26
  BLOCK_EFFECT,
27
27
  ROOT_EFFECT,
28
- ASYNC,
29
- WAS_MARKED,
30
- CONNECTED,
31
- REACTION_IS_UPDATING
28
+ ASYNC
32
29
  } from '#client/constants';
33
30
  import * as e from '../errors.js';
34
31
  import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
@@ -49,7 +46,7 @@ import { set_signal_status, update_derived_status } from './status.js';
49
46
  /** @type {Set<Effect>} */
50
47
  export let eager_effects = new Set();
51
48
 
52
- /** @type {Map<Source, any>} */
49
+ /** @type {Map<Value, any>} */
53
50
  export const old_values = new Map();
54
51
 
55
52
  /**
@@ -144,7 +141,7 @@ export function mutate(source, value) {
144
141
 
145
142
  /**
146
143
  * @template V
147
- * @param {Source<V>} source
144
+ * @param {Value<V>} source
148
145
  * @param {V} value
149
146
  * @param {boolean} [should_proxy]
150
147
  * @returns {V}
@@ -171,9 +168,20 @@ export function set(source, value, should_proxy = false) {
171
168
  return internal_set(source, new_value, legacy_updates);
172
169
  }
173
170
 
171
+ /**
172
+ * A set of signals we have already seen while traversing in mark_reactions.
173
+ * Not always set to balance the common case of sources only having a couple
174
+ * of (transitive) dependencies (where always creating a Set would be bad for perf)
175
+ * with the edge case of extremely deep or wide dependency arrays with cycles.
176
+ * @type {Set<any> | null}
177
+ */
178
+ var seen = null;
179
+ /** Number of transitive dependencies, see {@link seen} for more info */
180
+ var count_deps = 0;
181
+
174
182
  /**
175
183
  * @template V
176
- * @param {Source<V>} source
184
+ * @param {Value<V>} source
177
185
  * @param {V} value
178
186
  * @param {Effect[] | null} [updated_during_traversal]
179
187
  * @returns {V}
@@ -240,7 +248,10 @@ export function internal_set(source, value, updated_during_traversal = null) {
240
248
 
241
249
  // For debugging, in case you want to know which reactions are being scheduled:
242
250
  // log_reactions(source);
251
+ seen = null;
252
+ count_deps = 0;
243
253
  mark_reactions(source, DIRTY, updated_during_traversal);
254
+ seen = null;
244
255
 
245
256
  // It's possible that the current reaction might not have up-to-date dependencies
246
257
  // whilst it's actively running. So in the case of ensuring it registers the reaction
@@ -347,6 +358,18 @@ function mark_reactions(signal, status, updated_during_traversal) {
347
358
  var runes = is_runes();
348
359
  var length = reactions.length;
349
360
 
361
+ count_deps += length;
362
+ // Activate the `seen` Set if we think from the unusually high number of deps that
363
+ // there might be cycles in the graph, to avoid repeated lookups for reactions
364
+ // Example: https://github.com/sveltejs/svelte/issues/16658 has a graph with one source
365
+ // reaching ~10000 distinct deriveds/effects each, resulting in 65 million walks through repeated visits.
366
+ if (count_deps > 100000 && seen === null) seen = new Set();
367
+
368
+ if (seen !== null) {
369
+ if (seen.has(signal)) return;
370
+ seen.add(signal);
371
+ }
372
+
350
373
  for (var i = 0; i < length; i++) {
351
374
  var reaction = reactions[i];
352
375
  var flags = reaction.f;
@@ -370,18 +393,7 @@ function mark_reactions(signal, status, updated_during_traversal) {
370
393
  var derived = /** @type {Derived} */ (reaction);
371
394
 
372
395
  batch_values?.delete(derived);
373
-
374
- if ((flags & WAS_MARKED) === 0) {
375
- // Only connected deriveds being executed outside the update cycle can be reliably unmarked right away
376
- if (
377
- flags & CONNECTED &&
378
- (active_effect === null || (active_effect.f & REACTION_IS_UPDATING) === 0)
379
- ) {
380
- reaction.f |= WAS_MARKED;
381
- }
382
-
383
- mark_reactions(derived, MAYBE_DIRTY, updated_during_traversal);
384
- }
396
+ mark_reactions(derived, MAYBE_DIRTY, updated_during_traversal);
385
397
  } else if (not_dirty) {
386
398
  var effect = /** @type {Effect} */ (reaction);
387
399
 
@@ -1,24 +1,7 @@
1
- /** @import { Derived, Effect, Value } from '#client' */
2
- import { CLEAN, DERIVED, DIRTY, MAYBE_DIRTY, WAS_MARKED } from '#client/constants';
1
+ /** @import { Effect } from '#client' */
2
+ import { CLEAN, DIRTY, MAYBE_DIRTY } from '#client/constants';
3
3
  import { set_signal_status } from './status.js';
4
4
 
5
- /**
6
- * @param {Value[] | null} deps
7
- */
8
- function clear_marked(deps) {
9
- if (deps === null) return;
10
-
11
- for (const dep of deps) {
12
- if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
13
- continue;
14
- }
15
-
16
- dep.f ^= WAS_MARKED;
17
-
18
- clear_marked(/** @type {Derived} */ (dep).deps);
19
- }
20
- }
21
-
22
5
  /**
23
6
  * @param {Effect} effect
24
7
  * @param {Set<Effect>} dirty_effects
@@ -31,10 +14,6 @@ export function defer_effect(effect, dirty_effects, maybe_dirty_effects) {
31
14
  maybe_dirty_effects.add(effect);
32
15
  }
33
16
 
34
- // Since we're not executing these effects now, we need to clear any WAS_MARKED flags
35
- // so that other batches can correctly reach these effects during their own traversal
36
- clear_marked(effect.deps);
37
-
38
17
  // mark as clean so they get scheduled if they depend on pending async state
39
18
  set_signal_status(effect, CLEAN);
40
19
  }
@@ -21,7 +21,6 @@ import {
21
21
  REACTION_IS_UPDATING,
22
22
  STALE_REACTION,
23
23
  ERROR_VALUE,
24
- WAS_MARKED,
25
24
  MANAGED_EFFECT,
26
25
  REACTION_RAN
27
26
  } from './constants.js';
@@ -61,11 +60,6 @@ import { without_reactive_context } from './dom/elements/bindings/shared.js';
61
60
  import { set_signal_status, update_derived_status } from './reactivity/status.js';
62
61
  import * as w from './warnings.js';
63
62
 
64
- /**
65
- * True if updating in an effect context that is reactive (i.e. not branch/root effects)
66
- */
67
- let is_updating_effect = false;
68
-
69
63
  export let is_destroying_effect = false;
70
64
 
71
65
  /** @param {boolean} value */
@@ -94,13 +88,17 @@ export function set_active_effect(effect) {
94
88
  /**
95
89
  * When sources are created within a reaction, reading and writing
96
90
  * them within that reaction should not cause a re-run
97
- * @type {null | Set<Source>}
91
+ * @type {null | Set<Value>}
98
92
  */
99
93
  export let current_sources = null;
100
94
 
101
95
  /** @param {Value} value */
102
96
  export function push_reaction_value(value) {
103
- if (active_reaction !== null && (!async_mode_flag || (active_reaction.f & DERIVED) !== 0)) {
97
+ if (
98
+ active_reaction !== null &&
99
+ ((!async_mode_flag && (active_reaction.f & REACTION_IS_UPDATING) !== 0) ||
100
+ (active_reaction.f & DERIVED) !== 0)
101
+ ) {
104
102
  (current_sources ??= new Set()).add(value);
105
103
  }
106
104
  }
@@ -118,11 +116,11 @@ export let skipped_deps = 0;
118
116
  /**
119
117
  * Tracks writes that the effect it's executed in doesn't listen to yet,
120
118
  * so that the dependency can be added to the effect later on if it then reads it
121
- * @type {null | Source[]}
119
+ * @type {null | Value[]}
122
120
  */
123
121
  export let untracked_writes = null;
124
122
 
125
- /** @param {null | Source[]} value */
123
+ /** @param {null | Value[]} value */
126
124
  export function set_untracked_writes(value) {
127
125
  untracked_writes = value;
128
126
  }
@@ -160,10 +158,6 @@ export function is_dirty(reaction) {
160
158
  return true;
161
159
  }
162
160
 
163
- if (flags & DERIVED) {
164
- reaction.f &= ~WAS_MARKED;
165
- }
166
-
167
161
  if ((flags & MAYBE_DIRTY) !== 0) {
168
162
  var dependencies = /** @type {Value[]} */ (reaction.deps);
169
163
  var length = dependencies.length;
@@ -404,17 +398,14 @@ function remove_reaction(signal, dependency) {
404
398
  ) {
405
399
  var derived = /** @type {Derived} */ (dependency);
406
400
 
407
- // If we are working with a derived that is owned by an effect, then mark it as being
408
- // disconnected and remove the mark flag, as it cannot be reliably removed otherwise
409
401
  if ((derived.f & CONNECTED) !== 0) {
410
402
  derived.f ^= CONNECTED;
411
- derived.f &= ~WAS_MARKED;
412
403
  }
413
404
 
414
405
  // In a fork it's possible that a derived is executed and gets reactions, then commits, but is
415
406
  // never re-executed. This is possible when the derived is only executed once in the context
416
407
  // of a new branch which happens before fork.commit() runs. In this case, the derived still has
417
- // UNINITIALIZED as its value, and then when it's loosing its reactions we need to ensure it stays
408
+ // UNINITIALIZED as its value, and then when it's losing its reactions we need to ensure it stays
418
409
  // DIRTY so it is reexecuted once someone wants its value again.
419
410
  if (derived.v !== UNINITIALIZED) {
420
411
  update_derived_status(derived);
@@ -466,10 +457,8 @@ export function update_effect(effect) {
466
457
  set_signal_status(effect, CLEAN);
467
458
 
468
459
  var previous_effect = active_effect;
469
- var was_updating_effect = is_updating_effect;
470
460
 
471
461
  active_effect = effect;
472
- is_updating_effect = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0; // Branch/root effects are not reactive contexts
473
462
 
474
463
  if (DEV) {
475
464
  var previous_component_fn = dev_current_component_function;
@@ -502,7 +491,6 @@ export function update_effect(effect) {
502
491
  }
503
492
  }
504
493
  } finally {
505
- is_updating_effect = was_updating_effect;
506
494
  active_effect = previous_effect;
507
495
 
508
496
  if (DEV) {
@@ -684,13 +672,12 @@ export function get(signal) {
684
672
  return value;
685
673
  }
686
674
 
687
- // connect disconnected deriveds if we are reading them inside an effect,
688
- // or inside another derived that is already connected
675
+ // connect disconnected deriveds when reading them inside a connected reaction
689
676
  var should_connect =
690
677
  (derived.f & CONNECTED) === 0 &&
691
678
  !untracking &&
692
679
  active_reaction !== null &&
693
- (is_updating_effect || (active_reaction.f & CONNECTED) !== 0);
680
+ (active_reaction.f & CONNECTED) !== 0;
694
681
 
695
682
  var is_new = (derived.f & REACTION_RAN) === 0;
696
683
 
@@ -732,7 +719,13 @@ function reconnect(derived) {
732
719
  if (derived.deps === null) return;
733
720
 
734
721
  for (const dep of derived.deps) {
735
- (dep.reactions ??= []).push(derived);
722
+ var reactions = dep.reactions;
723
+
724
+ if (reactions === null) {
725
+ dep.reactions = [derived];
726
+ } else if (!includes.call(reactions, derived)) {
727
+ reactions.push(derived);
728
+ }
736
729
 
737
730
  if ((dep.f & DERIVED) !== 0 && (dep.f & CONNECTED) === 0) {
738
731
  unfreeze_derived_effects(/** @type {Derived} */ (dep));
@@ -1,6 +1,7 @@
1
1
  /** @import { SSRContext } from '#server' */
2
2
  import { DEV } from 'esm-env';
3
3
  import { create_context, get_or_init_context_map } from '../shared/context.js';
4
+ import * as e from './errors.js';
4
5
 
5
6
  /** @type {SSRContext | null} */
6
7
  export var ssr_context = null;
@@ -40,7 +41,13 @@ export function getContext(key) {
40
41
  * @returns {T}
41
42
  */
42
43
  export function setContext(key, context) {
43
- get_or_init_context_map(ssr_context, 'setContext').set(key, context);
44
+ const context_map = get_or_init_context_map(ssr_context, 'setContext');
45
+
46
+ if (/** @type {SSRContext} */ (ssr_context).i) {
47
+ e.set_context_after_init();
48
+ }
49
+
50
+ context_map.set(key, context);
44
51
  return context;
45
52
  }
46
53
 
@@ -61,7 +68,7 @@ export function getAllContexts() {
61
68
  * @param {Function} [fn]
62
69
  */
63
70
  export function push(fn) {
64
- ssr_context = { p: ssr_context, c: null, r: null };
71
+ ssr_context = { p: ssr_context, c: null, r: null, i: false };
65
72
 
66
73
  if (DEV) {
67
74
  ssr_context.function = fn;
@@ -146,8 +146,10 @@ export function attributes(attrs, css_hash, classes, styles, flags = 0) {
146
146
  const is_html = (flags & ELEMENT_IS_NAMESPACED) === 0;
147
147
  const lowercase = (flags & ELEMENT_PRESERVE_ATTRIBUTE_CASE) === 0;
148
148
  const is_input = (flags & ELEMENT_IS_INPUT) !== 0;
149
+ const names = Object.keys(attrs);
149
150
 
150
- for (name of Object.keys(attrs)) {
151
+ outer: for (let i = 0; i < names.length; i++) {
152
+ name = names[i];
151
153
  // omit functions, internal svelte properties and invalid attribute names
152
154
  if (typeof attrs[name] === 'function') continue;
153
155
  if (name[0] === '$' && name[1] === '$') continue; // faster than name.startsWith('$$')
@@ -163,8 +165,13 @@ export function attributes(attrs, css_hash, classes, styles, flags = 0) {
163
165
 
164
166
  if (is_input) {
165
167
  if (name === 'defaultvalue' || name === 'defaultchecked') {
168
+ // value/checked takes precedence over defaultValue/defaultChecked
166
169
  name = name === 'defaultvalue' ? 'value' : 'checked';
167
- if (attrs[name]) continue;
170
+ if (name in attrs) continue;
171
+ // We're checking prior entries as well because "name in attrs" is not enough as the attributes may have different casing
172
+ for (let j = 0; j < names.length; j++) {
173
+ if (names[j].toLowerCase() === name) continue outer;
174
+ }
168
175
  }
169
176
  }
170
177