svelte 5.56.9 → 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 (94) 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/migrate/index.js +6 -1
  5. package/src/compiler/phases/1-parse/acorn.js +1 -1
  6. package/src/compiler/phases/1-parse/read/style.js +16 -6
  7. package/src/compiler/phases/1-parse/utils/html.js +12 -7
  8. package/src/compiler/phases/2-analyze/css/css-prune.js +10 -0
  9. package/src/compiler/phases/2-analyze/index.js +45 -18
  10. package/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +10 -4
  11. package/src/compiler/phases/2-analyze/visitors/CallExpression.js +1 -1
  12. package/src/compiler/phases/2-analyze/visitors/DeclarationTag.js +2 -1
  13. package/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js +10 -10
  14. package/src/compiler/phases/2-analyze/visitors/SvelteBody.js +4 -4
  15. package/src/compiler/phases/2-analyze/visitors/SvelteDocument.js +4 -4
  16. package/src/compiler/phases/2-analyze/visitors/SvelteWindow.js +4 -4
  17. package/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +14 -2
  18. package/src/compiler/phases/2-analyze/visitors/shared/element.js +2 -8
  19. package/src/compiler/phases/2-analyze/visitors/shared/utils.js +23 -1
  20. package/src/compiler/phases/3-transform/client/transform-client.js +2 -2
  21. package/src/compiler/phases/3-transform/client/transform-template/index.js +6 -0
  22. package/src/compiler/phases/3-transform/client/utils.js +35 -8
  23. package/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +16 -3
  24. package/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js +6 -6
  25. package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +10 -13
  26. package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +19 -8
  27. package/src/compiler/phases/3-transform/client/visitors/DeclarationTag.js +3 -2
  28. package/src/compiler/phases/3-transform/client/visitors/EachBlock.js +4 -1
  29. package/src/compiler/phases/3-transform/client/visitors/Fragment.js +2 -7
  30. package/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +2 -1
  31. package/src/compiler/phases/3-transform/client/visitors/IfBlock.js +2 -1
  32. package/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +2 -1
  33. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +119 -46
  34. package/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +2 -1
  35. package/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js +0 -1
  36. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +11 -4
  37. package/src/compiler/phases/3-transform/client/visitors/shared/element.js +6 -1
  38. package/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +1 -6
  39. package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +5 -5
  40. package/src/compiler/phases/3-transform/css/index.js +5 -1
  41. package/src/compiler/phases/3-transform/server/transform-server.js +20 -9
  42. package/src/compiler/phases/3-transform/server/visitors/AssignmentExpression.js +1 -1
  43. package/src/compiler/phases/3-transform/server/visitors/DeclarationTag.js +1 -1
  44. package/src/compiler/phases/3-transform/server/visitors/EachBlock.js +8 -2
  45. package/src/compiler/phases/3-transform/server/visitors/IfBlock.js +4 -4
  46. package/src/compiler/phases/3-transform/server/visitors/LabeledStatement.js +1 -0
  47. package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +1 -1
  48. package/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +9 -4
  49. package/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js +4 -2
  50. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +53 -47
  51. package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +31 -1
  52. package/src/compiler/phases/bindings.js +3 -1
  53. package/src/compiler/phases/nodes.js +4 -0
  54. package/src/compiler/print/index.js +95 -24
  55. package/src/easing/index.js +62 -0
  56. package/src/internal/client/constants.js +9 -0
  57. package/src/internal/client/context.js +33 -56
  58. package/src/internal/client/dev/hmr.js +15 -4
  59. package/src/internal/client/dom/blocks/boundary.js +21 -12
  60. package/src/internal/client/dom/blocks/css-props.js +3 -3
  61. package/src/internal/client/dom/blocks/slot.js +19 -0
  62. package/src/internal/client/dom/blocks/svelte-head.js +8 -0
  63. package/src/internal/client/dom/css.js +6 -1
  64. package/src/internal/client/dom/elements/attributes.js +37 -35
  65. package/src/internal/client/dom/elements/bindings/select.js +99 -9
  66. package/src/internal/client/dom/elements/bindings/this.js +7 -2
  67. package/src/internal/client/dom/elements/events.js +19 -1
  68. package/src/internal/client/dom/elements/transitions.js +81 -68
  69. package/src/internal/client/dom/operations.js +21 -1
  70. package/src/internal/client/error-handling.js +10 -2
  71. package/src/internal/client/index.js +9 -2
  72. package/src/internal/client/proxy.js +8 -3
  73. package/src/internal/client/reactivity/async.js +23 -3
  74. package/src/internal/client/reactivity/batch.js +1 -0
  75. package/src/internal/client/reactivity/effects.js +14 -1
  76. package/src/internal/client/reactivity/sources.js +7 -1
  77. package/src/internal/client/render.js +4 -8
  78. package/src/internal/client/runtime.js +45 -33
  79. package/src/internal/client/warnings.js +0 -11
  80. package/src/internal/server/abort-signal.js +8 -9
  81. package/src/internal/server/context.js +9 -48
  82. package/src/internal/server/index.js +1 -1
  83. package/src/internal/server/renderer.js +164 -52
  84. package/src/internal/shared/attributes.js +4 -2
  85. package/src/internal/shared/clone.js +2 -0
  86. package/src/internal/shared/context.js +53 -0
  87. package/src/internal/shared/errors.js +2 -2
  88. package/src/legacy/legacy-server.js +1 -1
  89. package/src/reactivity/map.js +22 -0
  90. package/src/reactivity/url.js +2 -0
  91. package/src/transition/index.js +18 -7
  92. package/src/version.js +1 -1
  93. package/types/index.d.ts +148 -46
  94. package/types/index.d.ts.map +2 -2
