svelte 5.48.0 → 5.48.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 (26) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/compiler/errors.js +2 -2
  4. package/src/compiler/phases/3-transform/client/transform-client.js +1 -0
  5. package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +11 -5
  6. package/src/compiler/phases/3-transform/client/visitors/Fragment.js +4 -3
  7. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +2 -1
  8. package/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js +1 -1
  9. package/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +1 -1
  10. package/src/compiler/phases/3-transform/client/visitors/shared/component.js +1 -1
  11. package/src/compiler/phases/3-transform/server/visitors/ConstTag.js +4 -2
  12. package/src/compiler/phases/3-transform/server/visitors/HtmlTag.js +11 -1
  13. package/src/compiler/phases/3-transform/shared/transform-async.js +15 -12
  14. package/src/internal/client/dom/blocks/async.js +7 -2
  15. package/src/internal/client/dom/blocks/boundary.js +13 -7
  16. package/src/internal/client/dom/elements/attributes.js +2 -2
  17. package/src/internal/client/index.js +2 -1
  18. package/src/internal/client/reactivity/async.js +65 -40
  19. package/src/internal/client/reactivity/batch.js +27 -30
  20. package/src/internal/client/reactivity/effects.js +3 -3
  21. package/src/internal/client/reactivity/sources.js +9 -19
  22. package/src/internal/client/runtime.js +2 -13
  23. package/src/internal/client/validate.js +2 -1
  24. package/src/internal/server/dev.js +6 -1
  25. package/src/version.js +1 -1
  26. 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.48.0",
5
+ "version": "5.48.2",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -977,12 +977,12 @@ export function const_tag_invalid_expression(node) {
977
977
  }
978
978
 
979
979
  /**
980
- * `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>`, `<svelte:boundary` or `<Component>`
980
+ * `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>`, `<svelte:boundary>` or `<Component>`
981
981
  * @param {null | number | NodeLike} node
982
982
  * @returns {never}
983
983
  */
984
984
  export function const_tag_invalid_placement(node) {
985
- e(node, 'const_tag_invalid_placement', `\`{@const}\` must be the immediate child of \`{#snippet}\`, \`{#if}\`, \`{:else if}\`, \`{:else}\`, \`{#each}\`, \`{:then}\`, \`{:catch}\`, \`<svelte:fragment>\`, \`<svelte:boundary\` or \`<Component>\`\nhttps://svelte.dev/e/const_tag_invalid_placement`);
985
+ e(node, 'const_tag_invalid_placement', `\`{@const}\` must be the immediate child of \`{#snippet}\`, \`{#if}\`, \`{:else if}\`, \`{:else}\`, \`{#each}\`, \`{:then}\`, \`{:catch}\`, \`<svelte:fragment>\`, \`<svelte:boundary>\` or \`<Component>\`\nhttps://svelte.dev/e/const_tag_invalid_placement`);
986
986
  }
987
987
 
988
988
  /**
@@ -170,6 +170,7 @@ export function client_component(analysis, options) {
170
170
  // these are set inside the `Fragment` visitor, and cannot be used until then
171
171
  init: /** @type {any} */ (null),
172
172
  consts: /** @type {any} */ (null),
173
+ snippets: /** @type {any} */ (null),
173
174
  let_directives: /** @type {any} */ (null),
174
175
  update: /** @type {any} */ (null),
175
176
  after_update: /** @type {any} */ (null),
@@ -1,4 +1,4 @@
1
- /** @import { Pattern } from 'estree' */
1
+ /** @import { Expression, Identifier, Pattern } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types' */
4
4
  /** @import { ExpressionMetadata } from '../../../nodes.js' */
