svelte 5.56.10 → 5.57.0

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 (81) hide show
  1. package/compiler/index.js +1 -1
  2. package/elements.d.ts +3 -0
  3. package/package.json +3 -3
  4. package/src/compiler/phases/1-parse/acorn.js +1 -1
  5. package/src/compiler/phases/1-parse/utils/html.js +12 -7
  6. package/src/compiler/phases/2-analyze/css/css-prune.js +10 -0
  7. package/src/compiler/phases/2-analyze/index.js +26 -3
  8. package/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +10 -4
  9. package/src/compiler/phases/2-analyze/visitors/CallExpression.js +1 -1
  10. package/src/compiler/phases/2-analyze/visitors/DeclarationTag.js +2 -1
  11. package/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js +10 -10
  12. package/src/compiler/phases/2-analyze/visitors/SvelteBody.js +4 -4
  13. package/src/compiler/phases/2-analyze/visitors/SvelteDocument.js +4 -4
  14. package/src/compiler/phases/2-analyze/visitors/SvelteWindow.js +4 -4
  15. package/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +14 -2
  16. package/src/compiler/phases/2-analyze/visitors/shared/element.js +2 -8
  17. package/src/compiler/phases/2-analyze/visitors/shared/utils.js +23 -1
  18. package/src/compiler/phases/3-transform/client/transform-client.js +2 -2
  19. package/src/compiler/phases/3-transform/client/transform-template/index.js +6 -0
  20. package/src/compiler/phases/3-transform/client/utils.js +35 -8
  21. package/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js +6 -6
  22. package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +10 -13
  23. package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +19 -8
  24. package/src/compiler/phases/3-transform/client/visitors/DeclarationTag.js +3 -2
  25. package/src/compiler/phases/3-transform/client/visitors/EachBlock.js +4 -1
  26. package/src/compiler/phases/3-transform/client/visitors/Fragment.js +2 -7
  27. package/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +2 -1
  28. package/src/compiler/phases/3-transform/client/visitors/IfBlock.js +2 -1
  29. package/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +2 -1
  30. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +106 -44
  31. package/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +2 -1
  32. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +11 -4
  33. package/src/compiler/phases/3-transform/client/visitors/shared/element.js +6 -1
  34. package/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +1 -6
  35. package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +5 -5
  36. package/src/compiler/phases/3-transform/server/transform-server.js +20 -9
  37. package/src/compiler/phases/3-transform/server/visitors/AssignmentExpression.js +1 -1
  38. package/src/compiler/phases/3-transform/server/visitors/DeclarationTag.js +1 -1
  39. package/src/compiler/phases/3-transform/server/visitors/EachBlock.js +8 -2
  40. package/src/compiler/phases/3-transform/server/visitors/IfBlock.js +4 -4
  41. package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +1 -1
  42. package/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +9 -4
  43. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +5 -4
  44. package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +30 -0
  45. package/src/compiler/phases/bindings.js +3 -1
  46. package/src/compiler/phases/nodes.js +4 -0
  47. package/src/compiler/print/index.js +21 -16
  48. package/src/easing/index.js +62 -0
  49. package/src/internal/client/constants.js +9 -0
  50. package/src/internal/client/context.js +15 -5
  51. package/src/internal/client/dev/hmr.js +15 -4
  52. package/src/internal/client/dom/blocks/boundary.js +21 -12
  53. package/src/internal/client/dom/blocks/slot.js +19 -0
  54. package/src/internal/client/dom/blocks/svelte-head.js +8 -0
  55. package/src/internal/client/dom/elements/attributes.js +35 -34
  56. package/src/internal/client/dom/elements/bindings/select.js +99 -9
  57. package/src/internal/client/dom/elements/bindings/this.js +7 -2
  58. package/src/internal/client/dom/elements/transitions.js +81 -68
  59. package/src/internal/client/dom/operations.js +21 -1
  60. package/src/internal/client/error-handling.js +10 -2
  61. package/src/internal/client/index.js +9 -2
  62. package/src/internal/client/proxy.js +8 -3
  63. package/src/internal/client/reactivity/async.js +23 -3
  64. package/src/internal/client/reactivity/effects.js +14 -1
  65. package/src/internal/client/render.js +4 -8
  66. package/src/internal/client/runtime.js +45 -33
  67. package/src/internal/client/warnings.js +0 -11
  68. package/src/internal/server/abort-signal.js +8 -9
  69. package/src/internal/server/context.js +2 -2
  70. package/src/internal/server/index.js +1 -1
  71. package/src/internal/server/renderer.js +163 -52
  72. package/src/internal/shared/attributes.js +2 -1
  73. package/src/internal/shared/clone.js +2 -0
  74. package/src/internal/shared/context.js +3 -2
  75. package/src/legacy/legacy-server.js +1 -1
  76. package/src/reactivity/map.js +22 -0
  77. package/src/reactivity/url.js +2 -0
  78. package/src/transition/index.js +18 -7
  79. package/src/version.js +1 -1
  80. package/types/index.d.ts +135 -37
  81. package/types/index.d.ts.map +2 -2