@@ -2,6 +2,7 @@
2
2
  /** @import { Binding } from '#compiler' */
3
3
  /** @import { ClientTransformState, ComponentClientTransformState } from './types.js' */
4
4
  /** @import { Analysis } from '../../types.js' */
5
+ /** @import { ExpressionMetadata } from '../../nodes.js' */
5
6
  /** @import { Scope } from '../../scope.js' */
6
7
  import * as b from '#compiler/builders';
7
8
  import { is_simple_expression, save } from '../../../utils/ast.js';
@@ -164,20 +165,46 @@ export function should_proxy(node, scope) {
164
165
  return true;
165
166
  }
166
167
 
168
+ /**
169
+ * An async thunk. If an `await` inside restores the reaction context via `$.save`,
170
+ * the body exits through `$.unsave` so the context cannot leak into foreign microtasks
171
+ * that run before the returned promise settles
172
+ * @param {Expression | BlockStatement} body
173
+ * @param {ExpressionMetadata} metadata
174
+ */
175
+ export function async_thunk(body, metadata) {
176
+ if (!metadata.has_pickled_await) {
177
+ return b.arrow([], body, true);
178
+ }
179
+
180
+ const block = body.type === 'BlockStatement' ? body : b.block([b.return(body)]);
181
+
182
+ return b.arrow(
183
+ [],
184
+ b.block([
185
+ {
186
+ type: 'TryStatement',
187
+ block,
188
+ handler: null,
189
+ finalizer: b.block([b.stmt(b.call('$.unsave'))])
190
+ }
191
+ ]),
192
+ true
193
+ );
194
+ }
195
+
167
196
  /**
168
197
  * Svelte legacy mode should use safe equals in most places, runes mode shouldn't
169
198
  * @param {ComponentClientTransformState} state
170
199
  * @param {Expression | BlockStatement} expression
171
- * @param {boolean} [async]
200
+ * @param {ExpressionMetadata} [metadata]
172
201
  */
