svelte 5.55.9 → 5.56.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 (49) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +8 -5
  3. package/src/compiler/errors.js +18 -0
  4. package/src/compiler/legacy.js +4 -0
  5. package/src/compiler/phases/1-parse/acorn.js +44 -1
  6. package/src/compiler/phases/1-parse/index.js +4 -1
  7. package/src/compiler/phases/1-parse/state/tag.js +91 -3
  8. package/src/compiler/phases/2-analyze/index.js +5 -0
  9. package/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +1 -2
  10. package/src/compiler/phases/2-analyze/visitors/CallExpression.js +3 -0
  11. package/src/compiler/phases/2-analyze/visitors/ConstTag.js +2 -25
  12. package/src/compiler/phases/2-analyze/visitors/DeclarationTag.js +58 -0
  13. package/src/compiler/phases/2-analyze/visitors/Identifier.js +1 -1
  14. package/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +1 -1
  15. package/src/compiler/phases/3-transform/client/transform-client.js +5 -15
  16. package/src/compiler/phases/3-transform/client/transform-template/index.js +40 -3
  17. package/src/compiler/phases/3-transform/client/utils.js +21 -0
  18. package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +13 -24
  19. package/src/compiler/phases/3-transform/client/visitors/DeclarationTag.js +87 -0
  20. package/src/compiler/phases/3-transform/client/visitors/Fragment.js +2 -5
  21. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +32 -10
  22. package/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +7 -2
  23. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +14 -11
  24. package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +1 -1
  25. package/src/compiler/phases/3-transform/server/transform-server.js +2 -0
  26. package/src/compiler/phases/3-transform/server/visitors/ConstTag.js +9 -24
  27. package/src/compiler/phases/3-transform/server/visitors/DeclarationTag.js +85 -0
  28. package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +24 -7
  29. package/src/compiler/phases/3-transform/utils.js +1 -0
  30. package/src/compiler/phases/nodes.js +3 -2
  31. package/src/compiler/print/index.js +42 -0
  32. package/src/compiler/utils/builders.js +2 -1
  33. package/src/compiler/warnings.js +6 -5
  34. package/src/internal/client/dom/blocks/boundary.js +1 -1
  35. package/src/internal/client/dom/blocks/branches.js +2 -0
  36. package/src/internal/client/dom/blocks/svelte-element.js +5 -3
  37. package/src/internal/client/dom/elements/events.js +5 -10
  38. package/src/internal/client/dom/operations.js +12 -2
  39. package/src/internal/client/reactivity/async.js +4 -4
  40. package/src/internal/client/reactivity/batch.js +70 -78
  41. package/src/internal/client/reactivity/deriveds.js +7 -4
  42. package/src/internal/client/reactivity/effects.js +5 -2
  43. package/src/internal/client/reactivity/props.js +6 -6
  44. package/src/internal/client/reactivity/sources.js +1 -2
  45. package/src/internal/client/runtime.js +4 -8
  46. package/src/utils.js +1 -1
  47. package/src/version.js +1 -1
  48. package/types/index.d.ts +7 -0
  49. package/types/index.d.ts.map +1 -1
@@ -7,6 +7,7 @@ import * as b from '#compiler/builders';
7
7
  import { create_derived } from '../utils.js';
8
8
  import { get_value } from './shared/declarations.js';
9
9
  import { build_expression } from './shared/utils.js';
10
+ import { add_async_declaration } from './DeclarationTag.js';
10
11
 
11
12
  /**
12
13
  * @param {AST.ConstTag} node
@@ -26,7 +27,7 @@ export function ConstTag(node, context) {
26
27
 
27
28
  context.state.transform[declaration.id.name] = { read: get_value };
28
29
 
29
- add_const_declaration(context.state, declaration.id, expression, node.metadata);
30
+ add_const_declaration(context, declaration.id, expression, node.metadata);
30
31
  } else {
31
32
  const identifiers = extract_identifiers(declaration.id);
32
33
  const tmp = b.id(context.state.scope.generate('computed_const'));
@@ -63,7 +64,7 @@ export function ConstTag(node, context) {
63
64
  expression = b.call('$.tag', expression, b.literal('[@const]'));
64
65
  }
65
66
 
66
- add_const_declaration(context.state, tmp, expression, node.metadata);
67
+ add_const_declaration(context, tmp, expression, node.metadata);
67
68
 
68
69
  for (const node of identifiers) {
69
70
  context.state.transform[node.name] = {
@@ -74,38 +75,26 @@ export function ConstTag(node, context) {
74
75
  }
75
76
 
76
77
  /**
77
- * @param {ComponentContext['state']} state
78
+ * @param {ComponentContext} context
78
79
  * @param {Identifier} id
79
80
  * @param {Expression} expression
80
81
  * @param {AST.ConstTag['metadata']} metadata
81
82
  */
