svelte 5.57.0 → 5.57.1

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 (59) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +8 -8
  3. package/src/compiler/phases/1-parse/acorn.js +36 -2
  4. package/src/compiler/phases/1-parse/index.js +23 -22
  5. package/src/compiler/phases/1-parse/read/context.js +3 -18
  6. package/src/compiler/phases/1-parse/read/expression.js +110 -2
  7. package/src/compiler/phases/1-parse/read/script.js +1 -1
  8. package/src/compiler/phases/1-parse/read/style.js +3 -5
  9. package/src/compiler/phases/1-parse/state/element.js +68 -40
  10. package/src/compiler/phases/1-parse/state/tag.js +6 -6
  11. package/src/compiler/phases/1-parse/utils/html.js +2 -0
  12. package/src/compiler/phases/1-parse/utils/whitespace.js +18 -0
  13. package/src/compiler/phases/2-analyze/css/css-prune.js +7 -6
  14. package/src/compiler/phases/2-analyze/index.js +1 -1
  15. package/src/compiler/phases/2-analyze/visitors/BindDirective.js +1 -1
  16. package/src/compiler/phases/2-analyze/visitors/ClassDeclaration.js +1 -1
  17. package/src/compiler/phases/2-analyze/visitors/FunctionDeclaration.js +1 -1
  18. package/src/compiler/phases/2-analyze/visitors/Identifier.js +1 -2
  19. package/src/compiler/phases/2-analyze/visitors/SvelteBody.js +1 -1
  20. package/src/compiler/phases/2-analyze/visitors/SvelteDocument.js +1 -1
  21. package/src/compiler/phases/2-analyze/visitors/SvelteWindow.js +1 -1
  22. package/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js +2 -2
  23. package/src/compiler/phases/2-analyze/visitors/shared/a11y/constants.js +1 -1
  24. package/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +1 -1
  25. package/src/compiler/phases/2-analyze/visitors/shared/utils.js +0 -38
  26. package/src/compiler/phases/3-transform/client/utils.js +0 -38
  27. package/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +2 -2
  28. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +1 -2
  29. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +3 -8
  30. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +8 -0
  31. package/src/compiler/phases/3-transform/utils.js +1 -1
  32. package/src/compiler/phases/nodes.js +30 -0
  33. package/src/compiler/phases/scope.js +77 -2
  34. package/src/compiler/state.js +9 -5
  35. package/src/compiler/utils/ast.js +2 -5
  36. package/src/compiler/utils/compile_diagnostic.js +1 -1
  37. package/src/compiler/utils/extract_svelte_ignore.js +1 -1
  38. package/src/compiler/utils/mapped_code.js +13 -1
  39. package/src/internal/client/constants.js +0 -9
  40. package/src/internal/client/dev/debug.js +0 -2
  41. package/src/internal/client/dom/elements/events.js +6 -1
  42. package/src/internal/client/errors.js +0 -16
  43. package/src/internal/client/proxy.js +12 -7
  44. package/src/internal/client/reactivity/batch.js +91 -71
  45. package/src/internal/client/reactivity/deriveds.js +0 -3
  46. package/src/internal/client/reactivity/sources.js +28 -16
  47. package/src/internal/client/reactivity/utils.js +2 -23
  48. package/src/internal/client/runtime.js +5 -9
  49. package/src/internal/server/context.js +9 -2
  50. package/src/internal/server/index.js +9 -2
  51. package/src/internal/server/renderer.js +101 -69
  52. package/src/internal/shared/errors.js +16 -0
  53. package/src/version.js +1 -1
  54. package/types/index.d.ts.map +1 -1
  55. package/src/compiler/phases/1-parse/utils/create.js +0 -16
  56. package/src/compiler/phases/2-analyze/visitors/shared/special-element.js +0 -16
  57. package/src/compiler/utils/push_array.js +0 -13
  58. /package/src/compiler/{phases/2-analyze/utils → utils}/check_graph_for_cycles.js +0 -0
  59. /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() {
@@ -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';
@@ -171,6 +168,17 @@ 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
184
  * @param {Source<V>} source
@@ -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';
@@ -100,7 +99,11 @@ export let current_sources = null;
100
99
 
101
100
  /** @param {Value} value */
102
101
  export function push_reaction_value(value) {
103
- if (active_reaction !== null && (!async_mode_flag || (active_reaction.f & DERIVED) !== 0)) {
102
+ if (
103
+ active_reaction !== null &&
104
+ ((!async_mode_flag && (active_reaction.f & REACTION_IS_UPDATING) !== 0) ||
105
+ (active_reaction.f & DERIVED) !== 0)
106
+ ) {
104
107
  (current_sources ??= new Set()).add(value);
105
108
  }
106
109
  }
@@ -160,10 +163,6 @@ export function is_dirty(reaction) {
160
163
  return true;
161
164
  }
162
165
 
163
- if (flags & DERIVED) {
164
- reaction.f &= ~WAS_MARKED;
165
- }
166
-
167
166
  if ((flags & MAYBE_DIRTY) !== 0) {
168
167
  var dependencies = /** @type {Value[]} */ (reaction.deps);
169
168
  var length = dependencies.length;
@@ -404,11 +403,8 @@ function remove_reaction(signal, dependency) {
404
403
  ) {
405
404
  var derived = /** @type {Derived} */ (dependency);
406
405
 
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
406
  if ((derived.f & CONNECTED) !== 0) {
410
407
  derived.f ^= CONNECTED;
411
- derived.f &= ~WAS_MARKED;
412
408
  }
413
409
 
414
410
  // In a fork it's possible that a derived is executed and gets reactions, then commits, but is
@@ -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 aswell 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