svelte 5.41.1 → 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.
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.1",
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
  }
@@ -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);
@@ -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
+ }
@@ -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) {
@@ -291,13 +291,6 @@ export class Boundary {
291
291
  this.#anchor.before(this.#offscreen_fragment);
292
292
  this.#offscreen_fragment = null;
293
293
  }
294
-
295
- // TODO this feels like a little bit of a kludge, but until we
296
- // overhaul the boundary/batch relationship it's probably
297
- // the most pragmatic solution available to us
298
- queue_micro_task(() => {
299
- Batch.ensure().flush();
300
- });
301
294
  }
302
295
  }
303
296
 
@@ -218,10 +218,10 @@ export function unset_context() {
218
218
  export async function async_body(anchor, fn) {
219
219
  var boundary = get_boundary();
220
220
  var batch = /** @type {Batch} */ (current_batch);
221
- var pending = boundary.is_pending();
221
+ var blocking = !boundary.is_pending();
222
222
 
223
223
  boundary.update_pending_count(1);
224
- if (!pending) batch.increment();
224
+ batch.increment(blocking);
225
225
 
226
226
  var active = /** @type {Effect} */ (active_effect);
227
227
 
@@ -254,12 +254,7 @@ export async function async_body(anchor, fn) {
254
254
  }
255
255
 
256
256
  boundary.update_pending_count(-1);
257
-
258
- if (pending) {
259
- batch.flush();
260
- } else {
261
- batch.decrement();
262
- }
257
+ batch.decrement(blocking);
263
258
 
264
259
  unset_context();
265
260
  }