82
- function add_const_declaration(state, id, expression, metadata) {
83
+ function add_const_declaration(context, id, expression, metadata) {
83
84
  // we need to eagerly evaluate the expression in order to hit any
84
85
  // 'Cannot access x before initialization' errors
85
86
  const after = dev ? [b.stmt(b.call('$.get', id))] : [];
86
87
 
87
- const blockers = [...metadata.expression.dependencies]
88
- .map((dep) => dep.blocker)
89
- .filter((b) => b !== null && b.object !== state.async_consts?.id);
90
-
91
88
  if (metadata.promises_id) {
92
- const run = (state.async_consts ??= {
93
- id: metadata.promises_id,
94
- thunks: []
95
- });
96
-
97
- state.consts.push(b.let(id));
98
-
99
- if (blockers.length === 1) {
100
- run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
101
- } else if (blockers.length > 0) {
102
- run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
103
- }
104
-
105
- // keep the number of thunks pushed in sync with ConstTag in analysis phase
106
- const assignment = b.assignment('=', id, expression);
107
- run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
89
+ add_async_declaration(
90
+ context,
91
+ metadata,
92
+ [id],
93
+ [b.stmt(b.assignment('=', id, expression))],
94
+ 'let'
95
+ );
108
96
  } else {
97
+ const { state } = context;
109
98
  state.consts.push(b.const(id, expression));
110
99
  state.consts.push(...after);
111
100
  }
@@ -0,0 +1,87 @@
1
+ /** @import { Expression, Identifier, Pattern, Statement, ExpressionStatement, VariableDeclaration } from 'estree' */
2
+ /** @import { AST } from '#compiler' */
3
+ /** @import { ComponentContext } from '../types' */
4
+ import { extract_identifiers, has_await_expression } from '../../../../utils/ast.js';
5
+ import * as b from '#compiler/builders';
6
+ import { add_state_transformers } from './shared/declarations.js';
7
+
8
+ /**
9
+ * @param {AST.DeclarationTag} node
10
+ * @param {ComponentContext} context
11
+ */
12
+ export function DeclarationTag(node, context) {
13
+ const declaration = /** @type {Statement | undefined} */ (context.visit(node.declaration));
14
+ add_state_transformers(context);
15
+
16
+ if (
17
+ node.metadata.promises_id &&
18
+ node.declaration.type === 'VariableDeclaration' &&
19
+ declaration?.type === 'VariableDeclaration'
20
+ ) {
21
+ const { ids, assignments } = build_async_declaration_parts(declaration);
22
+ add_async_declaration(context, node.metadata, ids, assignments, declaration.kind);
23
+ } else {
24
+ context.state.consts.push(declaration ?? node.declaration);
25
+ }
26
+ }
27
+
28
+ /**
29
+ * @param {VariableDeclaration} declaration
30
+ */
31
+ export function build_async_declaration_parts(declaration) {
32
+ const ids = new Map();
33
+ for (const declarator of declaration.declarations) {
34
+ for (const id of extract_identifiers(declarator.id)) {
35
+ ids.set(id.name, id);
36
+ }
37
+ }
38
+
39
+ const assignments = declaration.declarations
40
+ .filter((declarator) => declarator.init !== null)
41
+ .map((declarator) =>
42
+ b.stmt(
43
+ b.assignment(
44
+ '=',
45
+ /** @type {Pattern} */ (declarator.id),
46
+ /** @type {Expression} */ (declarator.init)
47
+ )
48
+ )
49
+ );
50
+
51
+ return { ids: [...ids.values()], assignments };
52
+ }
53
+
54
+ /**
55
+ * @param {ComponentContext} context
56
+ * @param {AST.ConstTag['metadata'] | AST.DeclarationTag['metadata']} metadata
57
+ * @param {Identifier[]} ids
58
+ * @param {ExpressionStatement[]} assignments
59
+ * @param {VariableDeclaration['kind']} [kind]
60
+ */
61
+ export function add_async_declaration(context, metadata, ids, assignments, kind = 'let') {
62
+ const run = (context.state.async_consts ??= {
63
+ id: /** @type {Identifier} */ (metadata.promises_id),
64
+ thunks: []
65
+ });
66
+
67
+ for (const id of ids) {
68
+ context.state.consts.push(kind === 'var' ? b.var(id.name) : b.let(id.name));
69
+ }
70
+
71
+ const blockers = [...metadata.expression.dependencies]
72
+ .map((dep) => dep.blocker)
73
+ .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
74
+
75
+ if (blockers.length === 1) {
76
+ run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
77
+ } else if (blockers.length > 0) {
78
+ run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
79
+ }
80
+
81
+ // keep the number of thunks pushed in sync with analysis phase
82
+ const has_await =
83
+ metadata.expression.has_await ||
84
+ assignments.some((assignment) => has_await_expression(assignment));
85
+ const body = assignments.length === 1 ? assignments[0].expression : b.block(assignments);
86
+ run.thunks.push(b.thunk(body, has_await));
87
+ }
@@ -52,7 +52,6 @@ export function Fragment(node, context) {
52
52
  (trimmed[0].type === 'IfBlock' &&
53
53
  trimmed[0].elseif &&
54
54
  /** @type {AST.IfBlock} */ (parent).metadata.flattened?.includes(trimmed[0])));
55
- const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent
56
55
 
57
56
  /** @type {Statement[]} */
58
57
  const body = [];
@@ -96,8 +95,7 @@ export function Fragment(node, context) {
96
95
 
97
96
  let flags = state.template.needs_import_node ? TEMPLATE_USE_IMPORT_NODE : undefined;
98
97
 
99
- const template = transform_template(state, namespace, flags);
100
- state.hoisted.push(b.var(template_name, template));
98
+ const template_name = transform_template(state, 'root', flags);
101
99
 
102
100
  state.init.unshift(b.var(id, b.call(template_name)));
103
101
  close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
@@ -147,8 +145,7 @@ export function Fragment(node, context) {
147
145
  // special case — we can use `$.comment` instead of creating a unique template
148
146
  state.init.unshift(b.var(id, b.call('$.comment')));
149
147
  } else {
150
- const template = transform_template(state, namespace, flags);
151
- state.hoisted.push(b.var(template_name, template));
148
+ const template_name = transform_template(state, 'root', flags);
152
149
 
153
150
  state.init.unshift(b.var(id, b.call(template_name)));
154
151
  }
@@ -18,7 +18,7 @@ import {
18
18
  is_customizable_select_element
19
19
  } from '../../../nodes.js';
20
20
  import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
21
- import { build_getter } from '../utils.js';
21
+ import { build_getter, get_transform } from '../utils.js';
22
22
  import {
23
23
  get_attribute_name,
24
24
  build_attribute_value,
@@ -201,8 +201,8 @@ export function RegularElement(node, context) {
201
201
  }
202
202
  }
203
203
 
204
- // Let bindings first, they can be used on attributes
205
- context.state.init.push(...lets);
204
+ // Let bindings first, they can be used on attributes and `{@const}` declarations
205
+ context.state.let_directives.push(...lets);
206
206
 
207
207
  const node_id = context.state.node;
208
208
 
@@ -300,11 +300,14 @@ export function RegularElement(node, context) {
300
300
  }
301
301
  }
302
302
 
303
+ const scope = /** @type {Scope} */ (context.state.scopes.get(node.fragment));
304
+
303
305
  /** @type {ComponentClientTransformState} */
304
306
  const state = {
305
307
  ...context.state,
306
308
  metadata,
307
- scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)),
309
+ scope,
310
+ transform: get_transform(scope, context.state),
308
311
  preserve_whitespace: context.state.preserve_whitespace || name === 'pre' || name === 'textarea'
309
312
  };
