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
@@ -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() {
@@ -355,7 +355,11 @@ const visitors = {
355
355
  continue;
356
356
  }
357
357
 
358
- if (selector.type === 'TypeSelector' && selector.name === '*') {
358
+ if (
359
+ selector.type === 'TypeSelector' &&
360
+ selector.name === '*' &&
361
+ selector.namespace === undefined
362
+ ) {
359
363
  context.state.code.update(selector.start, selector.end, modifier);
360
364
  } else {
361
365
  context.state.code.appendLeft(selector.end, modifier);
@@ -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(
@@ -8,6 +8,7 @@ import * as b from '#compiler/builders';
8
8
  */
9
9
  export function LabeledStatement(node, context) {
10
10
  if (context.state.analysis.runes || context.path.length > 1 || node.label.name !== '$') {
11
+ context.next();
11
12
  return;
12
13
  }
13
14
 
@@ -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';
@@ -1,4 +1,4 @@
1
- /** @import { BlockStatement } from 'estree' */
1
+ /** @import { BlockStatement, Statement } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types' */
4
4
  import * as b from '#compiler/builders';
@@ -84,6 +84,9 @@ export function SvelteBoundary(node, context) {
84
84
  }
85
85
 
86
86
  const props = b.object([]);
87
+ /** @type {Statement[]} */
88
+ const init = [];
89
+
87
90
  if (failed_attribute && !failed_snippet) {
88
91
  const failed_callee = build_attribute_value(
89
92
  failed_attribute.value,
@@ -95,13 +98,15 @@ export function SvelteBoundary(node, context) {
95
98
 
96
99
  props.properties.push(b.init('failed', failed_callee));
97
100
  } else if (failed_snippet) {
98
- context.visit(failed_snippet, context.state);
101
+ context.visit(failed_snippet, { ...context.state, init });
99
102
  props.properties.push(b.init('failed', failed_snippet.expression));
100
103
  }
101
104
 
102
- context.state.template.push(
103
- b.stmt(b.call('$$renderer.boundary', props, b.arrow([b.id('$$renderer')], children_body)))
105
+ const boundary = b.stmt(
106
+ b.call('$$renderer.boundary', props, b.arrow([b.id('$$renderer')], children_body))
104
107
  );
108
+
109
+ context.state.template.push(init.length > 0 ? b.block([...init, boundary]) : boundary);
105
110
  }
106
111
 
107
112
  /**
@@ -102,9 +102,11 @@ export function VariableDeclaration(node, context) {
102
102
  } else {
103
103
  const call = /** @type {CallExpression} */ (declarator.init);
104
104
 
105
- let rhs = value;
105
+ // - cannot be a SpreadElement because refused during analysis
106
+ // - use args[0] rather than value to avoid visiting twice (above in const value = ... and below in for-ofs)
107
+ let rhs = /** @type {Expression} */ (call.arguments[0]);
106
108
 
107
- if (rune !== '$derived' || call.arguments[0].type !== 'Identifier') {
109
+ if (rune === '$derived.by' || call.arguments[0].type !== 'Identifier') {
108
110
  const id = b.id(context.state.scope.generate('$$d'));
109
111
 
110
112
  rhs = b.call(id);
@@ -3,7 +3,7 @@
3
3
  /** @import { ComponentContext, ComponentServerTransformState } from '../../types.js' */
4
4
  import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js';
5
5
  import { binding_properties } from '../../../../bindings.js';
6
- import { create_attribute, ExpressionMetadata, is_custom_element_node } from '../../../../nodes.js';
6
+ import { ExpressionMetadata, is_custom_element_node } from '../../../../nodes.js';
7
7
  import { regex_starts_with_newline } from '../../../../patterns.js';
8
8
  import * as b from '#compiler/builders';
9
9
  import {
@@ -21,6 +21,11 @@ import { escape_html } from '../../../../../../escaping.js';
21
21
 
22
22
  const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
23
23
 
24
+ /**
25
+ * @typedef {{ type: 'transformed', name: string, expression: Expression }} TransformedAttribute
26
+ * An attribute whose expression has already been transformed and must not be visited again.
27
+ */
28
+
24
29
  /**
25
30
  * Writes the output to the template output. Some elements may have attributes on them that require the
26
31
  * their output to be the child content instead. In this case, an object is returned.
@@ -29,7 +34,7 @@ const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
29
34
  * @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
30
35
  */
31
36
  export function build_element_attributes(node, context, transform) {
32
- /** @type {Array<AST.Attribute | AST.SpreadAttribute>} */
37
+ /** @type {Array<AST.Attribute | AST.SpreadAttribute | TransformedAttribute>} */
33
38
  const attributes = [];
34
39
 
35
40
  /** @type {AST.ClassDirective[]} */
@@ -145,42 +150,26 @@ export function build_element_attributes(node, context, transform) {
145
150
  attr.value[0].data === 'checkbox'
146
151
  );
147
152
 
148
- attributes.push(
149
- create_attribute('checked', null, -1, -1, [
150
- {
151
- type: 'ExpressionTag',
152
- start: -1,
153
- end: -1,
154
- expression: is_checkbox
155
- ? b.call(
156
- b.member(attribute.expression, 'includes'),
157
- build_attribute_value(value_attribute.value, context, transform)
158
- )
159
- : b.binary(
160
- '===',
161
- attribute.expression,
162
- build_attribute_value(value_attribute.value, context, transform)
163
- ),
164
- metadata: {
165
- expression: new ExpressionMetadata()
166
- }
167
- }
168
- ])
169
- );
153
+ attributes.push({
154
+ type: 'transformed',
155
+ name: 'checked',
156
+ expression: is_checkbox
157
+ ? b.call(
158
+ b.member(expression, 'includes'),
159
+ build_attribute_value(value_attribute.value, context, transform)
160
+ )
161
+ : b.binary(
162
+ '===',
163
+ expression,
164
+ build_attribute_value(value_attribute.value, context, transform)
165
+ )
166
+ });
170
167
  } else {
171
- attributes.push(
172
- create_attribute(attribute.name, null, -1, -1, [
173
- {
174
- type: 'ExpressionTag',
175
- start: -1,
176
- end: -1,
177
- expression,
178
- metadata: {
179
- expression: new ExpressionMetadata()
180
- }
181
- }
182
- ])
183
- );
168
+ attributes.push({
169
+ type: 'transformed',
170
+ name: get_attribute_name(node, attribute),
171
+ expression
172
+ });
184
173
  }
185
174
  } else if (attribute.type === 'SpreadAttribute') {
186
175
  attributes.push(attribute);
@@ -217,7 +206,21 @@ export function build_element_attributes(node, context, transform) {
217
206
  } else {
218
207
  const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null;
219
208
 
220
- for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
209
+ for (const attribute of /** @type {Array<AST.Attribute | TransformedAttribute>} */ (
210
+ attributes
211
+ )) {
212
+ if (attribute.type === 'transformed') {
213
+ context.state.template.push(
214
+ b.call(
215
+ '$.attr',
216
+ b.literal(attribute.name),
217
+ attribute.expression,
218
+ is_boolean_attribute(attribute.name) && b.true
219
+ )
220
+ );
221
+ continue;
222
+ }
223
+
221
224
  const name = get_attribute_name(node, attribute);
222
225
  const can_use_literal =
223
226
  (name !== 'class' || class_directives.length === 0) &&
@@ -298,15 +301,19 @@ function get_attribute_name(element, attribute) {
298
301
 
299
302
  /**
300
303
  * @param {AST.RegularElement | AST.SvelteElement} element
301
- * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} attributes
304
+ * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective | TransformedAttribute>} attributes
302
305
  * @param {ComponentContext} context
303
306
  * @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
304
307
  */
305
308
  export function build_spread_object(element, attributes, context, transform) {
309
+ const is_select = element.type === 'RegularElement' && element.name === 'select';
306
310
  const object = b.object(
307
311
  attributes.map((attribute) => {
308
- if (attribute.type === 'Attribute') {
309
- const name = get_attribute_name(element, attribute);
312
+ if (attribute.type === 'transformed') {
313
+ return b.prop('init', b.key(attribute.name), attribute.expression);
314
+ } else if (attribute.type === 'Attribute') {
315
+ let name = get_attribute_name(element, attribute);
316
+ if (is_select && name === 'defaultvalue') name = 'defaultValue';
310
317
  const value = build_attribute_value(
311
318
  attribute.value,
312
319
  context,
@@ -317,10 +324,9 @@ export function build_spread_object(element, attributes, context, transform) {
317
324
  return b.prop('init', b.key(name), value);
318
325
  } else if (attribute.type === 'BindDirective') {
319
326
  const name = get_attribute_name(element, attribute);
327
+ const expression = /** @type {Expression} */ (context.visit(attribute.expression));
320
328
  const value =
321
- attribute.expression.type === 'SequenceExpression'
322
- ? b.call(attribute.expression.expressions[0])
323
- : /** @type {Expression} */ (context.visit(attribute.expression));
329
+ expression.type === 'SequenceExpression' ? b.call(expression.expressions[0]) : expression;
324
330
 
325
331
  return b.prop('init', b.key(name), value);
326
332
  }
@@ -340,7 +346,7 @@ export function build_spread_object(element, attributes, context, transform) {
340
346
  /**
341
347
  *
342
348
  * @param {AST.RegularElement | AST.SvelteElement} element
343
- * @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
349
+ * @param {Array<AST.Attribute | AST.SpreadAttribute | TransformedAttribute>} attributes
344
350
  * @param {AST.StyleDirective[]} style_directives
345
351
  * @param {AST.ClassDirective[]} class_directives
346
352
  * @param {ComponentContext} context
@@ -356,7 +362,7 @@ function build_element_spread_attributes(
356
362
  ) {
357
363
  const args = prepare_element_spread(
358
364
  element,
359
- /** @type {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} */ (attributes),
365
+ attributes,
360
366
  style_directives,
361
367
  class_directives,
362
368
  context,
@@ -410,7 +416,7 @@ export function prepare_element_spread_object(element, context, transform) {
410
416
  /**
411
417
  * Prepare args for $.attributes(...): compute object, css_hash, classes, styles and flags.
412
418
  * @param {AST.RegularElement | AST.SvelteElement} element
413
- * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} attributes
419
+ * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective | TransformedAttribute>} attributes
414
420
  * @param {AST.StyleDirective[]} style_directives
415
421
  * @param {AST.ClassDirective[]} class_directives
416
422
  * @param {ComponentContext} context
@@ -180,6 +180,36 @@ export function build_template(template) {
180
180
  return statements;
181
181
  }
182
182
 
183
+ /**
184
+ * Prepends a hydration marker (e.g. `<!--[0-->`) to a branch. The branch has already been
185
+ * turned into statements by `build_template`, so if it happens to start with a static
186
+ * `$$renderer.push(...)` we fold the marker into that call rather than emitting a second one.
187
+ * @param {BlockStatement} block
188
+ * @param {string} marker
189
+ */
190
+ export function prepend_block_marker(block, marker) {
191
+ const first = block.body[0];
192
+
193
+ if (
194
+ first?.type === 'ExpressionStatement' &&
195
+ first.expression.type === 'CallExpression' &&
196
+ first.expression.callee.type === 'Identifier' &&
197
+ first.expression.callee.name === '$$renderer.push' &&
198
+ first.expression.arguments.length === 1 &&
199
+ first.expression.arguments[0].type === 'TemplateLiteral'
200
+ ) {
201
+ const quasi = first.expression.arguments[0].quasis[0];
202
+
203
+ // markers never contain characters that need escaping in a template literal
204
+ quasi.value.cooked = marker + quasi.value.cooked;
205
+ quasi.value.raw = marker + quasi.value.raw;
206
+
207
+ return;
208
+ }
209
+
210
+ block.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), b.literal(marker))));
211
+ }
212
+
183
213
  /**
184
214
  *
185
215
  * @param {AST.Attribute['value']} value
@@ -286,7 +316,7 @@ export function build_getter(node, state) {
286
316
  }
287
317
 
288
318
  if (binding.kind === 'derived') {
289
- return (binding.declaration_kind === 'var' ? b.maybe_call : b.call)(binding.node);
319
+ return (binding.declaration_kind === 'var' ? b.maybe_call : b.call)(node);
290
320
  }
291
321
 
292
322
  return node;
@@ -23,7 +23,9 @@ export const binding_properties = {
23
23
  event: 'durationchange',
24
24
  omit_in_ssr: true
25
25
  },
26
- focused: {},
26
+ focused: {
27
+ omit_in_ssr: true // no corresponding HTML attribute
28
+ },
27
29
  paused: {
28
30
  valid_elements: ['audio', 'video'],
29
31
  omit_in_ssr: true,
@@ -77,6 +77,9 @@ export class ExpressionMetadata {
77
77
  /** True if the expression contains `await` */
78
78
  has_await = false;
79
79
 
80
+ /** True if an `await` restores the reaction context afterwards, so the thunk must end it */
81
+ has_pickled_await = false;
82
+
80
83
  /** True if the expression includes a member expression */
81
84
  has_member_expression = false;
82
85
 
@@ -142,6 +145,7 @@ export class ExpressionMetadata {
142
145
  this.has_state ||= source.has_state;
143
146
  this.has_call ||= source.has_call;
144
147
  this.has_await ||= source.has_await;
148
+ this.has_pickled_await ||= source.has_pickled_await;
145
149
  this.has_member_expression ||= source.has_member_expression;
146
150
  this.has_assignment ||= source.has_assignment;
147
151
  this.#blockers = null; // so that blockers are recalculated