@@ -3,6 +3,7 @@
3
3
  /** @import { ComponentContext } from '../types' */
4
4
  import { extract_identifiers, has_await_expression } from '../../../../utils/ast.js';
5
5
  import * as b from '#compiler/builders';
6
+ import { async_thunk } from '../utils.js';
6
7
  import { add_state_transformers } from './shared/declarations.js';
7
8
 
8
9
  /**
@@ -70,7 +71,7 @@ export function add_async_declaration(context, metadata, ids, assignments, kind
70
71
  context.state.consts.push(kind === 'var' ? b.var(id.name) : b.let(id.name));
71
72
  }
72
73
 
73
- const blockers = [...metadata.expression.dependencies]
74
+ const blockers = [...metadata.expression.references]
74
75
  .map((dep) => dep.blocker)
75
76
  .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
76
77
 
@@ -85,5 +86,5 @@ export function add_async_declaration(context, metadata, ids, assignments, kind
85
86
  metadata.expression.has_await ||
86
87
  assignments.some((assignment) => has_await_expression(assignment));
87
88
  const body = assignments.length === 1 ? assignments[0].expression : b.block(assignments);
88
- run.thunks.push(b.thunk(body, has_await));
89
+ run.thunks.push(has_await ? async_thunk(body, metadata.expression) : b.thunk(body));
89
90
  }
@@ -12,6 +12,7 @@ import {
12
12
  import { dev } from '../../../../state.js';
13
13
  import { extract_paths, object } from '../../../../utils/ast.js';
14
14
  import * as b from '#compiler/builders';
15
+ import { async_thunk } from '../utils.js';
15
16
  import { get_value } from './shared/declarations.js';
16
17
  import { build_expression, add_svelte_meta } from './shared/utils.js';
17
18
 
@@ -313,7 +314,9 @@ export function EachBlock(node, context) {
313
314
 
314
315
  const has_await = node.metadata.expression.has_await;
315
316
 
316
- const get_collection = b.thunk(collection, has_await);
317
+ const get_collection = has_await
318
+ ? async_thunk(collection, node.metadata.expression)
319
+ : b.thunk(collection);
317
320
  const thunk = has_await ? b.thunk(b.call('$.get', b.id('$$collection'))) : get_collection;
318
321
 
319
322
  const render_args = [b.id('$$anchor'), item];
@@ -141,14 +141,9 @@ export function Fragment(node, context) {
141
141
  flags |= TEMPLATE_USE_IMPORT_NODE;
142
142
  }
143
143
 
144
- if (state.template.nodes.length === 1 && state.template.nodes[0].type === 'comment') {
145
- // special case — we can use `$.comment` instead of creating a unique template
146
- state.init.unshift(b.var(id, b.call('$.comment')));
147
- } else {
148
- const template_name = transform_template(state, 'root', flags);
144
+ const template_name = transform_template(state, 'root', flags);
149
145
 
150
- state.init.unshift(b.var(id, b.call(template_name)));
151
- }
146
+ state.init.unshift(b.var(id, b.call(template_name)));
152
147
 
153
148
  close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
154
149
  }
@@ -2,6 +2,7 @@
2
2
  /** @import { ComponentContext } from '../types' */
3
3
  import { is_ignored } from '../../../../state.js';
4
4
  import * as b from '#compiler/builders';
