svelte 5.41.1 → 5.41.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.
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.3",
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
  }
@@ -1,3 +1,4 @@
1
+ // General flags
1
2
  export const DERIVED = 1 << 1;
2
3
  export const EFFECT = 1 << 2;
3
4
  export const RENDER_EFFECT = 1 << 3;
@@ -5,13 +6,13 @@ export const BLOCK_EFFECT = 1 << 4;
5
6
  export const BRANCH_EFFECT = 1 << 5;
6
7
  export const ROOT_EFFECT = 1 << 6;
7
8
  export const BOUNDARY_EFFECT = 1 << 7;
8
- export const UNOWNED = 1 << 8;
9
- export const DISCONNECTED = 1 << 9;
10
9
  export const CLEAN = 1 << 10;
11
10
  export const DIRTY = 1 << 11;
12
11
  export const MAYBE_DIRTY = 1 << 12;
13
12
  export const INERT = 1 << 13;
14
13
  export const DESTROYED = 1 << 14;
14
+
15
+ // Flags exclusive to effects
15
16
  export const EFFECT_RAN = 1 << 15;
16
17
  /** 'Transparent' effects do not create a transition boundary */
17
18
  export const EFFECT_TRANSPARENT = 1 << 16;
@@ -20,6 +21,16 @@ export const HEAD_EFFECT = 1 << 18;
20
21
  export const EFFECT_PRESERVED = 1 << 19;
21
22
  export const USER_EFFECT = 1 << 20;
22
23
 
24
+ // Flags exclusive to deriveds
25
+ export const UNOWNED = 1 << 8;
26
+ export const DISCONNECTED = 1 << 9;
27
+ /**
28
+ * Tells that we marked this derived and its reactions as visited during the "mark as (maybe) dirty"-phase.
29
+ * Will be lifted during execution of the derived and during checking its dirty state (both are necessary
30
+ * because a derived might be checked but not executed).
31
+ */
32
+ export const WAS_MARKED = 1 << 15;
33
+
23
34
  // Flags used for async
24
35
  export const REACTION_IS_UPDATING = 1 << 21;
25
36
  export const ASYNC = 1 << 22;
@@ -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;
@@ -144,16 +153,20 @@ export function get_stack(label) {
144
153
 
145
154
  for (let i = 0; i < lines.length; i++) {
146
155
  const line = lines[i];
156
+ const posixified = line.replaceAll('\\', '/');
147
157
 
148
158
  if (line === 'Error') {
149
159
  continue;
150
160
  }
161
+
151
162
  if (line.includes('validate_each_keys')) {
152
163
  return null;
153
164
  }
154
- if (line.includes('svelte/src/internal')) {
165
+
166
+ if (posixified.includes('svelte/src/internal') || posixified.includes('node_modules/.vite')) {
155
167
  continue;
156
168
  }
169
+
157
170
  new_lines.push(line);
158
171
  }
159
172
 
@@ -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
  }