310
313
 
@@ -318,8 +321,19 @@ export function RegularElement(node, context) {
318
321
  state.options.preserveComments
319
322
  );
320
323
 
324
+ const has_declarations = !node.fragment.metadata.transparent;
325
+
321
326
  /** @type {typeof state} */
322
- const child_state = { ...state, init: [], update: [], after_update: [], snippets: [] };
327
+ const child_state = {
328
+ ...state,
329
+ init: [],
330
+ update: [],
331
+ after_update: [],
332
+ snippets: [],
333
+ consts: has_declarations ? [] : state.consts,
334
+ async_consts: has_declarations ? undefined : state.async_consts,
335
+ memoizer: has_declarations ? new Memoizer() : state.memoizer
336
+ };
323
337
 
324
338
  for (const node of hoisted) {
325
339
  context.visit(node, child_state);
@@ -360,7 +374,6 @@ export function RegularElement(node, context) {
360
374
  context.state.template.push_comment();
361
375
 
362
376
  // Create a separate template for the rich content
363
- const template_name = context.state.scope.root.unique(`${name}_content`);
364
377
  const fragment_id = b.id(context.state.scope.generate('fragment'));
365
378
  const anchor_id = b.id(context.state.scope.generate('anchor'));
366
379
 
@@ -384,9 +397,8 @@ export function RegularElement(node, context) {
384
397
  }
385
398
  );
386
399
 
387
- // Transform the template to $.from_html(...) and hoist it
388
- const template = transform_template(select_state, metadata.namespace, TEMPLATE_FRAGMENT);
389
- context.state.hoisted.push(b.var(template_name, template));
400
+ // Transform the template to $.from_html(...) and hoist it (deduplicating identical templates)
401
+ const template_name = transform_template(select_state, `${name}_content`, TEMPLATE_FRAGMENT);
390
402
 
391
403
  // Build the rich content function body
392
404
  // The anchor is the child of the element (a hydration marker during hydration)
@@ -427,11 +439,21 @@ export function RegularElement(node, context) {
427
439
  }
428
440
  }