5
+ import { async_thunk } from '../utils.js';
5
6
  import { build_expression } from './shared/utils.js';
6
7
 
7
8
  /**
@@ -46,7 +47,7 @@ export function HtmlTag(node, context) {
46
47
  '$.async',
47
48
  context.state.node,
48
49
  node.metadata.expression.blockers(),
49
- has_await ? b.array([b.thunk(expression, true)]) : b.void0,
50
+ has_await ? b.array([async_thunk(expression, node.metadata.expression)]) : b.void0,
50
51
  b.arrow(
51
52
  has_await ? [context.state.node, b.id('$$html')] : [context.state.node],
52
53
  b.block([statement])
@@ -2,6 +2,7 @@
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types' */
4
4
  import * as b from '#compiler/builders';
5
+ import { async_thunk } from '../utils.js';
5
6
  import { build_expression, add_svelte_meta } from './shared/utils.js';
6
7
 
7
8
  /**
@@ -117,7 +118,7 @@ export function IfBlock(node, context) {
117
118
  '$.async',
118
119
  context.state.node,
119
120
  node.metadata.expression.blockers(),
120
- has_await ? b.array([b.thunk(expression, true)]) : b.void0,
121
+ has_await ? b.array([async_thunk(expression, node.metadata.expression)]) : b.void0,
121
122
  b.arrow(
122
123
  has_await ? [context.state.node, b.id('$$condition')] : [context.state.node],
123
124
  b.block(statements)
@@ -2,6 +2,7 @@
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types' */
4
4
  import * as b from '#compiler/builders';
5
+ import { async_thunk } from '../utils.js';
5
6
  import { build_expression, add_svelte_meta } from './shared/utils.js';
6
7
 
7
8
  /**
@@ -31,7 +32,7 @@ export function KeyBlock(node, context) {
31
32
  '$.async',
32
33
  context.state.node,
33
34
  node.metadata.expression.blockers(),
34
- has_await ? b.array([b.thunk(expression, true)]) : b.void0,
35
+ has_await ? b.array([async_thunk(expression, node.metadata.expression)]) : b.void0,
35
36
  b.arrow(
36
37
  has_await ? [context.state.node, b.id('$$key')] : [context.state.node],
37
38
  b.block([statement])
@@ -1,10 +1,9 @@
1
- /** @import { ArrayExpression, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression } from 'estree' */
1
+ /** @import { ArrayExpression, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentClientTransformState, ComponentContext } from '../types' */
4
4
  /** @import { Scope } from '../../../scope' */
5
5
  import {
6
6
  cannot_be_set_statically,
7
- is_boolean_attribute,
8
7
  is_dom_property,
9
8
  is_load_error_element
10
9
  } from '../../../../../utils.js';
@@ -13,7 +12,6 @@ import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js'
13
12
  import * as b from '#compiler/builders';
14
13
  import {
15
14
  create_attribute,
16
- ExpressionMetadata,
17
15
  is_custom_element_node,
18
16
  is_customizable_select_element
19
17
  } from '../../../nodes.js';
@@ -231,11 +229,18 @@ export function RegularElement(node, context) {
231
229
  continue;
232
230
  }
233
231
 
232
+ // `<select defaultValue>` needs the options to exist before it can mark one
233
+ // as selected, so it is handled after the children, alongside `value`
234
+ if (node.name === 'select' && get_attribute_name(node, attribute) === 'defaultValue') {
235
+ continue;
236
+ }
237
+
234
238
  const name = get_attribute_name(node, attribute);
235
239
 
236
240
  if (
237
241
  !is_custom_element &&
238
242
  !cannot_be_set_statically(attribute.name) &&
243
+ (name !== 'value' || node.name !== 'textarea') &&
239
244
  (attribute.value === true || is_text_attribute(attribute)) &&
240
245
  (name !== 'class' || class_directives.length === 0) &&
241
246
  (name !== 'style' || style_directives.length === 0)
@@ -434,7 +439,7 @@ export function RegularElement(node, context) {
434
439
  state: child_state
435
440
  });
436
441
 
437
- if (needs_reset) {
442
+ if (needs_reset && !fold_reset_into_child(child_state.init, context.state.node)) {
438
443
  child_state.init.push(b.stmt(b.call('$.reset', context.state.node)));
439
444
  }
440
445
  }