173
- export function create_derived(state, expression, async = false) {
174
- const thunk = b.thunk(expression, async);
175
-
176
- if (async) {
177
- return save(b.call('$.async_derived', thunk));
178
- } else {
179
- return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', thunk);
202
+ export function create_derived(state, expression, metadata) {
203
+ if (metadata?.has_await) {
204
+ return save(b.call('$.async_derived', async_thunk(expression, metadata)));
180
205
  }
206
+
207
+ return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', b.thunk(expression));
181
208
  }
182
209
 
183
210
  /**
@@ -1,4 +1,4 @@
1
- /** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Pattern } from 'estree' */
1
+ /** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, LogicalOperator, Pattern } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { Context } from '../types.js' */
4
4
  import * as b from '#compiler/builders';
@@ -79,8 +79,13 @@ function build_assignment(operator, left, right, context) {
79
79
 
80
80
  // special case — assignment to private state field
81
81
  if (left.property.type === 'PrivateIdentifier') {
82
+ const logical_operator = ['||=', '&&=', '??='].includes(operator)
83
+ ? /** @type {LogicalOperator} */ (operator.slice(0, -1))
84
+ : null;
82
85
  let value = /** @type {Expression} */ (
83
- context.visit(build_assignment_value(operator, left, right))
86
+ context.visit(
87
+ logical_operator === null ? build_assignment_value(operator, left, right) : right
88
+ )
84
89
  );
85
90
 
86
91
  const needs_proxy =
@@ -88,7 +93,15 @@ function build_assignment(operator, left, right, context) {
88
93
  is_non_coercive_operator(operator) &&
89
94
  should_proxy(value, context.state.scope);
90
95
 
91
- return b.call('$.set', left, value, needs_proxy && b.true);
96
+ const assignment = b.call('$.set', left, value, needs_proxy && b.true);
97
+
98
+ return logical_operator === null
99
+ ? assignment
100
+ : b.logical(
101
+ logical_operator,
102
+ /** @type {Expression} */ (context.visit(left)),
103
+ assignment
104
+ );
92
105
  }
93
106
  }
94
107
  }
@@ -1,9 +1,9 @@
1
1
  /** @import { BlockStatement, Pattern, Statement } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentClientTransformState, ComponentContext } from '../types' */
4
- import { extract_identifiers, is_expression_async } from '../../../../utils/ast.js';
4
+ import { extract_identifiers } from '../../../../utils/ast.js';
5
5
  import * as b from '#compiler/builders';
6
- import { create_derived } from '../utils.js';
6
+ import { async_thunk, create_derived } from '../utils.js';
7
7
  import { get_value } from './shared/declarations.js';
8
8
  import { build_expression, add_svelte_meta } from './shared/utils.js';
9
9
 
@@ -15,10 +15,10 @@ export function AwaitBlock(node, context) {
15
15
  context.state.template.push_comment();
16
16
 
17
17
  // Visit {#await <expression>} first to ensure that scopes are in the correct order
18
- const expression = b.thunk(
19
- build_expression(context, node.expression, node.metadata.expression),
20
- node.metadata.expression.has_await
21
- );
18
+ const input = build_expression(context, node.expression, node.metadata.expression);
19
+ const expression = node.metadata.expression.has_await
20
+ ? async_thunk(input, node.metadata.expression)
21
+ : b.thunk(input);
22
22
 
23
23
  let then_block;
24
24
  let catch_block;
@@ -1,7 +1,7 @@
1
1
  /** @import { CallExpression, Expression, Pattern } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types' */
4
- import { dev, is_ignored } from '../../../../state.js';
4
+ import { dev, ignore_map, is_ignored } from '../../../../state.js';
5
5
  import { is_text_attribute } from '../../../../utils/ast.js';
6
6
  import * as b from '#compiler/builders';
7
7
  import { binding_properties } from '../../../bindings.js';
@@ -40,9 +40,15 @@ export function BindDirective(node, context) {
40
40
  validate_binding(context.state, node, expression);
41
41
  }
42
42
 
43
- const assignment = /** @type {Expression} */ (
44
- context.visit(b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value')))
43
+ const raw_assignment = b.assignment(
44
+ '=',
45
+ /** @type {Pattern} */ (node.expression),
46
+ b.id('$$value')
45
47
  );
48
+ // The assignment is generated, so inherit any ignores attached to the binding
49
+ ignore_map.set(raw_assignment, ignore_map.get(node) ?? []);
50
+
51
+ const assignment = /** @type {Expression} */ (context.visit(raw_assignment));
46
52
 
47
53
  if (dev) {
48
54
  // in dev, create named functions, so that `$inspect(...)` delivers
@@ -58,16 +64,7 @@ export function BindDirective(node, context) {
58
64
  get = b.thunk(expression);
59
65
 
60
66
  /** @type {Expression | undefined} */
61
- set = b.unthunk(
62
- b.arrow(
63
- [b.id('$$value')],
64
- /** @type {Expression} */ (
65
- context.visit(
66
- b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value'))
67
- )
68
- )
69
- )
70
- );
67
+ set = b.unthunk(b.arrow([b.id('$$value')], assignment));
71
68
 
72
69
  if (get === set) {
73
70
  set = undefined;
@@ -19,7 +19,7 @@ export function ConstTag(node, context) {
19
19
  if (declaration.id.type === 'Identifier') {
20
20
  const init = build_expression(context, declaration.init, node.metadata.expression);
21
21
 
22
- let expression = create_derived(context.state, init, node.metadata.expression.has_await);
22
+ let expression = create_derived(context.state, init, node.metadata.expression);
23
23
 
24
24
  if (dev) {
25
25
  expression = b.call('$.tag', expression, b.literal(declaration.id.name));
@@ -45,20 +45,31 @@ export function ConstTag(node, context) {
45
45
  transform
46
46
  });
47
47
 
48
- // TODO optimise the simple `{ x } = y` case — we can just return `y`
49
- // instead of destructuring it only to return a new object
48
+ const is_simple_object_pattern =
49
+ declaration.id.type === 'ObjectPattern' &&
50
+ declaration.id.properties.every(
51
+ (p) =>
52
+ p.type === 'Property' &&
53
+ !p.computed &&
54
+ p.key.type === 'Identifier' &&
55
+ p.value.type === 'Identifier' &&
56
+ p.key.name === p.value.name
57
+ );
58
+
50
59
  const init = build_expression(
51
60
  { ...context, state: child_state },
52
61
  declaration.init,
53
62
  node.metadata.expression
54
63
  );
55
64
 
56
- const block = b.block([
57
- b.const(/** @type {Pattern} */ (context.visit(declaration.id, child_state)), init),
58
- b.return(b.object(identifiers.map((node) => b.prop('init', node, node))))
59
- ]);
65
+ const block = is_simple_object_pattern
66
+ ? b.block([b.return(init)])
67
+ : b.block([
68
+ b.const(/** @type {Pattern} */ (context.visit(declaration.id, child_state)), init),
69
+ b.return(b.object(identifiers.map((node) => b.prop('init', node, node))))
70
+ ]);
60
71
 
61
- let expression = create_derived(context.state, block, node.metadata.expression.has_await);
72
+ let expression = create_derived(context.state, block, node.metadata.expression);
62
73
 
63
74
  if (dev) {
64
75
  expression = b.call('$.tag', expression, b.literal('[@const]'));
@@ -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
  /**
@@ -664,14 +685,25 @@ function build_element_attribute_update(element, node_id, name, value, attribute
664
685
  * @param {ComponentContext} context
665
686
  */
666
687
  function build_custom_element_attribute_update_assignment(node_id, attribute, context) {
667
- const { value, has_state } = build_attribute_value(attribute.value, context);
688
+ const memoizer = new Memoizer();
689
+ const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
690
+ memoizer.add(value, metadata)
691
+ );
668
692
 
669
693
  // don't lowercase name, as we set the element's property, which might be case sensitive
670
694
  const call = b.call('$.set_custom_element_data', node_id, b.literal(attribute.name), value);
671
695
 
672
696
  // this is different from other updates — it doesn't get grouped,
673
697
  // because set_custom_element_data may not be idempotent
674
- const update = has_state ? b.call('$.template_effect', b.thunk(call)) : call;
698
+ const update = has_state
699
+ ? b.call(
700
+ '$.template_effect',
701
+ b.arrow(memoizer.apply(), call),
702
+ memoizer.sync_values(),
703
+ memoizer.async_values(),
704
+ memoizer.blockers()
705
+ )
706
+ : call;
675
707
 
676
708
  context.state.init.push(b.stmt(update));
677
709
  }
@@ -704,28 +736,32 @@ function build_element_special_value_attribute(
704
736
  );
705
737
 
706
738
  const evaluated = context.state.scope.evaluate(value);
707
- const assignment = b.assignment('=', b.member(node_id, '__value'), value);
708
739
 
709
- const set_value_assignment = b.assignment(
710
- '=',
711
- b.member(node_id, 'value'),
712
- evaluated.is_defined ? assignment : b.logical('??', assignment, b.literal(''))
713
- );
740
+ /** @param {Expression} value */
741
+ const build_update = (value) => {
742
+ const assignment = b.assignment('=', b.member(node_id, '__value'), value);
714
743
 
715
- const update = b.stmt(
716
- is_select_with_value
717
- ? b.sequence([
718
- set_value_assignment,
719
- // This ensures a one-way street to the DOM in case it's <select {value}>
720
- // and not <select bind:value>. We need it in addition to $.init_select
721
- // because the select value is not reflected as an attribute, so the
722
- // mutation observer wouldn't notice.
723
- b.call('$.select_option', node_id, value)
724
- ])
725
- : synthetic
726
- ? assignment
727
- : set_value_assignment
728
- );
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
+ };
729
765
 
730
766
  if (has_state) {
731
767
  const id = b.id(state.scope.generate(`${node_id.name}_value`));
@@ -736,12 +772,49 @@ function build_element_special_value_attribute(
736
772
  const init = element === 'option' ? b.object([]) : undefined;
737
773
 
738
774
  state.init.push(b.var(id, init));
739
- 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
+ );
740
781
  } else {
741
- 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;
742
803
  }
743
804
 
744
- if (is_select_with_value) {
745
- 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;
746
815
  }
816
+
817
+ call.callee = b.id('$.only_child');
818
+
819
+ return true;
747
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)
@@ -13,7 +13,6 @@ export function UpdateExpression(node, context) {
13
13
 
14
14
  if (
15
15
  argument.type === 'MemberExpression' &&
16
- argument.object.type === 'ThisExpression' &&
17
16
  argument.property.type === 'PrivateIdentifier' &&
18
17
  context.state.state_fields.has('#' + argument.property.name)
19
18
  ) {
@@ -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'}]`