429
441
 
430
- if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
442
+ if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock') || has_declarations) {
443
+ if (child_state.async_consts && child_state.async_consts.thunks.length > 0) {
444
+ child_state.consts.push(
445
+ b.var(
446
+ child_state.async_consts.id,
447
+ b.call('$.run', b.array(child_state.async_consts.thunks))
448
+ )
449
+ );
450
+ }
451
+
431
452
  // Wrap children in `{...}` to avoid declaration conflicts
432
453
  context.state.init.push(
433
454
  b.block([
434
455
  ...child_state.snippets,
456
+ ...child_state.consts,
435
457
  ...child_state.init,
436
458
  ...element_state.init,
437
459
  child_state.update.length > 0 ? build_render_statement(child_state) : b.empty,
@@ -40,6 +40,7 @@ export function SvelteBoundary(node, context) {
40
40
  const hoisted = [];
41
41
 
42
42
  let has_const = false;
43
+ let has_declaration = false;
43
44
 
44
45
  // const tags need to live inside the boundary, but might also be referenced in hoisted snippets.
45
46
  // to resolve this we cheat: we duplicate const tags inside snippets
@@ -55,6 +56,10 @@ export function SvelteBoundary(node, context) {
55
56
  });
56
57
  }
57
58
  }
59
+
60
+ if (child.type === 'DeclarationTag') {
61
+ has_declaration = true;
62
+ }
58
63
  }
59
64
 
