svelte 5.42.1 → 5.42.3

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 (31) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/compiler/phases/1-parse/state/element.js +8 -9
  4. package/src/compiler/phases/1-parse/state/tag.js +9 -9
  5. package/src/compiler/phases/2-analyze/index.js +6 -6
  6. package/src/compiler/phases/2-analyze/visitors/CallExpression.js +2 -2
  7. package/src/compiler/phases/2-analyze/visitors/RenderTag.js +2 -2
  8. package/src/compiler/phases/2-analyze/visitors/shared/utils.js +4 -1
  9. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +11 -0
  10. package/src/compiler/phases/3-transform/client/visitors/ExpressionStatement.js +0 -10
  11. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +27 -19
  12. package/src/compiler/phases/3-transform/client/visitors/RenderTag.js +1 -1
  13. package/src/compiler/phases/3-transform/client/visitors/SlotElement.js +1 -1
  14. package/src/compiler/phases/3-transform/client/visitors/SvelteHead.js +3 -0
  15. package/src/compiler/phases/3-transform/client/visitors/shared/component.js +3 -3
  16. package/src/compiler/phases/3-transform/client/visitors/shared/element.js +7 -10
  17. package/src/compiler/phases/3-transform/client/visitors/shared/events.js +2 -1
  18. package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +15 -6
  19. package/src/compiler/phases/3-transform/server/transform-server.js +12 -12
  20. package/src/compiler/phases/3-transform/server/visitors/SvelteHead.js +10 -1
  21. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +4 -8
  22. package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +3 -2
  23. package/src/compiler/phases/nodes.js +27 -14
  24. package/src/compiler/phases/scope.js +2 -2
  25. package/src/internal/client/dom/blocks/await.js +6 -2
  26. package/src/internal/client/dom/blocks/svelte-head.js +10 -19
  27. package/src/internal/client/reactivity/async.js +2 -3
  28. package/src/internal/client/render.js +1 -9
  29. package/src/internal/server/index.js +4 -3
  30. package/src/version.js +1 -1
  31. package/types/index.d.ts.map +1 -1
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.1",
5
+ "version": "5.42.3",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -9,11 +9,10 @@ import { decode_character_references } from '../utils/html.js';
9
9
  import * as e from '../../../errors.js';
10
10
  import * as w from '../../../warnings.js';
11
11
  import { create_fragment } from '../utils/create.js';
12
- import { create_attribute, create_expression_metadata, is_element_node } from '../../nodes.js';
12
+ import { create_attribute, ExpressionMetadata, is_element_node } from '../../nodes.js';
13
13
  import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js';
14
14
  import { closing_tag_omitted } from '../../../../html-tree-validation.js';
15
15
  import { list } from '../../../utils/string.js';
16
- import { regex_whitespace } from '../../patterns.js';
17
16
 
18
17
  const regex_invalid_unquoted_attribute_value = /^(\/>|[\s"'=<>`])/;
19
18
  const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
@@ -297,7 +296,7 @@ export default function element(parser) {
297
296
  element.tag = get_attribute_expression(definition);
298
297
  }
299
298
 
300
- element.metadata.expression = create_expression_metadata();
299
+ element.metadata.expression = new ExpressionMetadata();
301
300
  }
302
301
 
303
302
  if (is_top_level_script_or_style) {
@@ -508,7 +507,7 @@ function read_attribute(parser) {
508
507
  end: parser.index,
509
508
  expression,
510
509
  metadata: {
511
- expression: create_expression_metadata()
510
+ expression: new ExpressionMetadata()
512
511
  }
513
512
  };
514
513
 
@@ -528,7 +527,7 @@ function read_attribute(parser) {
528
527
  end: parser.index,
529
528
  expression,
530
529
  metadata: {
531
- expression: create_expression_metadata()
530
+ expression: new ExpressionMetadata()
532
531
  }
533
532
  };
534
533
 
@@ -568,7 +567,7 @@ function read_attribute(parser) {
568
567
  name
569
568
  },
570
569
  metadata: {
571
- expression: create_expression_metadata()
570
+ expression: new ExpressionMetadata()
572
571
  }
573
572
  };
574
573
 
@@ -628,7 +627,7 @@ function read_attribute(parser) {
628
627
  modifiers: /** @type {Array<'important'>} */ (modifiers),
629
628
  value,
630
629
  metadata: {
631
- expression: create_expression_metadata()
630
+ expression: new ExpressionMetadata()
632
631
  }
633
632
  };
634
633
  }