@@ -88,8 +88,8 @@ export function ConstTag(node, context) {
88
88
 
89
89
  /**
90
90
  * @param {ComponentContext['state']} state
91
- * @param {import('estree').Identifier} id
92
- * @param {import('estree').Expression} expression
91
+ * @param {Identifier} id
92
+ * @param {Expression} expression
93
93
  * @param {ExpressionMetadata} metadata
94
94
  * @param {import('#compiler').Binding[]} bindings
95
95
  */
@@ -99,7 +99,9 @@ function add_const_declaration(state, id, expression, metadata, bindings) {
99
99
  const after = dev ? [b.stmt(b.call('$.get', id))] : [];
100
100
 
101
101
  const has_await = metadata.has_await;
102
- const blockers = [...metadata.dependencies].map((dep) => dep.blocker).filter((b) => b !== null);
102
+ const blockers = [...metadata.dependencies]
103
+ .map((dep) => dep.blocker)
104
+ .filter((b) => b !== null && b.object !== state.async_consts?.id);
103
105
 
104
106
  if (has_await || state.async_consts || blockers.length > 0) {
105
107
  const run = (state.async_consts ??= {
@@ -112,7 +114,11 @@ function add_const_declaration(state, id, expression, metadata, bindings) {
112
114
  const assignment = b.assignment('=', id, expression);
113
115
  const body = after.length === 0 ? assignment : b.block([b.stmt(assignment), ...after]);
114
116
 
115
- if (blockers.length > 0) run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
117
+ if (blockers.length === 1) {
118
+ run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
119
+ } else if (blockers.length > 0) {
120
+ run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
121
+ }
116
122
 
117
123
  run.thunks.push(b.thunk(body, has_await));
118
124
 
@@ -1,13 +1,13 @@
1
1
  /** @import { Expression, Statement } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentClientTransformState, ComponentContext } from '../types' */
4
- import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../../../constants.js';
5
4
  import * as b from '#compiler/builders';
5
+ import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../../../constants.js';
6
6
  import { clean_nodes, infer_namespace } from '../../utils.js';
7
7
  import { transform_template } from '../transform-template/index.js';
8
+ import { Template } from '../transform-template/template.js';
8
9
  import { process_children } from './shared/fragment.js';
9
10
  import { build_render_statement, Memoizer } from './shared/utils.js';
10
- import { Template } from '../transform-template/template.js';
11
11
 
12
12
  /**
13
13
  * @param {AST.Fragment} node
@@ -60,6 +60,7 @@ export function Fragment(node, context) {
60
60
  const state = {
61
61
  ...context.state,
62
62
  init: [],
63
+ snippets: [],
63
64
  consts: [],
64
65
  let_directives: [],
65
66
  update: [],
@@ -150,7 +151,7 @@ export function Fragment(node, context) {
150
151
  }
151
152
  }
152
153
 
153
- body.push(...state.let_directives, ...state.consts);
154
+ body.push(...state.snippets, ...state.let_directives, ...state.consts);
154
155
 
155
156
  if (state.async_consts && state.async_consts.thunks.length > 0) {
156
157
  body.push(b.var(state.async_consts.id, b.call('$.run', b.array(state.async_consts.thunks))));
@@ -329,7 +329,7 @@ export function RegularElement(node, context) {
329
329
  );
330
330
 
331
331
  /** @type {typeof state} */
332
- const child_state = { ...state, init: [], update: [], after_update: [] };
332
+ const child_state = { ...state, init: [], update: [], after_update: [], snippets: [] };
333
333
 
334
334
  for (const node of hoisted) {
335
335
  context.visit(node, child_state);
@@ -441,6 +441,7 @@ export function RegularElement(node, context) {
441
441
  // Wrap children in `{...}` to avoid declaration conflicts
442
442
  context.state.init.push(
443
443
  b.block([
444
+ ...child_state.snippets,
444
445
  ...child_state.init,
445
446
  ...element_state.init,
446
447
  child_state.update.length > 0 ? build_render_statement(child_state) : b.empty,
@@ -89,6 +89,6 @@ export function SnippetBlock(node, context) {
89
89
  context.state.instance_level_snippets.push(declaration);
90
90
  }
91
91
  } else {
92
- context.state.init.push(declaration);
92
+ context.state.snippets.push(declaration);
93
93
  }
94
94
  }
@@ -77,7 +77,7 @@ export function SvelteBoundary(node, context) {
77
77
  /** @type {Statement[]} */
78
78
  const statements = [];
79
79
 
80
- context.visit(child, { ...context.state, init: statements });
80
+ context.visit(child, { ...context.state, snippets: statements });
81
81
 
82
82
  const snippet = /** @type {VariableDeclaration} */ (statements[0]);
83
83
 
@@ -333,7 +333,7 @@ export function build_component(node, component_name, loc, context) {
333
333
  // can be used as props without creating conflicts
334
334
  context.visit(child, {
335
335
  ...context.state,
336
- init: snippet_declarations
336
+ snippets: snippet_declarations
337
337
  });
338
338
 
339
339
  push_prop(b.prop('init', child.expression, child.expression));
@@ -15,7 +15,7 @@ export function ConstTag(node, context) {
15
15
  const has_await = node.metadata.expression.has_await;
16
16
  const blockers = [...node.metadata.expression.dependencies]
17
17
  .map((dep) => dep.blocker)
18
- .filter((b) => b !== null);
18
+ .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
19
19
 
20
20
  if (has_await || context.state.async_consts || blockers.length > 0) {
21
21
  const run = (context.state.async_consts ??= {
@@ -30,7 +30,9 @@ export function ConstTag(node, context) {
30
30
  context.state.init.push(b.let(identifier.name));
31
31
  }
32
32
 
33
- if (blockers.length > 0) {
33
+ if (blockers.length === 1) {
34
+ run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
35
+ } else if (blockers.length > 0) {
34
36
  run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
35
37
  }
36
38
 
@@ -2,7 +2,7 @@
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types.js' */
4
4
  import * as b from '#compiler/builders';
5
- import { create_push } from './shared/utils.js';
5
+ import { block_close, block_open, create_push } from './shared/utils.js';
6
6
 
7
7
  /**
8
8
  * @param {AST.HtmlTag} node
@@ -12,5 +12,15 @@ export function HtmlTag(node, context) {
12
12
  const expression = /** @type {Expression} */ (context.visit(node.expression));
13
13
  const call = b.call('$.html', expression);
14
14
 
15
+ const has_await = node.metadata.expression.has_await;
16
+
17
+ if (has_await) {
18
+ context.state.template.push(block_open);
19
+ }
20
+
15
21
  context.state.template.push(create_push(call, node.metadata.expression, true));
22
+
23
+ if (has_await) {
24
+ context.state.template.push(block_close);
25
+ }
16
26
  }
@@ -49,22 +49,25 @@ export function transform_body(instance_body, runner, transform) {
49
49
  if (instance_body.async.length > 0) {
50
50
  const thunks = instance_body.async.map((s) => {
51
51
  if (s.node.type === 'VariableDeclarator') {
52
- const visited = /** @type {ESTree.VariableDeclaration} */ (
52
+ const visited = /** @type {ESTree.VariableDeclaration | ESTree.EmptyStatement} */ (
53
53
  transform(b.var(s.node.id, s.node.init))
54
54
  );
55
55
 
56
- const statements = visited.declarations.map((node) => {
57
- if (
58
- node.id.type === 'Identifier' &&
59
- (node.id.name.startsWith('$$d') || node.id.name.startsWith('$$array'))
60
- ) {
61
- // this is an intermediate declaration created in VariableDeclaration.js;
62
- // subsequent statements depend on it
63
- return b.var(node.id, node.init);
64
- }
56
+ const statements =
57
+ visited.type === 'VariableDeclaration'
58
+ ? visited.declarations.map((node) => {
59
+ if (
60
+ node.id.type === 'Identifier' &&
61
+ (node.id.name.startsWith('$$d') || node.id.name.startsWith('$$array'))
62
+ ) {
63
+ // this is an intermediate declaration created in VariableDeclaration.js;
64
+ // subsequent statements depend on it
65
+ return b.var(node.id, node.init);
66
+ }
65
67
 
66
- return b.stmt(b.assignment('=', node.id, node.init ?? b.void0));
67
- });
68
+ return b.stmt(b.assignment('=', node.id, node.init ?? b.void0));
69
+ })
70
+ : [];
68
71
 
69
72
  if (statements.length === 1) {
70
73
  const statement = /** @type {ESTree.ExpressionStatement} */ (statements[0]);
@@ -1,4 +1,4 @@
1
- /** @import { TemplateNode, Value } from '#client' */
1
+ /** @import { Blocker, TemplateNode, Value } from '#client' */
2
2
  import { flatten } from '../../reactivity/async.js';
3
3
  import { Batch, current_batch } from '../../reactivity/batch.js';
4
4
  import { get } from '../../runtime.js';
@@ -14,11 +14,16 @@ import { get_boundary } from './boundary.js';
14
14
 
15
15
  /**
16
16
  * @param {TemplateNode} node
17
- * @param {Array<Promise<void>>} blockers
17
+ * @param {Blocker[]} blockers
18
18
  * @param {Array<() => Promise<any>>} expressions
19
19
  * @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn
20
20
  */
21
21
  export function async(node, blockers = [], expressions = [], fn) {
22
+ if (expressions.length === 0 && blockers.every((b) => b.settled)) {
23
+ fn(node);
24
+ return;
25
+ }
26
+
22
27
  var boundary = get_boundary();
23
28
  var batch = /** @type {Batch} */ (current_batch);
24
29
  var blocking = boundary.is_rendered();
@@ -102,6 +102,7 @@ export class Boundary {
102
102
 
103
103
  #local_pending_count = 0;
104
104
  #pending_count = 0;
105
+ #pending_count_update_queued = false;
105
106
 
106
107
  #is_creating_fallback = false;
107
108
 
@@ -202,12 +203,11 @@ export class Boundary {
202
203
 
203
204
  #hydrate_pending_content() {
204
205
  const pending = this.#props.pending;
205
- if (!pending) {
206
- return;
207
- }
206
+ if (!pending) return;
207
+
208
208
  this.#pending_effect = branch(() => pending(this.#anchor));
209
209
 
210
- Batch.enqueue(() => {
210
+ queue_micro_task(() => {
211
211
  var anchor = this.#get_anchor();
212
212
 
213
213
  this.#main_effect = this.#run(() => {
@@ -359,9 +359,15 @@ export class Boundary {
359
359
 
360
360
  this.#local_pending_count += d;
361
361
 
362
- if (this.#effect_pending) {
363
- internal_set(this.#effect_pending, this.#local_pending_count);
364
- }
362
+ if (!this.#effect_pending || this.#pending_count_update_queued) return;
363
+ this.#pending_count_update_queued = true;
364
+
365
+ queue_micro_task(() => {
366
+ this.#pending_count_update_queued = false;
367
+ if (this.#effect_pending) {
368
+ internal_set(this.#effect_pending, this.#local_pending_count);
369
+ }
370
+ });
365
371
  }
366
372
 
367
373
  get_effect_pending() {
@@ -1,4 +1,4 @@
1
- /** @import { Effect } from '#client' */
1
+ /** @import { Blocker, Effect } from '#client' */
2
2
  import { DEV } from 'esm-env';
3
3
  import { hydrating, set_hydrating } from '../hydration.js';
4
4
  import { get_descriptors, get_prototype_of } from '../../../shared/utils.js';
@@ -483,7 +483,7 @@ function set_attributes(
483
483
  * @param {(...expressions: any) => Record<string | symbol, any>} fn
484
484
  * @param {Array<() => any>} sync
485
485
  * @param {Array<() => Promise<any>>} async
486
- * @param {Array<Promise<void>>} blockers
486
+ * @param {Blocker[]} blockers
487
487
  * @param {string} [css_hash]
488
488
  * @param {boolean} [should_remove_defaults]
489
489
  * @param {boolean} [skip_warning]
@@ -103,7 +103,8 @@ export {
103
103
  run,
104
104
  save,
105
105
  track_reactivity_loss,
106
- run_after_blockers
106
+ run_after_blockers,
107
+ wait
107
108
  } from './reactivity/async.js';
108
109
  export { eager, flushSync as flush } from './reactivity/batch.js';
109
110
  export {
@@ -1,4 +1,4 @@
1
- /** @import { Effect, TemplateNode, Value } from '#client' */
1
+ /** @import { Blocker, Effect, Value } from '#client' */
2
2
  import { DESTROYED, STALE_REACTION } from '#client/constants';
3
3
  import { DEV } from 'esm-env';
4
4
  import {
@@ -27,7 +27,7 @@ import {
27
27
  import { aborted } from './effects.js';
28
28
 
29
29
  /**
30
- * @param {Array<Promise<void>>} blockers
30
+ * @param {Blocker[]} blockers
31
31
  * @param {Array<() => any>} sync
32
32
  * @param {Array<() => Promise<any>>} async
33
33
  * @param {(values: Value[]) => any} fn
@@ -35,7 +35,10 @@ import { aborted } from './effects.js';
35
35
  export function flatten(blockers, sync, async, fn) {
36
36
  const d = is_runes() ? derived : derived_safe_equal;
37
37
 
38
- if (async.length === 0 && blockers.length === 0) {
38
+ // Filter out already-settled blockers - no need to wait for them
39
+ var pending = blockers.filter((b) => !b.settled);
40
+
41
+ if (async.length === 0 && pending.length === 0) {
39
42
  fn(sync.map(d));
40
43
  return;
41
44
  }
@@ -44,47 +47,52 @@ export function flatten(blockers, sync, async, fn) {
44
47
  var parent = /** @type {Effect} */ (active_effect);
45
48
 
46
49
  var restore = capture();
50
+ var blocker_promise =
51
+ pending.length === 1
52
+ ? pending[0].promise
53
+ : pending.length > 1
54
+ ? Promise.all(pending.map((b) => b.promise))
55
+ : null;
56
+
57
+ /** @param {Value[]} values */
58
+ function finish(values) {
59
+ restore();
47
60
 
48
- function run() {
49
- Promise.all(async.map((expression) => async_derived(expression)))
50
- .then((result) => {
51
- restore();
61
+ try {
62
+ fn(values);
63
+ } catch (error) {
64
+ if ((parent.f & DESTROYED) === 0) {
65
+ invoke_error_boundary(error, parent);
66
+ }
67
+ }
52
68
 
53
- try {
54
- fn([...sync.map(d), ...result]);
55
- } catch (error) {
56
- // ignore errors in blocks that have already been destroyed
57
- if ((parent.f & DESTROYED) === 0) {
58
- invoke_error_boundary(error, parent);
59
- }
60
- }
69
+ batch?.deactivate();
70
+ unset_context();
71
+ }
61
72
 
62
- batch?.deactivate();
63
- unset_context();
64
- })
65
- .catch((error) => {
66
- invoke_error_boundary(error, parent);
67
- });
73
+ // Fast path: blockers but no async expressions
74
+ if (async.length === 0) {
75
+ /** @type {Promise<any>} */ (blocker_promise).then(() => finish(sync.map(d)));
76
+ return;
68
77
  }
69
78
 
70
- if (blockers.length > 0) {
71
- Promise.all(blockers).then(() => {
72
- restore();
79
+ // Full path: has async expressions
80
+ function run() {
81
+ restore();
82
+ Promise.all(async.map((expression) => async_derived(expression)))
83
+ .then((result) => finish([...sync.map(d), ...result]))
84
+ .catch((error) => invoke_error_boundary(error, parent));
85
+ }
73
86
 
74
- try {
75
- return run();
76
- } finally {
77
- batch?.deactivate();
78
- unset_context();
79
- }
80
- });
87
+ if (blocker_promise) {
88
+ blocker_promise.then(run);
81
89
  } else {
82
90
  run();
83
91
  }
84
92
  }
85
93
 
86
94
  /**
87
- * @param {Array<Promise<void>>} blockers
95
+ * @param {Blocker[]} blockers
88
96
  * @param {(values: Value[]) => any} fn
89
97
  */
90
98
  export function run_after_blockers(blockers, fn) {
@@ -239,7 +247,13 @@ export function run(thunks) {
239
247
 
240
248
  var promise = Promise.resolve(thunks[0]()).catch(handle_error);
241
249
 
242
- var promises = [promise];
250
+ /** @type {Blocker} */
251
+ var blocker = { promise, settled: false };
252
+ var blockers = [blocker];
253
+
254
+ promise.finally(() => {
255
+ blocker.settled = true;
256
+ });
243
257
 
244
258
  for (const fn of thunks.slice(1)) {
245
259
  promise = promise
@@ -255,13 +269,17 @@ export function run(thunks) {
255
269
  restore();
256
270
  return fn();
257
271
  })
258
- .catch(handle_error)
259
- .finally(() => {
260
- unset_context();
261
- current_batch?.deactivate();
262
- });
272
+ .catch(handle_error);
273
+
274
+ const blocker = { promise, settled: false };
275
+ blockers.push(blocker);
263
276
 
264
- promises.push(promise);
277
+ promise.finally(() => {
278
+ blocker.settled = true;
279
+
280
+ unset_context();
281
+ current_batch?.deactivate();
282
+ });
265
283
  }
266
284
 
267
285
  promise
@@ -273,5 +291,12 @@ export function run(thunks) {
273
291
  batch.decrement(blocking);
274
292
  });
275
293
 
276
- return promises;
294
+ return blockers;
295
+ }
296
+
297
+ /**
298
+ * @param {Blocker[]} blockers
299
+ */
300
+ export function wait(blockers) {
301
+ return Promise.all(blockers.map((b) => b.promise));
277
302
  }
@@ -27,8 +27,6 @@ import {
27
27
  get,
28
28
  increment_write_version,
29
29
  is_dirty,
30
- is_updating_effect,
31
- set_is_updating_effect,
32
30
  update_effect
33
31
  } from '../runtime.js';
34
32
  import * as e from '../errors.js';
@@ -140,6 +138,8 @@ export class Batch {
140
138
 
141
139
  is_fork = false;
142
140
 
141
+ #decrement_queued = false;
142
+
143
143
  is_deferred() {
144
144
  return this.is_fork || this.#blocking_pending > 0;
145
145
  }
@@ -151,8 +151,6 @@ export class Batch {
151
151
  process(root_effects) {
152
152
  queued_root_effects = [];
153
153
 
154
- previous_batch = null;
155
-
156
154
  this.apply();
157
155
 
158
156
  /** @type {Effect[]} */
@@ -170,14 +168,18 @@ export class Batch {
170
168
  // log_inconsistent_branches(root);
171
169
  }
172
170
 
173
- if (!this.is_fork) {
174
- this.#resolve();
175
- }
176
-
177
171
  if (this.is_deferred()) {
178
172
  this.#defer_effects(render_effects);
179
173
  this.#defer_effects(effects);
180
174
  } else {
175
+ // append/remove branches
176
+ for (const fn of this.#commit_callbacks) fn();
177
+ this.#commit_callbacks.clear();
178
+
179
+ if (this.#pending === 0) {
180
+ this.#commit();
181
+ }
182
+
181
183
  // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
182
184
  // newly updated sources, which could lead to infinite loops when effects run over and over again.
183
185
  previous_batch = this;
@@ -330,18 +332,6 @@ export class Batch {
330
332
  this.#discard_callbacks.clear();
331
333
  }
332
334
 
333
- #resolve() {
334
- if (this.#blocking_pending === 0) {
335
- // append/remove branches
336
- for (const fn of this.#commit_callbacks) fn();
337
- this.#commit_callbacks.clear();
338
- }
339
-
340
- if (this.#pending === 0) {
341
- this.#commit();
342
- }
343
- }
344
-
345
335
  #commit() {
346
336
  // If there are other pending batches, they now need to be 'rebased' —
347
337
  // in other words, we re-run block/async effects with the newly
@@ -438,7 +428,22 @@ export class Batch {
438
428
  this.#pending -= 1;
439
429
  if (blocking) this.#blocking_pending -= 1;
440
430
 
441
- this.revive();
431
+ if (this.#decrement_queued) return;
432
+ this.#decrement_queued = true;
433
+
434
+ queue_micro_task(() => {
435
+ this.#decrement_queued = false;
436
+
437
+ if (!this.is_deferred()) {
438
+ // we only reschedule previously-deferred effects if we expect
439
+ // to be able to run them after processing the batch
440
+ this.revive();
441
+ } else if (queued_root_effects.length > 0) {
442
+ // if other effects are scheduled, process the batch _without_
443
+ // rescheduling the previously-deferred effects
444
+ this.flush();
445
+ }
446
+ });
442
447
  }
443
448
 
444
449
  revive() {
@@ -476,7 +481,7 @@ export class Batch {
476
481
  batches.add(current_batch);
477
482
 
478
483
  if (!is_flushing_sync) {
479
- Batch.enqueue(() => {
484
+ queue_micro_task(() => {
480
485
  if (current_batch !== batch) {
481
486
  // a flushSync happened in the meantime
482
487
  return;
@@ -490,11 +495,6 @@ export class Batch {
490
495
  return current_batch;
491
496
  }
492
497
 
493
- /** @param {() => void} task */
494
- static enqueue(task) {
495
- queue_micro_task(task);
496
- }
497
-
498
498
  apply() {
499
499
  if (!async_mode_flag || (!this.is_fork && batches.size === 1)) return;
500
500
 
@@ -561,14 +561,12 @@ export function flushSync(fn) {
561
561
  }
562
562
 
563
563
  function flush_effects() {
564
- var was_updating_effect = is_updating_effect;
565
564
  is_flushing = true;
566
565
 
567
566
  var source_stacks = DEV ? new Set() : null;
568
567
 
569
568
  try {
570
569
  var flush_count = 0;
571
- set_is_updating_effect(true);
572
570
 
573
571
  while (queued_root_effects.length > 0) {
574
572
  var batch = Batch.ensure();
@@ -612,7 +610,6 @@ function flush_effects() {
612
610
  }
613
611
  } finally {
614
612
  is_flushing = false;
615
- set_is_updating_effect(was_updating_effect);
616
613
 
617
614
  last_scheduled_effect = null;
618
615
 
@@ -1,4 +1,4 @@
1
- /** @import { ComponentContext, ComponentContextLegacy, Derived, Effect, TemplateNode, TransitionManager } from '#client' */
1
+ /** @import { Blocker, ComponentContext, ComponentContextLegacy, Derived, Effect, TemplateNode, TransitionManager } from '#client' */
2
2
  import {
3
3
  is_dirty,
4
4
  active_effect,
@@ -361,7 +361,7 @@ export function render_effect(fn, flags = 0) {
361
361
  * @param {(...expressions: any) => void | (() => void)} fn
362
362
  * @param {Array<() => any>} sync
363
363
  * @param {Array<() => Promise<any>>} async
364
- * @param {Array<Promise<void>>} blockers
364
+ * @param {Blocker[]} blockers
365
365
  */
366
366
  export function template_effect(fn, sync = [], async = [], blockers = []) {
367
367
  flatten(blockers, sync, async, (values) => {
@@ -374,7 +374,7 @@ export function template_effect(fn, sync = [], async = [], blockers = []) {
374
374
  * @param {(...expressions: any) => void | (() => void)} fn
375
375
  * @param {Array<() => any>} sync
376
376
  * @param {Array<() => Promise<any>>} async
377
- * @param {Array<Promise<void>>} blockers
377
+ * @param {Blocker[]} blockers
378
378
  */
379
379
  export function deferred_template_effect(fn, sync = [], async = [], blockers = []) {
380
380
  var batch = /** @type {Batch} */ (current_batch);