@@ -513,6 +518,34 @@ export function RegularElement(node, context) {
513
518
  }
514
519
  }
515
520
 
521
+ // deferred from the attribute loop above, so that the options it selects from
522
+ // have been created and had their values assigned
523
+ if (!has_spread && name === 'select') {
524
+ const default_value = /** @type {AST.Attribute[]} */ (attributes).find(
525
+ (attribute) => get_attribute_name(node, attribute) === 'defaultValue'
526
+ );
527
+
528
+ if (default_value) {
529
+ const { value, has_state } = build_attribute_value(default_value.value, context, (v, m) =>
530
+ context.state.memoizer.add(v, m)
531
+ );
532
+
533
+ (has_state ? context.state.update : context.state.init).push(
534
+ b.stmt(b.call('$.set_default_select_value', node_id, value))
535
+ );
536
+ }
537
+
538
+ const value_attribute = lookup.get('value');
539
+ const dynamic_value =
540
+ value_attribute !== undefined &&
541
+ value_attribute.value !== true &&
542
+ !is_text_attribute(value_attribute);
543
+
544
+ if (default_value || dynamic_value || bindings.has('value')) {
545
+ context.state.init.push(b.stmt(b.call('$.init_select', node_id)));
546
+ }
547
+ }
548
+
516
549
  context.state.template.pop_element();
517
550
  }
518
551
 
@@ -528,18 +561,12 @@ export function build_class_directives_object(
528
561
  ) {
529
562
  let properties = [];
530
563
 
531
- const metadata = new ExpressionMetadata();
532
-
533
564
  for (const d of class_directives) {
534
- metadata.merge(d.metadata.expression);
535
-
536
565
  const expression = /** @type Expression */ (context.visit(d.expression));
537
- properties.push(b.init(d.name, expression));
566
+ properties.push(b.init(d.name, memoizer.add(expression, d.metadata.expression)));
538
567
  }
539
568
 
540
- const directives = b.object(properties);
541
-
542
- return memoizer.add(directives, metadata);
569
+ return b.object(properties);
543
570
  }
544
571
 
545
572
  /**
@@ -555,23 +582,17 @@ export function build_style_directives_object(
555
582
  const normal = b.object([]);
556
583
  const important = b.object([]);
557
584
 
558
- const metadata = new ExpressionMetadata();
559
-
560
585
  for (const d of style_directives) {
561
- metadata.merge(d.metadata.expression);
562
-
563
586
  const expression =
564
587
  d.value === true
565
588
  ? build_getter(b.id(d.name), context.state)
566
589
  : build_attribute_value(d.value, context).value;
567
590
 
568
591
  const object = d.modifiers.includes('important') ? important : normal;
569
- object.properties.push(b.init(d.name, expression));
592
+ object.properties.push(b.init(d.name, memoizer.add(expression, d.metadata.expression)));
570
593
  }
571
594
 
572
- const directives = important.properties.length ? b.array([normal, important]) : normal;
573
-
574
- return memoizer.add(directives, metadata);
595
+ return important.properties.length ? b.array([normal, important]) : normal;
575
596
  }
576
597
 
577
598
  /**
@@ -715,28 +736,32 @@ function build_element_special_value_attribute(
715
736
  );
716
737
 
717
738
  const evaluated = context.state.scope.evaluate(value);
718
- const assignment = b.assignment('=', b.member(node_id, '__value'), value);
719
739
 
720
- const set_value_assignment = b.assignment(
721
- '=',
722
- b.member(node_id, 'value'),
723
- evaluated.is_defined ? assignment : b.logical('??', assignment, b.literal(''))
724
- );
740
+ /** @param {Expression} value */
741
+ const build_update = (value) => {
742
+ const assignment = b.assignment('=', b.member(node_id, '__value'), value);
725
743
 
726
- const update = b.stmt(
727
- is_select_with_value
728
- ? b.sequence([
729
- set_value_assignment,
730
- // This ensures a one-way street to the DOM in case it's <select {value}>
731
- // and not <select bind:value>. We need it in addition to $.init_select
732
- // because the select value is not reflected as an attribute, so the
733
- // mutation observer wouldn't notice.
734
- b.call('$.select_option', node_id, value)
735
- ])
736
- : synthetic
737
- ? assignment
738
- : set_value_assignment
739
- );
744
+ const set_value_assignment = b.assignment(
745
+ '=',
746
+ b.member(node_id, 'value'),
747
+ evaluated.is_defined ? assignment : b.logical('??', assignment, b.literal(''))
748
+ );
749
+
750
+ return b.stmt(
751
+ is_select_with_value
752
+ ? b.sequence([
753
+ set_value_assignment,
754
+ // This ensures a one-way street to the DOM in case it's <select {value}>
755
+ // and not <select bind:value>. We need it in addition to $.init_select
756
+ // because the select value is not reflected as an attribute, so the
757
+ // mutation observer wouldn't notice.
758
+ b.call('$.select_option', node_id, value)
759
+ ])
760
+ : synthetic
761
+ ? assignment
762
+ : set_value_assignment
763
+ );
764
+ };
740
765
 