@@ -658,7 +657,7 @@ function read_attribute(parser) {
658
657
  name: directive_name,
659
658
  expression,
660
659
  metadata: {
661
- expression: create_expression_metadata()
660
+ expression: new ExpressionMetadata()
662
661
  }
663
662
  };
664
663
 
@@ -824,7 +823,7 @@ function read_sequence(parser, done, location) {
824
823
  end: parser.index,
825
824
  expression,
826
825
  metadata: {
827
- expression: create_expression_metadata()
826
+ expression: new ExpressionMetadata()
828
827
  }
829
828
  };
830
829
 
@@ -3,7 +3,7 @@
3
3
  /** @import { Parser } from '../index.js' */
4
4
  import { walk } from 'zimmerframe';
5
5
  import * as e from '../../../errors.js';
6
- import { create_expression_metadata } from '../../nodes.js';
6
+ import { ExpressionMetadata } from '../../nodes.js';
7
7
  import { parse_expression_at } from '../acorn.js';
8
8
  import read_pattern from '../read/context.js';
9
9
  import read_expression, { get_loose_identifier } from '../read/expression.js';
@@ -42,7 +42,7 @@ export default function tag(parser) {
42
42
  end: parser.index,
43
43
  expression,
44
44
  metadata: {
45
- expression: create_expression_metadata()
45
+ expression: new ExpressionMetadata()
46
46
  }
47
47
  });
48
48
  }
@@ -65,7 +65,7 @@ function open(parser) {
65
65
  consequent: create_fragment(),
66
66
  alternate: null,
67
67
  metadata: {
68
- expression: create_expression_metadata()
68
+ expression: new ExpressionMetadata()
69
69
  }
70
70
  });
71
71
 
@@ -249,7 +249,7 @@ function open(parser) {
249
249
  then: null,
250
250
  catch: null,
251
251
  metadata: {
252
- expression: create_expression_metadata()
252
+ expression: new ExpressionMetadata()
253
253
  }
254
254
  });
255
255
 
@@ -334,7 +334,7 @@ function open(parser) {
334
334
  expression,
335
335
  fragment: create_fragment(),
336
336
  metadata: {
337
- expression: create_expression_metadata()
337
+ expression: new ExpressionMetadata()
338
338
  }
339
339
  });
340
340
 
@@ -477,7 +477,7 @@ function next(parser) {
477
477
  consequent: create_fragment(),
478
478
  alternate: null,
479
479
  metadata: {
480
- expression: create_expression_metadata()
480
+ expression: new ExpressionMetadata()
481
481
  }
482
482
  });
483
483
 
@@ -643,7 +643,7 @@ function special(parser) {
643
643
  end: parser.index,
644
644
  expression,
645
645
  metadata: {
646
- expression: create_expression_metadata()
646
+ expression: new ExpressionMetadata()
647
647
  }
648
648
  });
649
649
 
@@ -721,7 +721,7 @@ function special(parser) {
721
721
  end: parser.index - 1
722
722
  },
723
723
  metadata: {
724
- expression: create_expression_metadata()
724
+ expression: new ExpressionMetadata()
725
725
  }
726
726
  });
727
727
  }
