svelte 5.41.0 → 5.41.2

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 (37) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/compiler/phases/2-analyze/index.js +3 -3
  4. package/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +13 -6
  5. package/src/compiler/phases/2-analyze/visitors/CallExpression.js +1 -1
  6. package/src/compiler/phases/2-analyze/visitors/ConstTag.js +3 -1
  7. package/src/compiler/phases/2-analyze/visitors/EachBlock.js +2 -1
  8. package/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js +0 -6
  9. package/src/compiler/phases/3-transform/client/transform-client.js +4 -1
  10. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +21 -3
  11. package/src/compiler/phases/3-transform/client/visitors/Fragment.js +7 -2
  12. package/src/compiler/phases/3-transform/client/visitors/LetDirective.js +21 -17
  13. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +1 -1
  14. package/src/compiler/phases/3-transform/client/visitors/SlotElement.js +1 -1
  15. package/src/compiler/phases/3-transform/client/visitors/SvelteFragment.js +1 -1
  16. package/src/compiler/phases/3-transform/client/visitors/shared/component.js +2 -2
  17. package/src/compiler/phases/3-transform/server/visitors/CallExpression.js +10 -4
  18. package/src/compiler/phases/3-transform/utils.js +14 -25
  19. package/src/internal/client/dev/inspect.js +14 -4
  20. package/src/internal/client/dev/tracing.js +10 -1
  21. package/src/internal/client/dom/blocks/async.js +5 -0
  22. package/src/internal/client/dom/blocks/await.js +71 -137
  23. package/src/internal/client/dom/blocks/boundary.js +7 -26
  24. package/src/internal/client/dom/blocks/branches.js +185 -0
  25. package/src/internal/client/dom/blocks/if.js +28 -107
  26. package/src/internal/client/dom/blocks/key.js +12 -58
  27. package/src/internal/client/dom/blocks/snippet.js +6 -22
  28. package/src/internal/client/dom/blocks/svelte-component.js +7 -63
  29. package/src/internal/client/dom/blocks/svelte-element.js +34 -45
  30. package/src/internal/client/reactivity/async.js +27 -16
  31. package/src/internal/client/reactivity/batch.js +134 -69
  32. package/src/internal/client/reactivity/deriveds.js +19 -10
  33. package/src/internal/client/reactivity/effects.js +20 -2
  34. package/src/internal/client/reactivity/sources.js +1 -1
  35. package/src/internal/server/index.js +0 -9
  36. package/src/version.js +1 -1
  37. 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.41.0",
5
+ "version": "5.41.2",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -306,7 +306,7 @@ export function analyze_module(source, options) {
306
306
  fragment: null,
307
307
  parent_element: null,
308
308
  reactive_statement: null,
309
- in_derived: false
309
+ derived_function_depth: -1
310
310
  },
311
311
  visitors
312
312
  );
@@ -703,7 +703,7 @@ export function analyze_component(root, source, options) {
703
703
  state_fields: new Map(),
704
704
  function_depth: scope.function_depth,
705
705
  reactive_statement: null,
706
- in_derived: false
706
+ derived_function_depth: -1
707
707
  };
708
708
 
709
709
  walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
@@ -771,7 +771,7 @@ export function analyze_component(root, source, options) {
771
771
  expression: null,
772
772
  state_fields: new Map(),
773
773
  function_depth: scope.function_depth,
774
- in_derived: false
774
+ derived_function_depth: -1
775
775
  };
776
776
 
777
777
  walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