741
766
  if (has_state) {
742
767
  const id = b.id(state.scope.generate(`${node_id.name}_value`));
@@ -747,12 +772,49 @@ function build_element_special_value_attribute(
747
772
  const init = element === 'option' ? b.object([]) : undefined;
748
773
 
749
774
  state.init.push(b.var(id, init));
750
- state.update.push(b.if(b.binary('!==', id, b.assignment('=', id, value)), b.block([update])));
775
+
776
+ // the guard already evaluated `value` into `id`, so read that back rather than
777
+ // evaluating the same expression (and its signal reads) a second time
778
+ state.update.push(
779
+ b.if(b.binary('!==', id, b.assignment('=', id, value)), b.block([build_update(id)]))
780
+ );
751
781
  } else {
752
- state.init.push(update);
782
+ state.init.push(build_update(value));
783
+ }
784
+ }
785
+
786
+ /**
787
+ * `<p>{text}</p>` and friends produce `var x = $.child(p, true); $.reset(p);`. That pair is
788
+ * by far the most common shape in compiled output, and `$.only_child` does both, so fold the
789
+ * two together when the `$.child(...)` is the last thing we emitted for this element.
790
+ * @param {Statement[]} init
791
+ * @param {Expression} node_id
792
+ * @returns {boolean} whether the reset was folded in
793
+ */
794
+ function fold_reset_into_child(init, node_id) {
795
+ const last = init.at(-1);
796
+
797
+ if (
798
+ node_id?.type !== 'Identifier' ||
799
+ last?.type !== 'VariableDeclaration' ||
800
+ last.declarations.length !== 1
801
+ ) {
802
+ return false;
753
803
  }
754
804
 
755
- if (is_select_with_value) {
756
- state.init.push(b.stmt(b.call('$.init_select', node_id)));
805
+ const call = last.declarations[0].init;
806
+
807
+ if (
808
+ call?.type !== 'CallExpression' ||
809
+ call.callee.type !== 'Identifier' ||
810
+ call.callee.name !== '$.child' ||
811
+ call.arguments[0]?.type !== 'Identifier' ||
812
+ call.arguments[0].name !== node_id.name
813
+ ) {
814
+ return false;
757
815
  }
816
+
817
+ call.callee = b.id('$.only_child');
818
+
819
+ return true;
758
820
  }
@@ -4,6 +4,7 @@
4
4
  import { dev, locator } from '../../../../state.js';
5
5
  import { is_text_attribute } from '../../../../utils/ast.js';
6
6
  import * as b from '#compiler/builders';
7
+ import { async_thunk } from '../utils.js';
7
8
  import { determine_namespace_for_children } from '../../utils.js';
8
9
  import {
9
10
  build_attribute_value,
@@ -147,7 +148,7 @@ export function SvelteElement(node, context) {
147
148
  '$.async',
148
149
  context.state.node,
149
150
  node.metadata.expression.blockers(),
150
- has_await ? b.array([b.thunk(expression, true)]) : b.void0,
151
+ has_await ? b.array([async_thunk(expression, node.metadata.expression)]) : b.void0,
151
152
  b.arrow(
152
153
  has_await ? [context.state.node, b.id('$$tag')] : [context.state.node],
153
154
  b.block(statements)
@@ -6,7 +6,13 @@ import { extract_paths, save } from '../../../../utils/ast.js';
6
6
  import * as b from '#compiler/builders';
7
7
  import * as assert from '../../../../utils/assert.js';
8
8
  import { get_rune } from '../../../scope.js';
9
- import { get_prop_source, is_prop_source, is_state_source, should_proxy } from '../utils.js';
9
+ import {
10
+ async_thunk,
11
+ get_prop_source,
12
+ is_prop_source,
13
+ is_state_source,
14
+ should_proxy
15
+ } from '../utils.js';
10
16
  import { get_value } from './shared/declarations.js';
11
17
 
12
18
  /**
@@ -200,9 +206,10 @@ export function VariableDeclaration(node, context) {
200
206
  }
201
207
 
202
208
  if (rune === '$derived' || rune === '$derived.by') {
203
- const is_async = context.state.analysis.async_deriveds.has(
209
+ const metadata = context.state.analysis.async_deriveds.get(
204
210
  /** @type {CallExpression} */ (init)
205
211
  );
212
+ const is_async = metadata !== undefined;
206
213
 
207
214
  if (declarator.id.type === 'Identifier') {
208
215
  let expression = /** @type {Expression} */ (context.visit(value));
@@ -213,7 +220,7 @@ export function VariableDeclaration(node, context) {
213
220
  /** @type {Expression} */
214
221
  let call = b.call(
215
222
  '$.async_derived',
216
- b.thunk(expression, true),
223
+ async_thunk(expression, metadata),
217
224
  dev && b.literal(declarator.id.name),
218
225
  location ? b.literal(location) : undefined
219
226
  );
@@ -246,7 +253,7 @@ export function VariableDeclaration(node, context) {
246
253
 
247
254
  call = b.call(
248
255
  '$.async_derived',
249
- b.thunk(expression, true),
256
+ async_thunk(expression, metadata),
250
257
  dev &&
251
258
  b.literal(
252
259
  `[$derived ${declarator.id.type === 'ArrayPattern' ? 'iterable' : 'object'}]`
@@ -30,6 +30,7 @@ export function build_attribute_effect(
30
30
  ) {
31
31
  /** @type {ObjectExpression['properties']} */
32
32
  const values = [];
33
+ const is_select = element.type === 'RegularElement' && element.name === 'select';
33
34
 
34
35
  const memoizer = new Memoizer();
35
36
 
@@ -48,7 +49,11 @@ export function build_attribute_effect(
48
49
  context.state.init.push(b.var(id, value));
49
50
  values.push(b.init(attribute.name, b.id(id)));
50
51
  } else {
51
- values.push(b.init(attribute.name, value));
52
+ const name =
53
+ is_select && normalize_attribute(attribute.name) === 'defaultValue'
54
+ ? 'defaultValue'
55
+ : attribute.name;
56
+ values.push(b.init(name, value));
52
57
  }
53
58
  } else {
54
59
  let value = /** @type {Expression} */ (context.visit(attribute));
@@ -161,7 +161,7 @@ export function is_static_element(node) {
161
161
  }
162
162
 
163
163
  if (
164
- ['input', 'textarea'].includes(node.name) &&
164
+ ['input', 'textarea', 'select'].includes(node.name) &&
165
165
  ['value', 'checked'].includes(attribute.name)
166
166
  ) {
167
167
  return false;
@@ -171,11 +171,6 @@ export function is_static_element(node) {
171
171
  return false;
172
172
  }
173
173
 
174
- // We need to apply src and loading after appending the img to the DOM for lazy loading to work
175
- if (node.name === 'img' && attribute.name === 'loading') {
176
- return false;
177
- }
178
-
179
174
  if (attribute.value !== true && !is_text_attribute(attribute)) {
180
175
  return false;
181
176
  }
@@ -8,7 +8,7 @@ import { sanitize_template_string } from '../../../../../utils/sanitize_template
8
8
  import { regex_is_valid_identifier } from '../../../../patterns.js';
9
9
  import is_reference from 'is-reference';
10
10
  import { dev, is_ignored, locator, component_name } from '../../../../../state.js';
11
- import { build_getter, is_state_source } from '../../utils.js';
11
+ import { async_thunk, build_getter, is_state_source } from '../../utils.js';
12
12
  import { ExpressionMetadata } from '../../../../nodes.js';
13
13
 
14
14
  /**
@@ -16,10 +16,10 @@ import { ExpressionMetadata } from '../../../../nodes.js';
16
16
  * from templates and replacing them with `$0`, `$1` etc
17
17
  */
18
18
  export class Memoizer {
19
- /** @type {Array<{ id: Identifier, expression: Expression }>} */
19
+ /** @type {Array<{ id: Identifier, expression: Expression, metadata: ExpressionMetadata }>} */
20
20
  #sync = [];
21
21
 
22
- /** @type {Array<{ id: Identifier, expression: Expression }>} */
22
+ /** @type {Array<{ id: Identifier, expression: Expression, metadata: ExpressionMetadata }>} */
23
23
  #async = [];
24
24
 
25
25
  /** @type {Set<Expression>} */
@@ -43,7 +43,7 @@ export class Memoizer {
43
43
 
44
44
  const id = b.id('#'); // filled in later
45
45
 
46
- (metadata.has_await ? this.#async : this.#sync).push({ id, expression });
46
+ (metadata.has_await ? this.#async : this.#sync).push({ id, expression, metadata });
47
47
 
48
48
  return id;
49
49
  }
@@ -84,7 +84,7 @@ export class Memoizer {
84
84
  if (this.#async.length === 0) return;
85
85
  // use `b.arrow` rather than `b.thunk` so that deferred async/template effects
86
86
  // always read live bindings rather than a possibly stale snapshot.
87
- return b.array(this.#async.map((memo) => b.arrow([], memo.expression, true)));
87
+ return b.array(this.#async.map((memo) => async_thunk(memo.expression, memo.metadata)));
88
88
  }
89
89
 
90
90
  sync_values() {
@@ -148,7 +148,7 @@ export function server_component(analysis, options) {
148
148
  const legacy_reactive_declarations = [];
149
149
 
150
150
  for (const [node] of analysis.reactive_statements) {
151
- const statement = [...state.legacy_reactive_statements].find(([n]) => n === node);
151
+ const statement = state.legacy_reactive_statements.get(node);
152
152
  if (statement === undefined) {
153
153
  throw new Error('Could not find reactive statement');
154
154
  }
@@ -165,7 +165,7 @@ export function server_component(analysis, options) {
165
165
  }
166
166
  }
167
167
 
168
- instance.body.push(statement[1]);
168
+ instance.body.push(statement);
169
169
  }
170
170
 
171
171
  if (legacy_reactive_declarations.length > 0) {
@@ -210,14 +210,25 @@ export function server_component(analysis, options) {
210
210
  ];
211
211
  }
212
212
 
213
- if (
214
- [...analysis.instance.scope.declarations.values()].some(
215
- (binding) => binding.kind === 'store_sub'
216
- )
217
- ) {
213
+ const store_subs = [...analysis.instance.scope.declarations.values()].filter(
214
+ (binding) => binding.kind === 'store_sub'
215
+ );
216
+
217
+ // a blocked subscription is only created once its promise resolves, so its teardown must wait until the render is done
218
+ const defer_store_teardown = store_subs.some((binding) => binding.blocker);
219
+
220
+ if (store_subs.length > 0) {
218
221
  instance.body.unshift(b.var('$$store_subs'));
222
+
223
+ const unsubscribe = b.if(
224
+ b.id('$$store_subs'),
225
+ b.stmt(b.call('$.unsubscribe_stores', b.id('$$store_subs')))
226
+ );
227
+
219
228
  template.body.push(
220
- b.if(b.id('$$store_subs'), b.stmt(b.call('$.unsubscribe_stores', b.id('$$store_subs'))))
229
+ defer_store_teardown
230
+ ? b.stmt(b.call('$$renderer.on_destroy', b.arrow([], b.block([unsubscribe]))))
231
+ : unsubscribe
221
232
  );
222
233
  }
223
234
 
@@ -257,7 +268,7 @@ export function server_component(analysis, options) {
257
268
  );
258
269
  }
259
270
 
260
- let should_inject_context = dev || analysis.needs_context;
271
+ let should_inject_context = dev || analysis.needs_context || defer_store_teardown;
261
272
 
262
273
  if (should_inject_context) {
263
274
  component_block = b.block([
@@ -110,7 +110,7 @@ function build_assignment(operator, left, right, context) {
110
110
  context.visit(build_assignment_value(operator, left, right))
111
111
  );
112
112
 
113
- return b.call(binding.node, value);
113
+ return b.call(object, value);
114
114
  }
115
115
 
116
116
  return null;
@@ -66,7 +66,7 @@ export function add_async_declaration(context, metadata, ids, assignments, kind
66
66
  context.state.init.push(kind === 'var' ? b.var(id.name) : b.let(id.name));
67
67
  }
68
68
 
69
- const blockers = [...metadata.expression.dependencies]
69
+ const blockers = [...metadata.expression.references]
70
70
  .map((dep) => dep.blocker)
71
71
  .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
72
72
 
@@ -2,7 +2,13 @@
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types.js' */
4
4
  import * as b from '#compiler/builders';
5
- import { block_close, block_open, block_open_else, create_child_block } from './shared/utils.js';
5
+ import {
6
+ block_close,
7
+ block_open,
8
+ block_open_else,
9
+ create_child_block,
10
+ prepend_block_marker
11
+ } from './shared/utils.js';
6
12
 
7
13
  /**
8
14
  * @param {AST.EachBlock} node
@@ -51,7 +57,7 @@ export function EachBlock(node, context) {
51
57
 
52
58
  const fallback = /** @type {BlockStatement} */ (context.visit(node.fallback));
53
59
 
54
- fallback.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), block_open_else)));
60
+ prepend_block_marker(fallback, /** @type {string} */ (block_open_else.value));
55
61
 
56
62
  statements.push(
57
63
  b.if(
@@ -2,7 +2,7 @@
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types.js' */
4
4
  import * as b from '#compiler/builders';
5
- import { block_close, create_child_block } from './shared/utils.js';
5
+ import { block_close, create_child_block, prepend_block_marker } from './shared/utils.js';
6
6
 
7
7
  /**
8
8
  * @param {AST.IfBlock} node
@@ -10,7 +10,7 @@ import { block_close, create_child_block } from './shared/utils.js';
10
10
  */
11
11
  export function IfBlock(node, context) {
12
12
  const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent));
13
- consequent.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), b.literal(`<!--[0-->`))));
13
+ prepend_block_marker(consequent, `<!--[0-->`);
14
14
 
15
15
  /** @type {IfStatement} */
16
16
  let if_statement = b.if(/** @type {Expression} */ (context.visit(node.test)), consequent);
@@ -22,7 +22,7 @@ export function IfBlock(node, context) {
22
22
  // Walk the else-if chain, flattening branches
23
23
  for (const elseif of node.metadata.flattened ?? []) {
24
24
  const branch = /** @type {BlockStatement} */ (context.visit(elseif.consequent));
25
- branch.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), b.literal(`<!--[${index++}-->`))));
25
+ prepend_block_marker(branch, `<!--[${index++}-->`);
26
26
 
27
27
  current_if = current_if.alternate = b.if(
28
28
  /** @type {Expression} */ (context.visit(elseif.test)),
@@ -34,7 +34,7 @@ export function IfBlock(node, context) {
34
34
  // Handle final else (or remaining async chain)
35
35
  const final_alternate = alt ? /** @type {BlockStatement} */ (context.visit(alt)) : b.block([]);
36
36
 
37
- final_alternate.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), b.literal(`<!--[-1-->`))));
37
+ prepend_block_marker(final_alternate, `<!--[-1-->`);
38
38
  current_if.alternate = final_alternate;
39
39
 
40
40
  context.state.template.push(
@@ -46,7 +46,7 @@ export function RegularElement(node, context) {
46
46
  node.attributes.some(
47
47
  (attribute) =>
48
48
  ((attribute.type === 'Attribute' || attribute.type === 'BindDirective') &&
49
- attribute.name === 'value') ||
49
+ (attribute.name === 'value' || attribute.name.toLowerCase() === 'defaultvalue')) ||
50
50
  attribute.type === 'SpreadAttribute'
51
51
  );
52
52
  const is_option_special = name === 'option';