svelte 5.42.3 → 5.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/compiler/phases/2-analyze/index.js +201 -2
  4. package/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +6 -6
  5. package/src/compiler/phases/2-analyze/visitors/BindDirective.js +1 -0
  6. package/src/compiler/phases/2-analyze/visitors/SnippetBlock.js +6 -1
  7. package/src/compiler/phases/3-transform/client/transform-client.js +13 -34
  8. package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +18 -7
  9. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +2 -2
  10. package/src/compiler/phases/3-transform/client/visitors/EachBlock.js +6 -4
  11. package/src/compiler/phases/3-transform/client/visitors/ExpressionStatement.js +1 -1
  12. package/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +6 -4
  13. package/src/compiler/phases/3-transform/client/visitors/IfBlock.js +6 -4
  14. package/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +5 -4
  15. package/src/compiler/phases/3-transform/client/visitors/Program.js +15 -3
  16. package/src/compiler/phases/3-transform/client/visitors/RenderTag.js +6 -4
  17. package/src/compiler/phases/3-transform/client/visitors/SlotElement.js +3 -1
  18. package/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +5 -4
  19. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +13 -2
  20. package/src/compiler/phases/3-transform/client/visitors/shared/component.js +20 -16
  21. package/src/compiler/phases/3-transform/client/visitors/shared/element.js +1 -0
  22. package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +15 -1
  23. package/src/compiler/phases/3-transform/server/transform-server.js +9 -9
  24. package/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js +6 -2
  25. package/src/compiler/phases/3-transform/server/visitors/CallExpression.js +8 -1
  26. package/src/compiler/phases/3-transform/server/visitors/EachBlock.js +14 -3
  27. package/src/compiler/phases/3-transform/server/visitors/HtmlTag.js +3 -5
  28. package/src/compiler/phases/3-transform/server/visitors/IfBlock.js +12 -4
  29. package/src/compiler/phases/3-transform/server/visitors/KeyBlock.js +7 -1
  30. package/src/compiler/phases/3-transform/server/visitors/Program.js +25 -0
  31. package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +14 -7
  32. package/src/compiler/phases/3-transform/server/visitors/RenderTag.js +27 -11
  33. package/src/compiler/phases/3-transform/server/visitors/SlotElement.js +7 -4
  34. package/src/compiler/phases/3-transform/server/visitors/SvelteElement.js +24 -4
  35. package/src/compiler/phases/3-transform/server/visitors/shared/component.js +10 -4
  36. package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +79 -8
  37. package/src/compiler/phases/3-transform/shared/transform-async.js +102 -0
  38. package/src/compiler/phases/nodes.js +24 -0
  39. package/src/compiler/phases/scope.js +27 -8
  40. package/src/compiler/utils/ast.js +1 -1
  41. package/src/compiler/utils/builders.js +15 -4
  42. package/src/internal/client/dom/blocks/async.js +7 -6
  43. package/src/internal/client/dom/blocks/boundary.js +5 -8
  44. package/src/internal/client/dom/elements/attributes.js +3 -1
  45. package/src/internal/client/index.js +1 -0
  46. package/src/internal/client/proxy.js +1 -1
  47. package/src/internal/client/reactivity/async.js +105 -46
  48. package/src/internal/client/reactivity/batch.js +2 -15
  49. package/src/internal/client/reactivity/effects.js +3 -2
  50. package/src/internal/client/runtime.js +12 -11
  51. package/src/internal/server/renderer.js +58 -3
  52. package/src/version.js +1 -1
  53. package/types/index.d.ts.map +1 -1
  54. package/src/compiler/phases/3-transform/client/visitors/ImportDeclaration.js +0 -16
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "svelte",
3
3
  "description": "Cybernetically enhanced web apps",
4
4
  "license": "MIT",
5
- "version": "5.42.3",
5
+ "version": "5.43.1",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -6,7 +6,12 @@ import { walk } from 'zimmerframe';
6
6
  import { parse } from '../1-parse/acorn.js';
7
7
  import * as e from '../../errors.js';
8
8
  import * as w from '../../warnings.js';
9
- import { extract_identifiers, has_await_expression } from '../../utils/ast.js';
9
+ import {
10
+ extract_identifiers,
11
+ has_await_expression,
12
+ object,
13
+ unwrap_pattern
14
+ } from '../../utils/ast.js';
10
15
  import * as b from '#compiler/builders';