@@ -748,7 +748,7 @@ function special(parser) {
748
748
  end: parser.index,
749
749
  expression: /** @type {AST.RenderTag['expression']} */ (expression),
750
750
  metadata: {
751
- expression: create_expression_metadata(),
751
+ expression: new ExpressionMetadata(),
752
752
  dynamic: false,
753
753
  arguments: [],
754
754
  path: [],
@@ -1,4 +1,4 @@
1
- /** @import { Expression, Node, Program } from 'estree' */
1
+ /** @import * as ESTree from 'estree' */
2
2
  /** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
3
3
  /** @import { AnalysisState, Visitors } from './types' */
4
4
  /** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */
@@ -206,7 +206,7 @@ const visitors = {
206
206
  * @returns {Js}
207
207
  */
208
208
  function js(script, root, allow_reactive_declarations, parent) {
209
- /** @type {Program} */
209
+ /** @type {ESTree.Program} */
210
210
  const ast = script?.content ?? {
211
211
  type: 'Program',
212
212
  sourceType: 'module',
@@ -289,7 +289,7 @@ export function analyze_module(source, options) {
289
289
  });
290
290
 
291
291
  walk(
292
- /** @type {Node} */ (ast),
292
+ /** @type {ESTree.Node} */ (ast),
293
293
  {
294
294
  scope,
295
295
  scopes,
@@ -347,7 +347,7 @@ export function analyze_component(root, source, options) {
347
347
 
348
348
  const store_name = name.slice(1);
349
349
  const declaration = instance.scope.get(store_name);
350
- const init = /** @type {Node | undefined} */ (declaration?.initial);
350
+ const init = /** @type {ESTree.Node | undefined} */ (declaration?.initial);
351
351
 
352
352
  // If we're not in legacy mode through the compiler option, assume the user
353
353
  // is referencing a rune and not a global store.
@@ -407,7 +407,7 @@ export function analyze_component(root, source, options) {
407
407
  /** @type {number} */ (node.start) > /** @type {number} */ (module.ast.start) &&
408
408
  /** @type {number} */ (node.end) < /** @type {number} */ (module.ast.end) &&
409
409
  // const state = $state(0) is valid
410
- get_rune(/** @type {Node} */ (path.at(-1)), module.scope) === null
410
+ get_rune(/** @type {ESTree.Node} */ (path.at(-1)), module.scope) === null
411
411
  ) {
412
412
  e.store_invalid_subscription(node);
413
413
  }
@@ -636,7 +636,7 @@ export function analyze_component(root, source, options) {
636
636
  // @ts-expect-error
637
637
  _: set_scope,
638
638
  Identifier(node, context) {
639
- const parent = /** @type {Expression} */ (context.path.at(-1));
639
+ const parent = /** @type {ESTree.Expression} */ (context.path.at(-1));
640
640
 
641
641
  if (is_reference(node, parent)) {
642
642
  const binding = context.state.scope.get(node.name);
@@ -7,7 +7,7 @@ import { get_parent } from '../../../utils/ast.js';
7
7
  import { is_pure, is_safe_identifier } from './shared/utils.js';
8
8
  import { dev, locate_node, source } from '../../../state.js';
9
9
  import * as b from '#compiler/builders';
10
- import { create_expression_metadata } from '../../nodes.js';
10
+ import { ExpressionMetadata } from '../../nodes.js';
11
11
 
12
12
  /**
13
13
  * @param {CallExpression} node
@@ -243,7 +243,7 @@ export function CallExpression(node, context) {
243
243
 
244
244
  // `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning
245
245
  if (rune === '$derived') {
246
- const expression = create_expression_metadata();
246
+ const expression = new ExpressionMetadata();
247
247
 
248
248
  context.next({
249
249
  ...context.state,
@@ -5,7 +5,7 @@ import * as e from '../../../errors.js';
5
5
  import { validate_opening_tag } from './shared/utils.js';
6
6
  import { mark_subtree_dynamic } from './shared/fragment.js';
7
7
  import { is_resolved_snippet } from './shared/snippets.js';
8
- import { create_expression_metadata } from '../../nodes.js';
8
+ import { ExpressionMetadata } from '../../nodes.js';
9
9
 
10
10
  /**
11
11
  * @param {AST.RenderTag} node
@@ -57,7 +57,7 @@ export function RenderTag(node, context) {
57
57
  context.visit(callee, { ...context.state, expression: node.metadata.expression });
58
58
 
59
59
  for (const arg of expression.arguments) {
60
- const metadata = create_expression_metadata();
60
+ const metadata = new ExpressionMetadata();
61
61
  node.metadata.arguments.push(metadata);
62
62
 
63
63
  context.visit(arg, {
@@ -22,7 +22,10 @@ export function validate_assignment(node, argument, context) {
22
22
  const binding = context.state.scope.get(argument.name);
23
23
 
24
24
  if (context.state.analysis.runes) {
25
- if (binding?.node === context.state.analysis.props_id) {
25
+ if (
26
+ context.state.analysis.props_id != null &&
27
+ binding?.node === context.state.analysis.props_id
28
+ ) {
26
29
  e.constant_assignment(node, '$props.id()');
27
30
  }
28
31
 
@@ -62,6 +62,17 @@ export function CallExpression(node, context) {
62
62
  is_ignored(node, 'state_snapshot_uncloneable') && b.true
63
63
  );
64
64
 
65
+ case '$effect':
66
+ case '$effect.pre': {
67
+ const callee = rune === '$effect' ? '$.user_effect' : '$.user_pre_effect';
68
+ const func = /** @type {Expression} */ (context.visit(node.arguments[0]));
69
+
70
+ const expr = b.call(callee, /** @type {Expression} */ (func));
71
+ expr.callee.loc = node.callee.loc; // ensure correct mapping
72
+
73
+ return expr;
74
+ }
75
+
65
76
  case '$effect.root':
66
77
  return b.call(
67
78
  '$.effect_root',
@@ -11,16 +11,6 @@ export function ExpressionStatement(node, context) {
11
11
  if (node.expression.type === 'CallExpression') {
12
12
  const rune = get_rune(node.expression, context.state.scope);
13
13
 
14
- if (rune === '$effect' || rune === '$effect.pre') {
15
- const callee = rune === '$effect' ? '$.user_effect' : '$.user_pre_effect';
16
- const func = /** @type {Expression} */ (context.visit(node.expression.arguments[0]));
17
-
18
- const expr = b.call(callee, /** @type {Expression} */ (func));
19
- expr.callee.loc = node.expression.callee.loc; // ensure correct mapping
20
-
21
- return b.stmt(expr);
22
- }
23
-
24
14
  if (rune === '$inspect.trace') {
25
15
  return b.empty;
26
16
  }
@@ -11,7 +11,7 @@ import {
11
11
  import { is_ignored } from '../../../../state.js';
12
12
  import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js';
13
13
  import * as b from '#compiler/builders';
14
- import { create_attribute, is_custom_element_node } from '../../../nodes.js';
14
+ import { create_attribute, ExpressionMetadata, is_custom_element_node } from '../../../nodes.js';
15
15
  import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
16
16
  import { build_getter } from '../utils.js';
17
17
  import {
@@ -267,10 +267,7 @@ export function RegularElement(node, context) {
267
267
  const { value, has_state } = build_attribute_value(
268
268
  attribute.value,
269
269
  context,
270
- (value, metadata) =>
271
- metadata.has_call || metadata.has_await
272
- ? context.state.memoizer.add(value, metadata.has_await)
273
- : value
270
+ (value, metadata) => context.state.memoizer.add(value, metadata)
274
271
  );
275
272
 
276
273
  const update = build_element_attribute_update(node, node_id, name, value, attributes);
@@ -487,11 +484,25 @@ function setup_select_synchronization(value_binding, context) {
487
484
  );
488
485
  }
489
486
 
487
+ /**
488
+ * @param {ExpressionMetadata} target
489
+ * @param {ExpressionMetadata} source
490
+ */
491
+ function merge_metadata(target, source) {
492
+ target.has_assignment ||= source.has_assignment;
493
+ target.has_await ||= source.has_await;
494
+ target.has_call ||= source.has_call;
495
+ target.has_member_expression ||= source.has_member_expression;
496
+ target.has_state ||= source.has_state;
497
+
498
+ for (const r of source.references) target.references.add(r);
499
+ for (const b of source.dependencies) target.dependencies.add(b);
500
+ }
501
+
490
502
  /**
491
503
  * @param {AST.ClassDirective[]} class_directives
492
504
  * @param {ComponentContext} context
493
505
  * @param {Memoizer} memoizer
494
- * @return {ObjectExpression | Identifier}
495
506
  */
496
507
  export function build_class_directives_object(
497
508
  class_directives,
@@ -499,26 +510,25 @@ export function build_class_directives_object(
499
510
  memoizer = context.state.memoizer
500
511
  ) {
501
512
  let properties = [];
502
- let has_call_or_state = false;
503
- let has_await = false;
513
+
514
+ const metadata = new ExpressionMetadata();
504
515
 
505
516
  for (const d of class_directives) {
517
+ merge_metadata(metadata, d.metadata.expression);
518
+
506
519
  const expression = /** @type Expression */ (context.visit(d.expression));
507
520
  properties.push(b.init(d.name, expression));
508
- has_call_or_state ||= d.metadata.expression.has_call || d.metadata.expression.has_state;
509
- has_await ||= d.metadata.expression.has_await;
510
521
  }
511
522
 
512
523
  const directives = b.object(properties);
513
524
 
514
- return has_call_or_state || has_await ? memoizer.add(directives, has_await) : directives;
525
+ return memoizer.add(directives, metadata);
515
526
  }
516
527
 
517
528
  /**
518
529
  * @param {AST.StyleDirective[]} style_directives
519
530
  * @param {ComponentContext} context
520
531
  * @param {Memoizer} memoizer
521
- * @return {ObjectExpression | ArrayExpression | Identifier}}
522
532
  */
523
533
  export function build_style_directives_object(
524
534
  style_directives,
@@ -528,10 +538,11 @@ export function build_style_directives_object(
528
538
  const normal = b.object([]);
529
539
  const important = b.object([]);
530
540
 
531
- let has_call_or_state = false;
532
- let has_await = false;
541
+ const metadata = new ExpressionMetadata();
533
542
 
534
543
  for (const d of style_directives) {
544
+ merge_metadata(metadata, d.metadata.expression);
545
+
535
546
  const expression =
536
547
  d.value === true
537
548
  ? build_getter(b.id(d.name), context.state)
@@ -539,14 +550,11 @@ export function build_style_directives_object(
539
550
 
540
551
  const object = d.modifiers.includes('important') ? important : normal;
541
552
  object.properties.push(b.init(d.name, expression));
542
-
543
- has_call_or_state ||= d.metadata.expression.has_call || d.metadata.expression.has_state;
544
- has_await ||= d.metadata.expression.has_await;
545
553
  }
546
554
 
547
555
  const directives = important.properties.length ? b.array([normal, important]) : normal;
548
556
 
549
- return has_call_or_state || has_await ? memoizer.add(directives, has_await) : directives;
557
+ return memoizer.add(directives, metadata);
550
558
  }
551
559
 
552
560
  /**
@@ -675,7 +683,7 @@ function build_element_special_value_attribute(
675
683
  element === 'select' && attribute.value !== true && !is_text_attribute(attribute);
676
684
 
677
685
  const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
678
- metadata.has_call || metadata.has_await ? state.memoizer.add(value, metadata.has_await) : value
686
+ state.memoizer.add(value, metadata)
679
687
  );
680
688
 
681
689
  const evaluated = context.state.scope.evaluate(value);
@@ -26,7 +26,7 @@ export function RenderTag(node, context) {
26
26
  let expression = build_expression(context, arg, metadata);
27
27
 
28
28
  if (metadata.has_await || metadata.has_call) {
29
- expression = b.call('$.get', memoizer.add(expression, metadata.has_await));
29
+ expression = b.call('$.get', memoizer.add(expression, metadata));
30
30
  }
31
31
 
32
32
  args.push(b.thunk(expression));
@@ -35,7 +35,7 @@ export function SlotElement(node, context) {
35
35
  context,
36
36
  (value, metadata) =>
37
37
  metadata.has_call || metadata.has_await
38
- ? b.call('$.get', memoizer.add(value, metadata.has_await))
38
+ ? b.call('$.get', memoizer.add(value, metadata))
39
39
  : value
40
40
  );
41
41
 
@@ -2,6 +2,8 @@
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types' */
4
4
  import * as b from '#compiler/builders';
5
+ import { hash } from '../../../../../utils.js';
6
+ import { filename } from '../../../../state.js';
5
7
 
6
8
  /**
7
9
  * @param {AST.SvelteHead} node
@@ -13,6 +15,7 @@ export function SvelteHead(node, context) {
13
15
  b.stmt(
14
16
  b.call(
15
17
  '$.head',
18
+ b.literal(hash(filename)),
16
19
  b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.fragment)))
17
20
  )
18
21
  )
@@ -134,7 +134,7 @@ export function build_component(node, component_name, context) {
134
134
  props_and_spreads.push(
135
135
  b.thunk(
136
136
  attribute.metadata.expression.has_await || attribute.metadata.expression.has_call
137
- ? b.call('$.get', memoizer.add(expression, attribute.metadata.expression.has_await))
137
+ ? b.call('$.get', memoizer.add(expression, attribute.metadata.expression))
138
138
  : expression
139
139
  )
140
140
  );
@@ -149,7 +149,7 @@ export function build_component(node, component_name, context) {
149
149
  build_attribute_value(attribute.value, context, (value, metadata) => {
150
150
  // TODO put the derived in the local block
151
151
  return metadata.has_call || metadata.has_await
152
- ? b.call('$.get', memoizer.add(value, metadata.has_await))
152
+ ? b.call('$.get', memoizer.add(value, metadata))
153
153
  : value;
154
154
  }).value
155
155
  )
@@ -185,7 +185,7 @@ export function build_component(node, component_name, context) {
185
185
  });
186
186
 
187
187
  return should_wrap_in_derived
188
- ? b.call('$.get', memoizer.add(value, metadata.has_await))
188
+ ? b.call('$.get', memoizer.add(value, metadata, true))
189
189
  : value;
190
190
  }
191
191
  );
@@ -1,5 +1,5 @@
1
1
  /** @import { Expression, Identifier, ObjectExpression } from 'estree' */
2
- /** @import { AST, ExpressionMetadata } from '#compiler' */
2
+ /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../../types' */
4
4
  import { escape_html } from '../../../../../../escaping.js';
5
5
  import { normalize_attribute } from '../../../../../../utils.js';
@@ -8,6 +8,7 @@ import { is_event_attribute } from '../../../../../utils/ast.js';
8
8
  import * as b from '#compiler/builders';
9
9
  import { build_class_directives_object, build_style_directives_object } from '../RegularElement.js';
10
10
  import { build_expression, build_template_chunk, Memoizer } from './utils.js';
11
+ import { ExpressionMetadata } from '../../../../nodes.js';
11
12
 
12
13
  /**
13
14
  * @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
@@ -35,7 +36,7 @@ export function build_attribute_effect(
35
36
  for (const attribute of attributes) {
36
37
  if (attribute.type === 'Attribute') {
37
38
  const { value } = build_attribute_value(attribute.value, context, (value, metadata) =>
38
- metadata.has_call || metadata.has_await ? memoizer.add(value, metadata.has_await) : value
39
+ memoizer.add(value, metadata)
39
40
  );
40
41
 
41
42
  if (
@@ -52,9 +53,7 @@ export function build_attribute_effect(
52
53
  } else {
53
54
  let value = /** @type {Expression} */ (context.visit(attribute));
54
55
 
55
- if (attribute.metadata.expression.has_call || attribute.metadata.expression.has_await) {
56
- value = memoizer.add(value, attribute.metadata.expression.has_await);
57
- }
56
+ value = memoizer.add(value, attribute.metadata.expression);
58
57
 
59
58
  values.push(b.spread(value));
60
59
  }
@@ -155,9 +154,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c
155
154
  value = b.call('$.clsx', value);
156
155
  }
157
156
 
158
- return metadata.has_call || metadata.has_await
159
- ? context.state.memoizer.add(value, metadata.has_await)
160
- : value;
157
+ return context.state.memoizer.add(value, metadata);
161
158
  });
162
159
 
163
160
  /** @type {Identifier | undefined} */
@@ -166,7 +163,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c
166
163
  /** @type {ObjectExpression | Identifier | undefined} */
167
164
  let prev;
168
165
 
169
- /** @type {ObjectExpression | Identifier | undefined} */
166
+ /** @type {Expression | undefined} */
170
167
  let next;
171
168
 
172
169
  if (class_directives.length) {
@@ -227,7 +224,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c
227
224
  */
228
225
  export function build_set_style(node_id, attribute, style_directives, context) {
229
226
  let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
230
- metadata.has_call ? context.state.memoizer.add(value, metadata.has_await) : value
227
+ context.state.memoizer.add(value, metadata)
231
228
  );
232
229
 
233
230
  /** @type {Identifier | undefined} */
@@ -1,9 +1,10 @@
1
1
  /** @import { Expression } from 'estree' */
2
- /** @import { AST, ExpressionMetadata } from '#compiler' */
2
+ /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../../types' */
4
4
  import { is_capture_event, is_passive_event } from '../../../../../../utils.js';
5
5
  import { dev, locator } from '../../../../../state.js';
6
6
  import * as b from '#compiler/builders';
7
+ import { ExpressionMetadata } from '../../../../nodes.js';
7
8
 
8
9
  /**
9
10
  * @param {AST.Attribute} node
@@ -1,5 +1,5 @@
1
1
  /** @import { AssignmentExpression, Expression, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression, ExpressionStatement } from 'estree' */
2
- /** @import { AST, ExpressionMetadata } from '#compiler' */
2
+ /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentClientTransformState, ComponentContext, Context } from '../../types' */
4
4
  import { walk } from 'zimmerframe';
5
5
  import { object } from '../../../../../utils/ast.js';
@@ -9,6 +9,7 @@ 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
11
  import { build_getter } from '../../utils.js';
12
+ import { ExpressionMetadata } from '../../../../nodes.js';
12
13
 
13
14
  /**
14
15
  * A utility for extracting complex expressions (such as call expressions)
@@ -23,12 +24,21 @@ export class Memoizer {
23
24
 
24
25
  /**
25
26
  * @param {Expression} expression
26
- * @param {boolean} has_await
27
+ * @param {ExpressionMetadata} metadata
28
+ * @param {boolean} memoize_if_state
27
29
  */
28
- add(expression, has_await) {
30
+ add(expression, metadata, memoize_if_state = false) {
31
+ const should_memoize =
32
+ metadata.has_call || metadata.has_await || (memoize_if_state && metadata.has_state);
33
+
34
+ if (!should_memoize) {
35
+ // no memoization required
36
+ return expression;
37
+ }
38
+
29
39
  const id = b.id('#'); // filled in later
30
40
 
31
- (has_await ? this.#async : this.#sync).push({ id, expression });
41
+ (metadata.has_await ? this.#async : this.#sync).push({ id, expression });
32
42
 
33
43
  return id;
34
44
  }
@@ -72,8 +82,7 @@ export function build_template_chunk(
72
82
  values,
73
83
  context,
74
84
  state = context.state,
75
- memoize = (value, metadata) =>
76
- metadata.has_call || metadata.has_await ? state.memoizer.add(value, metadata.has_await) : value
85
+ memoize = (value, metadata) => state.memoizer.add(value, metadata)
77
86
  ) {
78
87
  /** @type {Expression[]} */
79
88
  const expressions = [];