@@ -15,7 +15,10 @@ export function AwaitExpression(node, context) {
15
15
  // b) awaits that precede other expressions in template or `$derived(...)`
16
16
  if (
17
17
  tla ||
18
- (is_reactive_expression(context.path, context.state.in_derived) &&
18
+ (is_reactive_expression(
19
+ context.path,
20
+ context.state.derived_function_depth === context.state.function_depth
21
+ ) &&
19
22
  !is_last_evaluated_expression(context.path, node))
20
23
  ) {
21
24
  context.state.analysis.pickled_awaits.add(node);
@@ -53,9 +56,7 @@ export function AwaitExpression(node, context) {
53
56
  * @param {boolean} in_derived
54
57
  */
55
58
  export function is_reactive_expression(path, in_derived) {
56
- if (in_derived) {
57
- return true;
58
- }
59
+ if (in_derived) return true;
59
60
 
60
61
  let i = path.length;
61
62
 
@@ -67,6 +68,7 @@ export function is_reactive_expression(path, in_derived) {
67
68
  parent.type === 'FunctionExpression' ||
68
69
  parent.type === 'FunctionDeclaration'
69
70
  ) {
71
+ // No reactive expression found between function and await
70
72
  return false;
71
73
  }
72
74
 
@@ -83,11 +85,16 @@ export function is_reactive_expression(path, in_derived) {
83
85
  * @param {AST.SvelteNode[]} path
84
86
  * @param {Expression | SpreadElement | Property} node
85
87
  */
86
- export function is_last_evaluated_expression(path, node) {
88
+ function is_last_evaluated_expression(path, node) {
87
89
  let i = path.length;
88
90
 
89
91
  while (i--) {
90
- const parent = /** @type {Expression | Property | SpreadElement} */ (path[i]);
92
+ const parent = path[i];
93
+
94
+ if (parent.type === 'ConstTag') {
95
+ // {@const ...} tags are treated as deriveds and its contents should all get the preserve-reactivity treatment
96
+ return false;
97
+ }
91
98
 
92
99
  // @ts-expect-error we could probably use a neater/more robust mechanism
93
100
  if (parent.metadata) {
@@ -248,7 +248,7 @@ export function CallExpression(node, context) {
248
248
  context.next({
249
249
  ...context.state,
250
250
  function_depth: context.state.function_depth + 1,
251
- in_derived: true,
251
+ derived_function_depth: context.state.function_depth + 1,
252
252
  expression
253
253
  });
254
254
 
@@ -38,6 +38,8 @@ export function ConstTag(node, context) {
38
38
  context.visit(declaration.init, {
39
39
  ...context.state,
40
40
  expression: node.metadata.expression,
41
- in_derived: true
41
+ // We're treating this like a $derived under the hood
42
+ function_depth: context.state.function_depth + 1,
43
+ derived_function_depth: context.state.function_depth + 1
42
44
  });
43
45
  }
@@ -1,3 +1,4 @@
1
+ /** @import { Expression } from 'estree' */
1
2
  /** @import { AST, Binding } from '#compiler' */
2
3
  /** @import { Context } from '../types' */
3
4
  /** @import { Scope } from '../../scope' */
@@ -29,7 +30,7 @@ export function EachBlock(node, context) {
29
30
  }
30
31
 
31
32
  if (node.metadata.keyed && !node.context) {
32
- e.each_key_without_as(node);
33
+ e.each_key_without_as(/** @type {Expression} */ (node.key));
33
34
  }
34
35
 
35
36
  // evaluate expression in parent scope
@@ -64,12 +64,6 @@ export function VariableDeclarator(node, context) {
64
64
  }
65
65
  }
66
66
 
67
- if (rune === '$derived') {
68
- context.visit(node.id);
69
- context.visit(/** @type {Expression} */ (node.init), { ...context.state, in_derived: true });
70
- return;
71
- }
72
-
73
67
  if (rune === '$props') {
74
68
  if (node.id.type !== 'ObjectPattern' && node.id.type !== 'Identifier') {
75
69
  e.props_invalid_identifier(node);
@@ -172,6 +172,7 @@ export function client_component(analysis, options) {
172
172
  // these are set inside the `Fragment` visitor, and cannot be used until then
173
173
  init: /** @type {any} */ (null),
174
174
  consts: /** @type {any} */ (null),
175
+ let_directives: /** @type {any} */ (null),
175
176
  update: /** @type {any} */ (null),
176
177
  after_update: /** @type {any} */ (null),
177
178
  template: /** @type {any} */ (null),
@@ -384,7 +385,9 @@ export function client_component(analysis, options) {
384
385
  .../** @type {ESTree.Statement[]} */ (template.body)
385
386
  ]);
386
387
 
387
- component_block.body.push(b.stmt(b.call(`$.async_body`, b.arrow([], body, true))));
388
+ component_block.body.push(
389
+ b.stmt(b.call(`$.async_body`, b.id('$$anchor'), b.arrow([b.id('$$anchor')], body, true)))
390
+ );
388
391
  } else {
389
392
  component_block.body.push(
390
393
  ...state.instance_level_snippets,
@@ -1,10 +1,10 @@
1
- /** @import { CallExpression, Expression } from 'estree' */
1
+ /** @import { CallExpression, Expression, MemberExpression } 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';
5
5
  import { get_rune } from '../../../scope.js';
6
- import { transform_inspect_rune } from '../../utils.js';
7
6
  import { should_proxy } from '../utils.js';
7
+ import { get_inspect_args } from '../../utils.js';
8
8
 
9
9
  /**
10
10
  * @param {CallExpression} node
@@ -73,7 +73,7 @@ export function CallExpression(node, context) {
73
73
 
74
74
  case '$inspect':
75
75
  case '$inspect().with':
76
- return transform_inspect_rune(node, context);
76
+ return transform_inspect_rune(rune, node, context);
77
77
  }
78
78
 
79
79
  if (
@@ -104,3 +104,21 @@ export function CallExpression(node, context) {
104
104
 
105
105
  context.next();
106
106
  }
107
+
108
+ /**
109
+ * @param {'$inspect' | '$inspect().with'} rune
110
+ * @param {CallExpression} node
111
+ * @param {Context} context
112
+ */
113
+ function transform_inspect_rune(rune, node, context) {
114
+ if (!dev) return b.empty;
115
+
116
+ const { args, inspector } = get_inspect_args(rune, node, context.visit);
117
+
118
+ // by passing an arrow function, the log appears to come from the `$inspect` callsite
119
+ // rather than the `inspect.js` file containing the utility
120
+ const id = b.id('$$args');
121
+ const fn = b.arrow([b.rest(id)], b.call(inspector, b.spread(id)));
122
+
123
+ return b.call('$.inspect', b.thunk(b.array(args)), fn, rune === '$inspect' && b.true);
124
+ }
@@ -63,6 +63,7 @@ export function Fragment(node, context) {
63
63
  ...context.state,
64
64
  init: [],
65
65
  consts: [],
66
+ let_directives: [],
66
67
  update: [],
67
68
  after_update: [],
68
69
  memoizer: new Memoizer(),
@@ -150,7 +151,7 @@ export function Fragment(node, context) {
150
151
  }
151
152
  }
152
153
 
153
- body.push(...state.consts);
154
+ body.push(...state.let_directives, ...state.consts);
154
155
 
155
156
  if (has_await) {
156
157
  body.push(b.if(b.call('$.aborted'), b.return()));
@@ -177,7 +178,11 @@ export function Fragment(node, context) {
177
178
  }
178
179
 
179
180
  if (has_await) {
180
- return b.block([b.stmt(b.call('$.async_body', b.arrow([], b.block(body), true)))]);
181
+ return b.block([
182
+ b.stmt(
183
+ b.call('$.async_body', b.id('$$anchor'), b.arrow([b.id('$$anchor')], b.block(body), true))
184
+ )
185
+ ]);
181
186
  } else {
182
187
  return b.block(body);
183
188
  }
@@ -21,22 +21,24 @@ export function LetDirective(node, context) {
21
21
  };
22
22
  }
23
23
 
24
- return b.const(
25
- name,
26
- b.call(
27
- '$.derived',
28
- b.thunk(
29
- b.block([
30
- b.let(
31
- /** @type {Expression} */ (node.expression).type === 'ObjectExpression'
32
- ? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
33
- b.object_pattern(node.expression.properties)
34
- : // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
35
- b.array_pattern(node.expression.elements),
36
- b.member(b.id('$$slotProps'), node.name)
37
- ),
38
- b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node))))
39
- ])
24
+ context.state.let_directives.push(
25
+ b.const(
26
+ name,
27
+ b.call(
28
+ '$.derived',
29
+ b.thunk(
30
+ b.block([
31
+ b.let(
32
+ /** @type {Expression} */ (node.expression).type === 'ObjectExpression'
33
+ ? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
34
+ b.object_pattern(node.expression.properties)
35
+ : // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
36
+ b.array_pattern(node.expression.elements),
37
+ b.member(b.id('$$slotProps'), node.name)
38
+ ),
39
+ b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node))))
40
+ ])
41
+ )
40
42
  )
41
43
  )
42
44
  );
@@ -46,6 +48,8 @@ export function LetDirective(node, context) {
46
48
  read: (node) => b.call('$.get', node)
47
49
  };
48
50
 
49
- return b.const(name, create_derived(context.state, b.member(b.id('$$slotProps'), node.name)));
51
+ context.state.let_directives.push(
52
+ b.const(name, create_derived(context.state, b.member(b.id('$$slotProps'), node.name)))
53
+ );
50
54
  }
51
55
  }
@@ -106,7 +106,7 @@ export function RegularElement(node, context) {
106
106
 
107
107
  case 'LetDirective':
108
108
  // visit let directives before everything else, to set state
109
- lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
109
+ context.visit(attribute, { ...context.state, let_directives: lets });
110
110
  break;
111
111
 
112
112
  case 'OnDirective':
@@ -49,7 +49,7 @@ export function SlotElement(node, context) {
49
49
  }
50
50
  }
51
51
  } else if (attribute.type === 'LetDirective') {
52
- lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
52
+ context.visit(attribute, { ...context.state, let_directives: lets });
53
53
  }
54
54
  }
55
55
 
@@ -9,7 +9,7 @@
9
9
  export function SvelteFragment(node, context) {
10
10
  for (const attribute of node.attributes) {
11
11
  if (attribute.type === 'LetDirective') {
12
- context.state.init.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
12
+ context.visit(attribute);
13
13
  }
14
14
  }
15
15
 
@@ -101,7 +101,7 @@ export function build_component(node, component_name, context) {
101
101
  if (slot_scope_applies_to_itself) {
102
102
  for (const attribute of node.attributes) {
103
103
  if (attribute.type === 'LetDirective') {
104
- lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
104
+ context.visit(attribute, { ...context.state, let_directives: lets });
105
105
  }
106
106
  }
107
107
  }
@@ -109,7 +109,7 @@ export function build_component(node, component_name, context) {
109
109
  for (const attribute of node.attributes) {
110
110
  if (attribute.type === 'LetDirective') {
111
111
  if (!slot_scope_applies_to_itself) {
112
- lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute, states.default)));
112
+ context.visit(attribute, { ...states.default, let_directives: lets });
113
113
  }
114
114
  } else if (attribute.type === 'OnDirective') {
115
115
  if (!attribute.expression) {
@@ -1,9 +1,9 @@
1
- /** @import { CallExpression, Expression } from 'estree' */
1
+ /** @import { CallExpression, Expression, MemberExpression } from 'estree' */
2
2
  /** @import { Context } from '../types.js' */
3
- import { is_ignored } from '../../../../state.js';
3
+ import { dev, is_ignored } from '../../../../state.js';
4
4
  import * as b from '#compiler/builders';
5
5
  import { get_rune } from '../../../scope.js';
6
- import { transform_inspect_rune } from '../../utils.js';
6
+ import { get_inspect_args } from '../../utils.js';
7
7
 
8
8
  /**
9
9
  * @param {CallExpression} node
@@ -51,7 +51,13 @@ export function CallExpression(node, context) {
51
51
  }
52
52
 
53
53
  if (rune === '$inspect' || rune === '$inspect().with') {
54
- return transform_inspect_rune(node, context);
54
+ if (!dev) return b.empty;
55
+
56
+ const { args, inspector } = get_inspect_args(rune, node, context.visit);
57
+
58
+ return rune === '$inspect'
59
+ ? b.call(inspector, b.literal('$inspect('), ...args, b.literal(')'))
60
+ : b.call(inspector, b.literal('init'), ...args);
55
61
  }
56
62
 
57
63
  context.next();
@@ -1,7 +1,7 @@
1
1
  /** @import { Context } from 'zimmerframe' */
2
2
  /** @import { TransformState } from './types.js' */
3
3
  /** @import { AST, Binding, Namespace, ValidatedCompileOptions } from '#compiler' */
4
- /** @import { Node, Expression, CallExpression } from 'estree' */
4
+ /** @import { Node, Expression, CallExpression, MemberExpression } from 'estree' */
5
5
  import {
6
6
  regex_ends_with_whitespaces,
7
7
  regex_not_whitespace,
@@ -452,30 +452,19 @@ export function determine_namespace_for_children(node, namespace) {
452
452
  }
453
453
 
454
454
  /**
455
- * @template {TransformState} T
455
+ * @param {'$inspect' | '$inspect().with'} rune
456
456
  * @param {CallExpression} node
457
- * @param {Context<any, T>} context
457
+ * @param {(node: AST.SvelteNode) => AST.SvelteNode} visit
458
458
  */
459
- export function transform_inspect_rune(node, context) {
460
- const { state, visit } = context;
461
- const as_fn = state.options.generate === 'client';
462
-
463
- if (!dev) return b.empty;
464
-
465
- if (node.callee.type === 'MemberExpression') {
466
- const raw_inspect_args = /** @type {CallExpression} */ (node.callee.object).arguments;
467
- const inspect_args =
468
- /** @type {Array<Expression>} */
469
- (raw_inspect_args.map((arg) => visit(arg)));
470
- const with_arg = /** @type {Expression} */ (visit(node.arguments[0]));
471
-
472
- return b.call(
473
- '$.inspect',
474
- as_fn ? b.thunk(b.array(inspect_args)) : b.array(inspect_args),
475
- with_arg
476
- );
477
- } else {
478
- const arg = node.arguments.map((arg) => /** @type {Expression} */ (visit(arg)));
479
- return b.call('$.inspect', as_fn ? b.thunk(b.array(arg)) : b.array(arg));
480
- }
459
+ export function get_inspect_args(rune, node, visit) {
460
+ const call =
461
+ rune === '$inspect'
462
+ ? node
463
+ : /** @type {CallExpression} */ (/** @type {MemberExpression} */ (node.callee).object);
464
+
465
+ return {
466
+ args: call.arguments.map((arg) => /** @type {Expression} */ (visit(arg))),
467
+ inspector:
468
+ rune === '$inspect' ? 'console.log' : /** @type {Expression} */ (visit(node.arguments[0]))
469
+ };
481
470
  }
@@ -2,13 +2,14 @@ import { UNINITIALIZED } from '../../../constants.js';
2
2
  import { snapshot } from '../../shared/clone.js';
3
3
  import { inspect_effect, render_effect, validate_effect } from '../reactivity/effects.js';
4
4
  import { untrack } from '../runtime.js';
5
+ import { get_stack } from './tracing.js';
5
6
 
6
7
  /**
7
8
  * @param {() => any[]} get_value
8
- * @param {Function} [inspector]
9
+ * @param {Function} inspector
10
+ * @param {boolean} show_stack
9
11
  */
10
- // eslint-disable-next-line no-console
11
- export function inspect(get_value, inspector = console.log) {
12
+ export function inspect(get_value, inspector, show_stack = false) {
12
13
  validate_effect('$inspect');
13
14
 
14
15
  let initial = true;
@@ -28,7 +29,16 @@ export function inspect(get_value, inspector = console.log) {
28
29
 
29
30
  var snap = snapshot(value, true, true);
30
31
  untrack(() => {
31
- inspector(initial ? 'init' : 'update', ...snap);
32
+ if (show_stack) {
33
+ inspector(...snap);
34
+
35
+ if (!initial) {
36
+ // eslint-disable-next-line no-console
37
+ console.log(get_stack('UpdatedAt'));
38
+ }
39
+ } else {
40
+ inspector(initial ? 'init' : 'update', ...snap);
41
+ }
32
42
  });
33
43
 
34
44
  initial = false;
@@ -134,7 +134,16 @@ export function trace(label, fn) {
134
134
  * @returns {Error & { stack: string } | null}
135
135
  */
136
136
  export function get_stack(label) {
137
+ // @ts-ignore stackTraceLimit doesn't exist everywhere
138
+ const limit = Error.stackTraceLimit;
139
+
140
+ // @ts-ignore
141
+ Error.stackTraceLimit = Infinity;
137
142
  let error = Error();
143
+
144
+ // @ts-ignore
145
+ Error.stackTraceLimit = limit;
146
+
138
147
  const stack = error.stack;
139
148
 
140
149
  if (!stack) return null;
@@ -151,7 +160,7 @@ export function get_stack(label) {
151
160
  if (line.includes('validate_each_keys')) {
152
161
  return null;
153
162
  }
154
- if (line.includes('svelte/src/internal')) {
163
+ if (line.includes('svelte/src/internal') || line.includes('svelte\\src\\internal')) {
155
164
  continue;
156
165
  }
157
166
  new_lines.push(line);
@@ -1,5 +1,6 @@
1
1
  /** @import { TemplateNode, Value } from '#client' */
2
2
  import { flatten } from '../../reactivity/async.js';
3
+ import { Batch, current_batch } from '../../reactivity/batch.js';
3
4
  import { get } from '../../runtime.js';
4
5
  import {
5
6
  hydrate_next,
@@ -18,8 +19,11 @@ import { get_boundary } from './boundary.js';
18
19
  */
19
20
  export function async(node, expressions, fn) {
20
21
  var boundary = get_boundary();
22
+ var batch = /** @type {Batch} */ (current_batch);
23
+ var blocking = !boundary.is_pending();
21
24
 
22
25
  boundary.update_pending_count(1);
26
+ batch.increment(blocking);
23
27
 
24
28
  var was_hydrating = hydrating;
25
29
 
@@ -44,6 +48,7 @@ export function async(node, expressions, fn) {
44
48
  fn(node, ...values);
45
49
  } finally {
46
50
  boundary.update_pending_count(-1);
51
+ batch.decrement(blocking);
47
52
  }
48
53
 
49
54
  if (was_hydrating) {