60
65
  for (const child of node.fragment.nodes) {
@@ -68,10 +73,10 @@ export function SvelteBoundary(node, context) {
68
73
  if (child.type === 'SnippetBlock') {
69
74
  if (
70
75
  context.state.options.experimental.async &&
71
- has_const &&
76
+ (has_const || has_declaration) &&
72
77
  !['failed', 'pending'].includes(child.expression.name)
73
78
  ) {
74
- // we can't hoist snippets as they may reference const tags, so we just keep them in the fragment
79
+ // we can't hoist snippets as they may reference const/declaration tags, so we just keep them in the fragment
75
80
  nodes.push(child);
76
81
  } else {
77
82
  /** @type {Statement[]} */
@@ -49,8 +49,13 @@ export function VariableDeclaration(node, context) {
49
49
  }
50
50
 
51
51
  if (declarator.id.type === 'Identifier') {
52
+ const exclude_id = context.state.scope.root.unique('rest_excludes');
53
+ context.state.hoisted.push(
54
+ b.var(exclude_id, b.new('Set', b.array(seen.map((name) => b.literal(name)))))
55
+ );
56
+
52
57
  /** @type {Expression[]} */
53
- const args = [b.id('$$props'), b.array(seen.map((name) => b.literal(name)))];
58
+ const args = [b.id('$$props'), exclude_id];
54
59
 
55
60
  if (dev) {
56
61
  // include rest name, so we can provide informative error messages
@@ -95,8 +100,13 @@ export function VariableDeclaration(node, context) {
95
100
  }
96
101
  } else {
97
102
  // RestElement
103
+ const exclude_id = context.state.scope.root.unique('rest_excludes');
104
+ context.state.hoisted.push(
105
+ b.var(exclude_id, b.new('Set', b.array(seen.map((name) => b.literal(name)))))
106
+ );
107
+
98
108
  /** @type {Expression[]} */
99
- const args = [b.id('$$props'), b.array(seen.map((name) => b.literal(name)))];
109
+ const args = [b.id('$$props'), exclude_id];
100
110
 
101
111
  if (dev) {
102
112
  // include rest name, so we can provide informative error messages
@@ -194,11 +204,6 @@ export function VariableDeclaration(node, context) {
194
204
  /** @type {CallExpression} */ (init)
195
205
  );
196
206
 
197
- // for now, only wrap async derived in $.save if it's not
198
- // a top-level instance derived. TODO in future maybe we
199
- // can dewaterfall all of them?
200
- const should_save = context.state.is_instance && context.state.scope.function_depth > 1;
201
-
202
207
  if (declarator.id.type === 'Identifier') {
203
208
  let expression = /** @type {Expression} */ (context.visit(value));
204
209
 
@@ -213,9 +218,7 @@ export function VariableDeclaration(node, context) {
213
218
  location ? b.literal(location) : undefined
214
219
  );
215
220
 
216
- call = should_save ? save(call) : b.await(call);
217
-
218
- declarations.push(b.declarator(declarator.id, call));
221
+ declarations.push(b.declarator(declarator.id, b.await(call)));
219
222
  } else {
220
223
  if (rune === '$derived') expression = b.thunk(expression);
221
224
 
@@ -251,7 +254,7 @@ export function VariableDeclaration(node, context) {
251
254
  location ? b.literal(location) : undefined
252
255
  );
253
256
 
254
- call = should_save ? save(call) : b.await(call);
257
+ call = b.await(call);
255
258
  }
256
259
 
257
260
  declarations.push(b.declarator(id, call));
@@ -52,7 +52,7 @@ export class Memoizer {
52
52
  * @param {ExpressionMetadata} metadata
53
53
  */
54
54
  check_blockers(metadata) {
55
- for (const binding of metadata.dependencies) {
55
+ for (const binding of metadata.references) {
56
56
  if (binding.blocker) {
57
57
  this.#blockers.add(binding.blocker);
58
58
  }
@@ -15,6 +15,7 @@ import { CallExpression } from './visitors/CallExpression.js';
15
15
  import { ClassBody } from './visitors/ClassBody.js';
16
16
  import { Component } from './visitors/Component.js';
17
17
  import { ConstTag } from './visitors/ConstTag.js';
18
+ import { DeclarationTag } from './visitors/DeclarationTag.js';
18
19
  import { DebugTag } from './visitors/DebugTag.js';
19
20
  import { EachBlock } from './visitors/EachBlock.js';
20
21
  import { ExpressionStatement } from './visitors/ExpressionStatement.js';
@@ -64,6 +65,7 @@ const template_visitors = {
64
65
  AwaitBlock,
65
66
  Component,
66
67
  ConstTag,
68
+ DeclarationTag,
67
69
  DebugTag,
68
70
  EachBlock,
69
71
  Fragment,
@@ -1,8 +1,9 @@
1
- /** @import { Expression, Pattern, Statement } from 'estree' */
1
+ /** @import { Expression, Pattern } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types.js' */
4
4
  import * as b from '#compiler/builders';
5
5
  import { extract_identifiers } from '../../../../utils/ast.js';
6
+ import { add_async_declaration } from './DeclarationTag.js';
6
7
 
7
8
  /**
8
9
  * @param {AST.ConstTag} node
@@ -12,31 +13,15 @@ export function ConstTag(node, context) {
12
13
  const declaration = node.declaration.declarations[0];
13
14
  const id = /** @type {Pattern} */ (context.visit(declaration.id));
14
15
  const init = /** @type {Expression} */ (context.visit(declaration.init));
15
- const blockers = [...node.metadata.expression.dependencies]
16
- .map((dep) => dep.blocker)
17
- .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
18
16
 
19
17
  if (node.metadata.promises_id) {
20
- const run = (context.state.async_consts ??= {
21
- id: node.metadata.promises_id,
22
- thunks: []
23
- });
24
-
25
- const identifiers = extract_identifiers(declaration.id);
26
-
27
- for (const identifier of identifiers) {
28
- context.state.init.push(b.let(identifier.name));
29
- }
30
-
31
- if (blockers.length === 1) {
32
- run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
33
- } else if (blockers.length > 0) {
34
- run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
35
- }
36
-
37
- // keep the number of thunks pushed in sync with ConstTag in analysis phase
38
- const assignment = b.assignment('=', id, init);
39
- run.thunks.push(b.thunk(assignment, node.metadata.expression.has_await));
18
+ add_async_declaration(
19
+ context,
20
+ node.metadata,
21
+ extract_identifiers(id),
22
+ [b.stmt(b.assignment('=', id, init))],
23
+ 'let'
24
+ );
40
25
  } else {
41
26
  context.state.init.push(b.const(id, init));
42
27
  }
@@ -0,0 +1,85 @@
1
+ /** @import { Expression, Identifier, Pattern, Statement, ExpressionStatement, VariableDeclaration } from 'estree' */
2
+ /** @import { AST } from '#compiler' */
3
+ /** @import { ComponentContext } from '../types.js' */
4
+ import { extract_identifiers, has_await_expression } from '../../../../utils/ast.js';
5
+ import * as b from '#compiler/builders';
6
+
7
+ /**
8
+ * @param {AST.DeclarationTag} node
9
+ * @param {ComponentContext} context
10
+ */
11
+ export function DeclarationTag(node, context) {
12
+ const declaration = /** @type {Statement} */ (context.visit(node.declaration));
13
+
14
+ if (
15
+ node.metadata.promises_id &&
16
+ node.declaration.type === 'VariableDeclaration' &&
17
+ declaration.type === 'VariableDeclaration'
18
+ ) {
19
+ const { ids, assignments } = build_async_declaration_parts(declaration);
20
+ add_async_declaration(context, node.metadata, ids, assignments, declaration.kind);
21
+ } else {
22
+ context.state.init.push(declaration);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * @param {VariableDeclaration} declaration
28
+ */
29
+ export function build_async_declaration_parts(declaration) {
30
+ const ids = new Map();
31
+ for (const declarator of declaration.declarations) {
32
+ for (const id of extract_identifiers(declarator.id)) {
33
+ ids.set(id.name, id);
34
+ }
35
+ }
36
+
37
+ const assignments = declaration.declarations
38
+ .filter((declarator) => declarator.init !== null)
39
+ .map((declarator) =>
40
+ b.stmt(
41
+ b.assignment(
42
+ '=',
43
+ /** @type {Pattern} */ (declarator.id),
44
+ /** @type {Expression} */ (declarator.init)
45
+ )
46
+ )
47
+ );
48
+
49
+ return { ids: [...ids.values()], assignments };
50
+ }
51
+
52
+ /**
53
+ * @param {ComponentContext} context
54
+ * @param {AST.ConstTag['metadata'] | AST.DeclarationTag['metadata']} metadata
55
+ * @param {Identifier[]} ids
56
+ * @param {ExpressionStatement[]} assignments
57
+ * @param {VariableDeclaration['kind']} [kind]
58
+ */
59
+ export function add_async_declaration(context, metadata, ids, assignments, kind = 'let') {
60
+ const run = (context.state.async_consts ??= {
61
+ id: /** @type {Identifier} */ (metadata.promises_id),
62
+ thunks: []
63
+ });
64
+
65
+ for (const id of ids) {
66
+ context.state.init.push(kind === 'var' ? b.var(id.name) : b.let(id.name));
67
+ }
68
+
69
+ const blockers = [...metadata.expression.dependencies]
70
+ .map((dep) => dep.blocker)
71
+ .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
72
+
73
+ if (blockers.length === 1) {
74
+ run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
75
+ } else if (blockers.length > 0) {
76
+ run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
77
+ }
78
+
79
+ // keep the number of thunks pushed in sync with analysis phase
80
+ const has_await =
81
+ metadata.expression.has_await ||
82
+ assignments.some((assignment) => has_await_expression(assignment));
83
+ const body = assignments.length === 1 ? assignments[0].expression : b.block(assignments);
84
+ run.thunks.push(b.thunk(body, has_await));
85
+ }
@@ -17,17 +17,23 @@ import { is_customizable_select_element } from '../../../nodes.js';
17
17
  export function RegularElement(node, context) {
18
18
  const name = context.state.namespace === 'html' ? node.name.toLowerCase() : node.name;
19
19
  const namespace = determine_namespace_for_children(node, context.state.namespace);
20
+ const has_child_declarations = !node.fragment.metadata.transparent;
20
21
 
21
22
  /** @type {ComponentServerTransformState} */
22
23
  const state = {
23
24
  ...context.state,
24
25
  namespace,
26
+ scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)),
25
27
  preserve_whitespace:
26
28
  context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea',
27
29
  init: [],
28
- template: []
30
+ template: [],
31
+ async_consts: undefined
29
32
  };
30
33
 
34
+ /** @type {ComponentServerTransformState} */
35
+ const attribute_state = { ...state, scope: context.state.scope };
36
+
31
37
  const node_is_void = is_void(name);
32
38
 
33
39
  const optimiser = new PromiseOptimiser();
@@ -50,7 +56,11 @@ export function RegularElement(node, context) {
50
56
  if (!is_special) {
51
57
  // only open the tag in the non-special path
52
58
  state.template.push(b.literal(`<${name}`));
53
- body = build_element_attributes(node, { ...context, state }, optimiser.transform);
59
+ body = build_element_attributes(
60
+ node,
61
+ { ...context, state: attribute_state },
62
+ optimiser.transform
63
+ );
54
64
  state.template.push(b.literal(node_is_void ? '/>' : '>')); // add `/>` for XHTML compliance
55
65
  }
56
66
 
@@ -72,10 +82,7 @@ export function RegularElement(node, context) {
72
82
  node.fragment.nodes,
73
83
  context.path,
74
84
  namespace,
75
- {
76
- ...state,
77
- scope: /** @type {Scope} */ (state.scopes.get(node.fragment))
78
- },
85
+ state,
79
86
  state.preserve_whitespace,
80
87
  state.options.preserveComments
81
88
  );
@@ -205,7 +212,17 @@ export function RegularElement(node, context) {
205
212
  state.template.push(b.stmt(b.call('$.pop_element')));
206
213
  }
207
214
 
208
- if (optimiser.is_async()) {
215
+ if (has_child_declarations && state.async_consts && state.async_consts.thunks.length > 0) {
216
+ state.init.push(
217
+ b.var(state.async_consts.id, b.call('$$renderer.run', b.array(state.async_consts.thunks)))
218
+ );
219
+ }
220
+
221
+ if (has_child_declarations) {
222
+ context.state.template.push(
223
+ ...optimiser.render([b.block([...state.init, ...build_template(state.template)])])
224
+ );
225
+ } else if (optimiser.is_async()) {
209
226
  context.state.template.push(
210
227
  ...optimiser.render([...state.init, ...build_template(state.template)])
211
228
  );
@@ -152,6 +152,7 @@ export function clean_nodes(
152
152
 
153
153
  if (
154
154
  node.type === 'ConstTag' ||
155
+ node.type === 'DeclarationTag' ||
155
156
  node.type === 'DebugTag' ||
156
157
  node.type === 'SvelteBody' ||
157
158
  node.type === 'SvelteWindow' ||
@@ -102,8 +102,8 @@ export class ExpressionMetadata {
102
102
  if (!this.#blockers) {
103
103
  this.#blockers = new Set();
104
104
 
105
- for (const d of this.dependencies) {
106
- if (d.blocker) this.#blockers.add(d.blocker);
105
+ for (const r of this.references) {
106
+ if (r.blocker) this.#blockers.add(r.blocker);
107
107
  }
108
108
  }
109
109
 
@@ -217,6 +217,7 @@ function* find_descendants(fragment) {
217
217
  case 'SnippetBlock':
218
218
  case 'DebugTag':
219
219
  case 'ConstTag':
220
+ case 'DeclarationTag':
220
221
  case 'Comment':
221
222
  case 'ExpressionTag':
222
223
  break;