11
16
  import { Scope, ScopeRoot, create_scopes, get_rune, set_scope } from '../scope.js';
12
17
  import check_graph_for_cycles from './utils/check_graph_for_cycles.js';
@@ -543,7 +548,13 @@ export function analyze_component(root, source, options) {
543
548
  snippet_renderers: new Map(),
544
549
  snippets: new Set(),
545
550
  async_deriveds: new Set(),
546
- pickled_awaits: new Set()
551
+ pickled_awaits: new Set(),
552
+ instance_body: {
553
+ sync: [],
554
+ async: [],
555
+ declarations: [],
556
+ hoisted: []
557
+ }
547
558
  };
548
559
 
549
560
  if (!runes) {
@@ -676,6 +687,194 @@ export function analyze_component(root, source, options) {
676
687
  }
677
688
  }
678
689
 
690
+ /**
691
+ * @param {ESTree.Node} expression
692
+ * @param {Scope} scope
693
+ * @param {Set<Binding>} touched
694
+ * @param {Set<ESTree.Node>} seen
695
+ */
696
+ const touch = (expression, scope, touched, seen = new Set()) => {
697
+ if (seen.has(expression)) return;
698
+ seen.add(expression);
699
+
700
+ walk(
701
+ expression,
702
+ { scope },
703
+ {
704
+ ImportDeclaration(node) {},
705
+ Identifier(node, context) {
706
+ const parent = /** @type {ESTree.Node} */ (context.path.at(-1));
707
+ if (is_reference(node, parent)) {
708
+ const binding = context.state.scope.get(node.name);
709
+ if (binding) {
710
+ touched.add(binding);
711
+
712
+ for (const assignment of binding.assignments) {
713
+ touch(assignment.value, assignment.scope, touched, seen);
714
+ }
715
+ }
716
+ }
717
+ }
718
+ }
719
+ );
720
+ };
721
+
722
+ /**
723
+ * @param {ESTree.Node} node
724
+ * @param {Set<ESTree.Node>} seen
725
+ * @param {Set<Binding>} reads
726
+ * @param {Set<Binding>} writes
727
+ */
728
+ const trace_references = (node, reads, writes, seen = new Set()) => {
729
+ if (seen.has(node)) return;
730
+ seen.add(node);
731
+
732
+ /**
733
+ * @param {ESTree.Pattern} node
734
+ * @param {Scope} scope
735
+ */
736
+ function update(node, scope) {
737
+ for (const pattern of unwrap_pattern(node)) {
738
+ const node = object(pattern);
739
+ if (!node) return;
740
+
741
+ const binding = scope.get(node.name);
742
+ if (!binding) return;
743
+
744
+ writes.add(binding);
745
+ }
746
+ }
747
+
748
+ walk(
749
+ node,
750
+ { scope: instance.scope },
751
+ {
752
+ _(node, context) {
753
+ const scope = scopes.get(node);
754
+ if (scope) {
755
+ context.next({ scope });
756
+ } else {
757
+ context.next();
758
+ }
759
+ },
760
+ AssignmentExpression(node, context) {
761
+ update(node.left, context.state.scope);
762
+ },
763
+ UpdateExpression(node, context) {
764
+ update(
765
+ /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node.argument),
766
+ context.state.scope
767
+ );
768
+ },
769
+ CallExpression(node, context) {
770
+ // for now, assume everything touched by the callee ends up mutating the object
771
+ // TODO optimise this better
772
+
773
+ // special case — no need to peek inside effects as they only run once async work has completed
774
+ const rune = get_rune(node, context.state.scope);
775
+ if (rune === '$effect') return;
776
+
777
+ /** @type {Set<Binding>} */
778
+ const touched = new Set();
779
+ touch(node, context.state.scope, touched);
780
+
781
+ for (const b of touched) {
782
+ writes.add(b);
783
+ }
784
+ },
785
+ // don't look inside functions until they are called
786
+ ArrowFunctionExpression(_, context) {},
787
+ FunctionDeclaration(_, context) {},
788
+ FunctionExpression(_, context) {},
789
+ Identifier(node, context) {
790
+ const parent = /** @type {ESTree.Node} */ (context.path.at(-1));
791
+ if (is_reference(node, parent)) {
792
+ const binding = context.state.scope.get(node.name);
793
+ if (binding) {
794
+ reads.add(binding);
795
+ }
796
+ }
797
+ }
798
+ }
799
+ );
800
+ };
801
+
802
+ let awaited = false;
803
+
804
+ // TODO this should probably be attached to the scope?
805
+ var promises = b.id('$$promises');
806
+
807
+ /**
808
+ * @param {ESTree.Identifier} id
809
+ * @param {ESTree.Expression} blocker
810
+ */
811
+ function push_declaration(id, blocker) {
812
+ analysis.instance_body.declarations.push(id);
813
+
814
+ const binding = /** @type {Binding} */ (instance.scope.get(id.name));
815
+ binding.blocker = blocker;
816
+ }
817
+
818
+ for (let node of instance.ast.body) {
819
+ if (node.type === 'ImportDeclaration') {
820
+ analysis.instance_body.hoisted.push(node);
821
+ continue;
822
+ }
823
+
824
+ if (node.type === 'ExportDefaultDeclaration' || node.type === 'ExportAllDeclaration') {
825
+ // these can't exist inside `<script>` but TypeScript doesn't know that
826
+ continue;
827
+ }
828
+
829
+ if (node.type === 'ExportNamedDeclaration') {
830
+ if (node.declaration) {
831
+ node = node.declaration;
832
+ } else {
833
+ continue;
834
+ }
835
+ }
836
+
837
+ const has_await = has_await_expression(node);
838
+ awaited ||= has_await;
839
+
840
+ if (awaited && node.type !== 'FunctionDeclaration') {
841
+ /** @type {Set<Binding>} */
842
+ const reads = new Set(); // TODO we're not actually using this yet
843
+
844
+ /** @type {Set<Binding>} */
845
+ const writes = new Set();
846
+
847
+ trace_references(node, reads, writes);
848
+
849
+ const blocker = b.member(promises, b.literal(analysis.instance_body.async.length), true);
850
+
851
+ for (const binding of writes) {
852
+ binding.blocker = blocker;
853
+ }
854
+
855
+ if (node.type === 'VariableDeclaration') {
856
+ for (const declarator of node.declarations) {
857
+ for (const id of extract_identifiers(declarator.id)) {
858
+ push_declaration(id, blocker);
859
+ }
860
+
861
+ // one declarator per declaration, makes things simpler
862
+ analysis.instance_body.async.push({
863
+ node: declarator,
864
+ has_await
865
+ });
866
+ }
867
+ } else if (node.type === 'ClassDeclaration') {
868
+ push_declaration(node.id, blocker);
869
+ analysis.instance_body.async.push({ node, has_await });
870
+ } else {
871
+ analysis.instance_body.async.push({ node, has_await });
872
+ }
873
+ } else {
874
+ analysis.instance_body.sync.push(node);
875
+ }
876
+ }
877
+
679
878
  if (analysis.runes) {
680
879
  const props_refs = module.scope.references.get('$$props');
681
880
  if (props_refs) {
@@ -10,16 +10,13 @@ import * as e from '../../../errors.js';
10
10
  export function AwaitExpression(node, context) {
11
11
  const tla = context.state.ast_type === 'instance' && context.state.function_depth === 1;
12
12
 
13
- // preserve context for
14
- // a) top-level await and
15
- // b) awaits that precede other expressions in template or `$derived(...)`
13
+ // preserve context for awaits that precede other expressions in template or `$derived(...)`
16
14
  if (
17
- tla ||
18
- (is_reactive_expression(
15
+ is_reactive_expression(
19
16
  context.path,
20
17
  context.state.derived_function_depth === context.state.function_depth
21
18
  ) &&
22
- !is_last_evaluated_expression(context.path, node))
19
+ !is_last_evaluated_expression(context.path, node)
23
20
  ) {
24
21
  context.state.analysis.pickled_awaits.add(node);
25
22
  }
@@ -145,6 +142,9 @@ function is_last_evaluated_expression(path, node) {
145
142
  if (node !== parent.expressions.at(-1)) return false;
146
143
  break;
147
144
 
145
+ case 'VariableDeclarator':
146
+ return true;
147
+
148
148
  default:
149
149
  return false;
150
150
  }
@@ -172,6 +172,7 @@ export function BindDirective(node, context) {
172
172
  }
173
173
 
174
174
  const binding = context.state.scope.get(left.name);
175
+ node.metadata.binding = binding;
175
176
 
176
177
  if (assignee.type === 'Identifier') {
177
178
  // reassignment
@@ -81,8 +81,13 @@ export function SnippetBlock(node, context) {
81
81
  function can_hoist_snippet(scope, scopes, visited = new Set()) {
82
82
  for (const [reference] of scope.references) {
83
83
  const binding = scope.get(reference);
84
+ if (!binding) continue;
84
85
 
85
- if (!binding || binding.scope.function_depth === 0) {
86
+ if (binding.blocker) {
87
+ return false;
88
+ }
89
+
90
+ if (binding.scope.function_depth === 0) {
86
91
  continue;
87
92
  }
88
93
 
@@ -33,7 +33,6 @@ import { FunctionExpression } from './visitors/FunctionExpression.js';
33
33
  import { HtmlTag } from './visitors/HtmlTag.js';
34
34
  import { Identifier } from './visitors/Identifier.js';
35
35
  import { IfBlock } from './visitors/IfBlock.js';
36
- import { ImportDeclaration } from './visitors/ImportDeclaration.js';
37
36
  import { KeyBlock } from './visitors/KeyBlock.js';
38
37
  import { LabeledStatement } from './visitors/LabeledStatement.js';
39
38
  import { LetDirective } from './visitors/LetDirective.js';
@@ -111,7 +110,6 @@ const visitors = {
111
110
  HtmlTag,
112
111
  Identifier,
113
112
  IfBlock,
114
- ImportDeclaration,
115
113
  KeyBlock,
116
114
  LabeledStatement,
117
115
  LetDirective,
@@ -153,7 +151,7 @@ export function client_component(analysis, options) {
153
151
  scope: analysis.module.scope,
154
152
  scopes: analysis.module.scopes,
155
153
  is_instance: false,
156
- hoisted: [b.import_all('$', 'svelte/internal/client')],
154
+ hoisted: [b.import_all('$', 'svelte/internal/client'), ...analysis.instance_body.hoisted],
157
155
  node: /** @type {any} */ (null), // populated by the root node
158
156
  legacy_reactive_imports: [],
159
157
  legacy_reactive_statements: new Map(),
@@ -370,41 +368,22 @@ export function client_component(analysis, options) {
370
368
  analysis.reactive_statements.size > 0 ||
371
369
  component_returned_object.length > 0;
372
370
 
373
- if (analysis.instance.has_await) {
374
- if (should_inject_context && component_returned_object.length > 0) {
375
- component_block.body.push(b.var('$$exports'));
376
- }
377
- const body = b.block([
378
- ...store_setup,
379
- ...state.instance_level_snippets,
380
- .../** @type {ESTree.Statement[]} */ (instance.body),
381
- ...(should_inject_context && component_returned_object.length > 0
382
- ? [b.stmt(b.assignment('=', b.id('$$exports'), b.object(component_returned_object)))]
383
- : []),
384
- b.if(b.call('$.aborted'), b.return()),
385
- .../** @type {ESTree.Statement[]} */ (template.body)
386
- ]);
387
-
388
- component_block.body.push(
389
- b.stmt(b.call(`$.async_body`, b.id('$$anchor'), b.arrow([b.id('$$anchor')], body, true)))
390
- );
391
- } else {
392
- component_block.body.push(
393
- ...state.instance_level_snippets,
394
- .../** @type {ESTree.Statement[]} */ (instance.body)
395
- );
396
- if (should_inject_context && component_returned_object.length > 0) {
397
- component_block.body.push(b.var('$$exports', b.object(component_returned_object)));
398
- }
399
- component_block.body.unshift(...store_setup);
371
+ component_block.body.push(
372
+ ...state.instance_level_snippets,
373
+ .../** @type {ESTree.Statement[]} */ (instance.body)
374
+ );
400
375
 
401
- if (!analysis.runes && analysis.needs_context) {
402
- component_block.body.push(b.stmt(b.call('$.init', analysis.immutable ? b.true : undefined)));
403
- }
376
+ if (should_inject_context && component_returned_object.length > 0) {
377
+ component_block.body.push(b.var('$$exports', b.object(component_returned_object)));
378
+ }
379
+ component_block.body.unshift(...store_setup);
404
380
 
405
- component_block.body.push(.../** @type {ESTree.Statement[]} */ (template.body));
381
+ if (!analysis.runes && analysis.needs_context) {
382
+ component_block.body.push(b.stmt(b.call('$.init', analysis.immutable ? b.true : undefined)));
406
383
  }
407
384
 
385
+ component_block.body.push(.../** @type {ESTree.Statement[]} */ (template.body));
386
+
408
387
  if (analysis.needs_mutation_validation) {
409
388
  component_block.body.unshift(
410
389
  b.var('$$ownership_validator', b.call('$.create_ownership_validator', b.id('$$props')))
@@ -243,18 +243,29 @@ export function BindDirective(node, context) {
243
243
  }
244
244
  }
245
245
 
246
+ const defer =
247
+ node.name !== 'this' &&
248
+ parent.type === 'RegularElement' &&
249
+ parent.attributes.find((a) => a.type === 'UseDirective');
250
+
251
+ let statement = defer ? b.stmt(b.call('$.effect', b.thunk(call))) : b.stmt(call);
252
+
253
+ // TODO this doesn't account for function bindings
254
+ if (node.metadata.binding?.blocker) {
255
+ statement = b.stmt(
256
+ b.call(b.member(node.metadata.binding.blocker, b.id('then')), b.thunk(b.block([statement])))
257
+ );
258
+ }
259
+
246
260
  // Bindings need to happen after attribute updates, therefore after the render effect, and in order with events/actions.
247
261
  // bind:this is a special case as it's one-way and could influence the render effect.
248
262
  if (node.name === 'this') {
249
- context.state.init.push(b.stmt(call));
263
+ context.state.init.push(statement);
250
264
  } else {
251
- const has_use =
252
- parent.type === 'RegularElement' && parent.attributes.find((a) => a.type === 'UseDirective');
253
-
254
- if (has_use) {
255
- context.state.init.push(b.stmt(b.call('$.effect', b.thunk(call))));
265
+ if (defer) {
266
+ context.state.init.push(statement);
256
267
  } else {
257
- context.state.after_update.push(b.stmt(call));
268
+ context.state.after_update.push(statement);
258
269
  }
259
270
  }
260
271
  }
@@ -1,4 +1,4 @@
1
- /** @import { CallExpression, Expression, MemberExpression } from 'estree' */
1
+ /** @import { CallExpression, Expression } from 'estree' */
2
2
  /** @import { Context } from '../types' */
3
3
  import { dev, is_ignored } from '../../../../state.js';
4
4
  import * as b from '#compiler/builders';
@@ -80,7 +80,7 @@ export function CallExpression(node, context) {
80
80
  );
81
81
 
82
82
  case '$effect.pending':
83
- return b.call('$.pending');
83
+ return b.call('$.eager', b.thunk(b.call('$.pending')));
84
84
 
85
85
  case '$inspect':
86
86
  case '$inspect().with':
@@ -312,9 +312,10 @@ export function EachBlock(node, context) {
312
312
  declarations.push(b.let(node.index, index));
313
313
  }
314
314
 
315
- const { has_await } = node.metadata.expression;
316
- const get_collection = b.thunk(collection, has_await);
317
- const thunk = has_await ? b.thunk(b.call('$.get', b.id('$$collection'))) : get_collection;
315
+ const is_async = node.metadata.expression.is_async();
316
+
317
+ const get_collection = b.thunk(collection, node.metadata.expression.has_await);
318
+ const thunk = is_async ? b.thunk(b.call('$.get', b.id('$$collection'))) : get_collection;
318
319
 
319
320
  const render_args = [b.id('$$anchor'), item];
320
321
  if (uses_index || collection_id) render_args.push(index);
@@ -341,12 +342,13 @@ export function EachBlock(node, context) {
341
342
  statements.unshift(b.stmt(b.call('$.validate_each_keys', thunk, key_function)));
342
343
  }
343
344
 
344
- if (has_await) {
345
+ if (is_async) {
345
346
  context.state.init.push(
346
347
  b.stmt(
347
348
  b.call(
348
349
  '$.async',
349
350
  context.state.node,
351
+ node.metadata.expression.blockers(),
350
352
  b.array([get_collection]),
351
353
  b.arrow([context.state.node, b.id('$$collection')], b.block(statements))
352
354
  )
@@ -1,4 +1,4 @@
1
- /** @import { Expression, ExpressionStatement } from 'estree' */
1
+ /** @import { ExpressionStatement } from 'estree' */
2
2
  /** @import { ComponentContext } from '../types' */
3
3
  import * as b from '#compiler/builders';
4
4
  import { get_rune } from '../../../scope.js';
@@ -11,9 +11,10 @@ import { build_expression } from './shared/utils.js';
11
11
  export function HtmlTag(node, context) {
12
12
  context.state.template.push_comment();
13
13
 
14
- const { has_await } = node.metadata.expression;
14
+ const is_async = node.metadata.expression.is_async();
15
+
15
16
  const expression = build_expression(context, node.expression, node.metadata.expression);
16
- const html = has_await ? b.call('$.get', b.id('$$html')) : expression;
17
+ const html = is_async ? b.call('$.get', b.id('$$html')) : expression;
17
18
 
18
19
  const is_svg = context.state.metadata.namespace === 'svg';
19
20
  const is_mathml = context.state.metadata.namespace === 'mathml';
@@ -30,13 +31,14 @@ export function HtmlTag(node, context) {
30
31
  );
31
32
 
32
33
  // push into init, so that bindings run afterwards, which might trigger another run and override hydration
33
- if (node.metadata.expression.has_await) {
34
+ if (is_async) {
34
35
  context.state.init.push(
35
36
  b.stmt(
36
37
  b.call(
37
38
  '$.async',
38
39
  context.state.node,
39
- b.array([b.thunk(expression, true)]),
40
+ node.metadata.expression.blockers(),
41
+ b.array([b.thunk(expression, node.metadata.expression.has_await)]),
40
42
  b.arrow([context.state.node, b.id('$$html')], b.block([statement]))
41
43
  )
42
44
  )
@@ -25,9 +25,10 @@ export function IfBlock(node, context) {
25
25
  statements.push(b.var(alternate_id, b.arrow([b.id('$$anchor')], alternate)));
26
26
  }
27
27
 
28
- const { has_await } = node.metadata.expression;
28
+ const is_async = node.metadata.expression.is_async();
29
+
29
30
  const expression = build_expression(context, node.test, node.metadata.expression);
30
- const test = has_await ? b.call('$.get', b.id('$$condition')) : expression;
31
+ const test = is_async ? b.call('$.get', b.id('$$condition')) : expression;
31
32
 
32
33
  /** @type {Expression[]} */
33
34
  const args = [
@@ -71,13 +72,14 @@ export function IfBlock(node, context) {
71
72
 
72
73
  statements.push(add_svelte_meta(b.call('$.if', ...args), node, 'if'));
73
74
 
74
- if (has_await) {
75
+ if (is_async) {
75
76
  context.state.init.push(
76
77
  b.stmt(
77
78
  b.call(
78
79
  '$.async',
79
80
  context.state.node,
80
- b.array([b.thunk(expression, true)]),
81
+ node.metadata.expression.blockers(),
82
+ b.array([b.thunk(expression, node.metadata.expression.has_await)]),
81
83
  b.arrow([context.state.node, b.id('$$condition')], b.block(statements))
82
84
  )
83
85
  )
@@ -11,10 +11,10 @@ import { build_expression, add_svelte_meta } from './shared/utils.js';
11
11
  export function KeyBlock(node, context) {
12
12
  context.state.template.push_comment();
13
13
 
14
- const { has_await } = node.metadata.expression;
14
+ const is_async = node.metadata.expression.is_async();
15
15
 
16
16
  const expression = build_expression(context, node.expression, node.metadata.expression);
17
- const key = b.thunk(has_await ? b.call('$.get', b.id('$$key')) : expression);
17
+ const key = b.thunk(is_async ? b.call('$.get', b.id('$$key')) : expression);
18
18
  const body = /** @type {Expression} */ (context.visit(node.fragment));
19
19
 
20
20
  let statement = add_svelte_meta(
@@ -23,12 +23,13 @@ export function KeyBlock(node, context) {
23
23
  'key'
24
24
  );
25
25
 
26
- if (has_await) {
26
+ if (is_async) {
27
27
  statement = b.stmt(
28
28
  b.call(
29
29
  '$.async',
30
30
  context.state.node,
31
- b.array([b.thunk(expression, true)]),
31
+ node.metadata.expression.blockers(),
32
+ b.array([b.thunk(expression, node.metadata.expression.has_await)]),
32
33
  b.arrow([context.state.node, b.id('$$key')], b.block([statement]))
33
34
  )
34
35
  );
@@ -1,14 +1,15 @@
1
- /** @import { Expression, ImportDeclaration, MemberExpression, Program } from 'estree' */
1
+ /** @import { Expression, ImportDeclaration, MemberExpression, Node, Program } from 'estree' */
2
2
  /** @import { ComponentContext } from '../types' */
3
3
  import { build_getter, is_prop_source } from '../utils.js';
4
4
  import * as b from '#compiler/builders';
5
5
  import { add_state_transformers } from './shared/declarations.js';
6
+ import { transform_body } from '../../shared/transform-async.js';
6
7
 
7
8
  /**
8
- * @param {Program} _
9
+ * @param {Program} node
9
10
  * @param {ComponentContext} context
10
11
  */
11
- export function Program(_, context) {
12
+ export function Program(node, context) {
12
13
  if (!context.state.analysis.runes) {
13
14
  context.state.transform['$$props'] = {
14
15
  read: (node) => ({ ...node, name: '$$sanitized_props' })
@@ -137,5 +138,16 @@ export function Program(_, context) {
137
138
 
138
139
  add_state_transformers(context);
139
140
 
141
+ if (context.state.is_instance) {
142
+ return {
143
+ ...node,
144
+ body: transform_body(
145
+ context.state.analysis.instance_body,
146
+ b.id('$.run'),
147
+ (node) => /** @type {Node} */ (context.visit(node))
148
+ )
149
+ };
150
+ }
151
+
140
152
  context.next();
141
153
  }
@@ -22,11 +22,11 @@ export function RenderTag(node, context) {
22
22
  for (let i = 0; i < call.arguments.length; i++) {
23
23
  const arg = /** @type {Expression} */ (call.arguments[i]);
24
24
  const metadata = node.metadata.arguments[i];
25
-
26
25
  let expression = build_expression(context, arg, metadata);
26
+ const memoized = memoizer.add(expression, metadata);
27
27
 
28
- if (metadata.has_await || metadata.has_call) {
29
- expression = b.call('$.get', memoizer.add(expression, metadata));
28
+ if (expression !== memoized) {
29
+ expression = b.call('$.get', memoized);
30
30
  }
31
31
 
32
32
  args.push(b.thunk(expression));
@@ -71,13 +71,15 @@ export function RenderTag(node, context) {
71
71
  }
72
72
 
73
73
  const async_values = memoizer.async_values();
74
+ const blockers = memoizer.blockers();
74
75
 
75
- if (async_values) {
76
+ if (async_values || blockers) {
76
77
  context.state.init.push(
77
78
  b.stmt(
78
79
  b.call(
79
80
  '$.async',
80
81
  context.state.node,
82
+ blockers,
81
83
  memoizer.async_values(),
82
84
  b.arrow([context.state.node, ...memoizer.async_ids()], b.block(statements))
83
85
  )
@@ -74,13 +74,15 @@ export function SlotElement(node, context) {
74
74
  );
75
75
 
76
76
  const async_values = memoizer.async_values();
77
+ const blockers = memoizer.blockers();
77
78
 
78
- if (async_values) {
79
+ if (async_values || blockers) {
79
80
  context.state.init.push(
80
81
  b.stmt(
81
82
  b.call(
82
83
  '$.async',
83
84
  context.state.node,
85
+ blockers,
84
86
  async_values,
85
87
  b.arrow([context.state.node, ...memoizer.async_ids()], b.block(statements))
86
88
  )
@@ -93,10 +93,10 @@ export function SvelteElement(node, context) {
93
93
  );
94
94
  }
95
95
 
96
- const { has_await } = node.metadata.expression;
96
+ const is_async = node.metadata.expression.is_async();
97
97
 
98
98
  const expression = /** @type {Expression} */ (context.visit(node.tag));
99
- const get_tag = b.thunk(has_await ? b.call('$.get', b.id('$$tag')) : expression);
99
+ const get_tag = b.thunk(is_async ? b.call('$.get', b.id('$$tag')) : expression);
100
100
 
101
101
  /** @type {Statement[]} */
102
102
  const inner = inner_context.state.init;
@@ -139,13 +139,14 @@ export function SvelteElement(node, context) {
139
139
  )
140
140
  );
141
141
 
142
- if (has_await) {
142
+ if (is_async) {
143
143
  context.state.init.push(
144
144
  b.stmt(
145
145
  b.call(
146
146
  '$.async',
147
147
  context.state.node,
148
- b.array([b.thunk(expression, true)]),
148
+ node.metadata.expression.blockers(),
149
+ b.array([b.thunk(expression, node.metadata.expression.has_await)]),
149
150
  b.arrow([context.state.node, b.id('$$tag')], b.block(statements))
150
151
  )
151
152
  )