svelte 5.42.2 → 5.43.0

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 (60) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/compiler/phases/2-analyze/index.js +201 -2
  4. package/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +6 -6
  5. package/src/compiler/phases/2-analyze/visitors/BindDirective.js +1 -0
  6. package/src/compiler/phases/2-analyze/visitors/SnippetBlock.js +6 -1
  7. package/src/compiler/phases/3-transform/client/transform-client.js +13 -34
  8. package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +18 -7
  9. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +2 -2
  10. package/src/compiler/phases/3-transform/client/visitors/EachBlock.js +6 -4
  11. package/src/compiler/phases/3-transform/client/visitors/ExpressionStatement.js +1 -1
  12. package/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +6 -4
  13. package/src/compiler/phases/3-transform/client/visitors/IfBlock.js +6 -4
  14. package/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +5 -4
  15. package/src/compiler/phases/3-transform/client/visitors/Program.js +15 -3
  16. package/src/compiler/phases/3-transform/client/visitors/RenderTag.js +6 -4
  17. package/src/compiler/phases/3-transform/client/visitors/SlotElement.js +3 -1
  18. package/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +5 -4
  19. package/src/compiler/phases/3-transform/client/visitors/SvelteHead.js +3 -0
  20. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +13 -2
  21. package/src/compiler/phases/3-transform/client/visitors/shared/component.js +20 -16
  22. package/src/compiler/phases/3-transform/client/visitors/shared/element.js +1 -0
  23. package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +15 -1
  24. package/src/compiler/phases/3-transform/server/transform-server.js +9 -9
  25. package/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js +6 -2
  26. package/src/compiler/phases/3-transform/server/visitors/CallExpression.js +8 -1
  27. package/src/compiler/phases/3-transform/server/visitors/EachBlock.js +14 -3
  28. package/src/compiler/phases/3-transform/server/visitors/HtmlTag.js +3 -5
  29. package/src/compiler/phases/3-transform/server/visitors/IfBlock.js +12 -4
  30. package/src/compiler/phases/3-transform/server/visitors/KeyBlock.js +7 -1
  31. package/src/compiler/phases/3-transform/server/visitors/Program.js +25 -0
  32. package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +14 -7
  33. package/src/compiler/phases/3-transform/server/visitors/RenderTag.js +27 -11
  34. package/src/compiler/phases/3-transform/server/visitors/SlotElement.js +7 -4
  35. package/src/compiler/phases/3-transform/server/visitors/SvelteElement.js +24 -4
  36. package/src/compiler/phases/3-transform/server/visitors/SvelteHead.js +10 -1
  37. package/src/compiler/phases/3-transform/server/visitors/shared/component.js +10 -4
  38. package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +79 -8
  39. package/src/compiler/phases/3-transform/shared/transform-async.js +102 -0
  40. package/src/compiler/phases/nodes.js +24 -0
  41. package/src/compiler/phases/scope.js +27 -8
  42. package/src/compiler/utils/ast.js +1 -1
  43. package/src/compiler/utils/builders.js +15 -4
  44. package/src/internal/client/dom/blocks/async.js +7 -6
  45. package/src/internal/client/dom/blocks/await.js +6 -2
  46. package/src/internal/client/dom/blocks/boundary.js +5 -8
  47. package/src/internal/client/dom/blocks/svelte-head.js +10 -19
  48. package/src/internal/client/dom/elements/attributes.js +3 -1
  49. package/src/internal/client/index.js +1 -0
  50. package/src/internal/client/proxy.js +1 -1
  51. package/src/internal/client/reactivity/async.js +107 -49
  52. package/src/internal/client/reactivity/batch.js +2 -15
  53. package/src/internal/client/reactivity/effects.js +3 -2
  54. package/src/internal/client/render.js +1 -9
  55. package/src/internal/client/runtime.js +12 -11
  56. package/src/internal/server/index.js +4 -3
  57. package/src/internal/server/renderer.js +58 -3
  58. package/src/version.js +1 -1
  59. package/types/index.d.ts.map +1 -1
  60. package/src/compiler/phases/3-transform/client/visitors/ImportDeclaration.js +0 -16
@@ -1,4 +1,4 @@
1
- /** @import { Expression, Identifier, Node, Statement, BlockStatement } from 'estree' */
1
+ /** @import { Expression, Identifier, Node, Statement, BlockStatement, ArrayExpression } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext, ServerTransformState } from '../../types.js' */
4
4
 
@@ -77,12 +77,11 @@ export function process_children(nodes, { visit, state }) {
77
77
  }
78
78
 
79
79
  for (const node of nodes) {
80
- if (node.type === 'ExpressionTag' && node.metadata.expression.has_await) {
80
+ if (node.type === 'ExpressionTag' && node.metadata.expression.is_async()) {
81
81
  flush();
82
- const visited = /** @type {Expression} */ (visit(node.expression));
83
- state.template.push(
84
- b.stmt(b.call('$$renderer.push', b.thunk(b.call('$.escape', visited), true)))
85
- );
82
+
83
+ const expression = /** @type {Expression} */ (visit(node.expression));
84
+ state.template.push(create_push(b.call('$.escape', expression), node.metadata.expression));
86
85
  } else if (node.type === 'Text' || node.type === 'Comment' || node.type === 'ExpressionTag') {
87
86
  sequence.push(node);
88
87
  } else {
@@ -275,9 +274,50 @@ export function create_child_block(body, async) {
275
274
  /**
276
275
  * Creates a `$$renderer.async(...)` expression statement
277
276
  * @param {BlockStatement | Expression} body
277
+ * @param {ArrayExpression} blockers
278
+ * @param {boolean} has_await
279
+ * @param {boolean} needs_hydration_markers
280
+ */
281
+ export function create_async_block(
282
+ body,
283
+ blockers = b.array([]),
284
+ has_await = true,
285
+ needs_hydration_markers = true
286
+ ) {
287
+ return b.stmt(
288
+ b.call(
289
+ needs_hydration_markers ? '$$renderer.async_block' : '$$renderer.async',
290
+ blockers,
291
+ b.arrow([b.id('$$renderer')], body, has_await)
292
+ )
293
+ );
294
+ }
295
+
296
+ /**
297
+ * @param {Expression} expression
298
+ * @param {ExpressionMetadata} metadata
299
+ * @param {boolean} needs_hydration_markers
300
+ * @returns {Expression | Statement}
278
301
  */
279
- export function create_async_block(body) {
280
- return b.stmt(b.call('$$renderer.async', b.arrow([b.id('$$renderer')], body, true)));
302
+ export function create_push(expression, metadata, needs_hydration_markers = false) {
303
+ if (metadata.is_async()) {
304
+ let statement = b.stmt(b.call('$$renderer.push', b.thunk(expression, metadata.has_await)));
305
+
306
+ const blockers = metadata.blockers();
307
+
308
+ if (blockers.elements.length > 0) {
309
+ statement = create_async_block(
310
+ b.block([statement]),
311
+ blockers,
312
+ false,
313
+ needs_hydration_markers
314
+ );
315
+ }
316
+
317
+ return statement;
318
+ }
319
+
320
+ return expression;
281
321
  }
282
322
 
283
323
  /**
@@ -291,17 +331,36 @@ export function call_component_renderer(body, component_fn_id) {
291
331
  );
292
332
  }
293
333
 
334
+ /**
335
+ * A utility for optimising promises in templates. Without it code like
336
+ * `<Component foo={await fetch()} bar={await other()} />` would be transformed
337
+ * into two blocking promises, with it it's using `Promise.all` to await them.
338
+ * It also keeps track of blocking promises, i.e. those that need to be resolved before continuing.
339
+ */
294
340
  export class PromiseOptimiser {
295
341
  /** @type {Expression[]} */
296
342
  expressions = [];
297
343
 
344
+ has_await = false;
345
+
346
+ /** @type {Set<Expression>} */
347
+ #blockers = new Set();
348
+
298
349
  /**
299
350
  *
300
351
  * @param {Expression} expression
301
352
  * @param {ExpressionMetadata} metadata
302
353
  */
303
354
  transform = (expression, metadata) => {
355
+ for (const binding of metadata.dependencies) {
356
+ if (binding.blocker) {
357
+ this.#blockers.add(binding.blocker);
358
+ }
359
+ }
360
+
304
361
  if (metadata.has_await) {
362
+ this.has_await = true;
363
+
305
364
  const length = this.expressions.push(expression);
306
365
  return b.id(`$$${length - 1}`);
307
366
  }
@@ -310,6 +369,10 @@ export class PromiseOptimiser {
310
369
  };
311
370
 
312
371
  apply() {
372
+ if (this.expressions.length === 0) {
373
+ return b.empty;
374
+ }
375
+
313
376
  if (this.expressions.length === 1) {
314
377
  return b.const('$$0', this.expressions[0]);
315
378
  }
@@ -327,4 +390,12 @@ export class PromiseOptimiser {
327
390
  b.await(b.call('Promise.all', promises))
328
391
  );
329
392
  }
393
+
394
+ blockers() {
395
+ return b.array([...this.#blockers]);
396
+ }
397
+
398
+ is_async() {
399
+ return this.expressions.length > 0 || this.#blockers.size > 0;
400
+ }
330
401
  }
@@ -0,0 +1,102 @@
1
+ /** @import * as ESTree from 'estree' */
2
+ /** @import { ComponentAnalysis } from '../../types' */
3
+ import * as b from '#compiler/builders';
4
+
5
+ /**
6
+ * Transforms the body of the instance script in such a way that await expressions are made non-blocking as much as possible.
7
+ *
8
+ * Example Transformation:
9
+ * ```js
10
+ * let x = 1;
11
+ * let data = await fetch('/api');
12
+ * let y = data.value;
13
+ * ```
14
+ * becomes:
15
+ * ```js
16
+ * let x = 1;
17
+ * var data, y;
18
+ * var $$promises = $.run([
19
+ * () => data = await fetch('/api'),
20
+ * () => y = data.value
21
+ * ]);
22
+ * ```
23
+ * where `$$promises` is an array of promises that are resolved in the order they are declared,
24
+ * and which expressions in the template can await on like `await $$promises[0]` which means they
25
+ * wouldn't have to wait for e.g. `$$promises[1]` to resolve.
26
+ *
27
+ * @param {ComponentAnalysis['instance_body']} instance_body
28
+ * @param {ESTree.Expression} runner
29
+ * @param {(node: ESTree.Node) => ESTree.Node} transform
30
+ * @returns {Array<ESTree.Statement | ESTree.VariableDeclaration>}
31
+ */
32
+ export function transform_body(instance_body, runner, transform) {
33
+ // Any sync statements before the first await expression
34
+ const statements = instance_body.sync.map(
35
+ (node) => /** @type {ESTree.Statement | ESTree.VariableDeclaration} */ (transform(node))
36
+ );
37
+
38
+ // Declarations for the await expressions (they will asign to them; need to be hoisted to be available in whole instance scope)
39
+ if (instance_body.declarations.length > 0) {
40
+ statements.push(
41
+ b.declaration(
42
+ 'var',
43
+ instance_body.declarations.map((id) => b.declarator(id))
44
+ )
45
+ );
46
+ }
47
+
48
+ // Thunks for the await expressions
49
+ if (instance_body.async.length > 0) {
50
+ const thunks = instance_body.async.map((s) => {
51
+ if (s.node.type === 'VariableDeclarator') {
52
+ const visited = /** @type {ESTree.VariableDeclaration} */ (
53
+ transform(b.var(s.node.id, s.node.init))
54
+ );
55
+
56
+ if (visited.declarations.length === 1) {
57
+ return b.thunk(
58
+ b.assignment('=', s.node.id, visited.declarations[0].init ?? b.void0),
59
+ s.has_await
60
+ );
61
+ }
62
+
63
+ // if we have multiple declarations, it indicates destructuring
64
+ return b.thunk(
65
+ b.block([
66
+ b.var(visited.declarations[0].id, visited.declarations[0].init),
67
+ ...visited.declarations
68
+ .slice(1)
69
+ .map((d) => b.stmt(b.assignment('=', d.id, d.init ?? b.void0)))
70
+ ]),
71
+ s.has_await
72
+ );
73
+ }
74
+
75
+ if (s.node.type === 'ClassDeclaration') {
76
+ return b.thunk(
77
+ b.assignment(
78
+ '=',
79
+ s.node.id,
80
+ /** @type {ESTree.ClassExpression} */ ({ ...s.node, type: 'ClassExpression' })
81
+ ),
82
+ s.has_await
83
+ );
84
+ }
85
+
86
+ if (s.node.type === 'ExpressionStatement') {
87
+ const expression = /** @type {ESTree.Expression} */ (transform(s.node.expression));
88
+
89
+ return expression.type === 'AwaitExpression'
90
+ ? b.thunk(expression, true)
91
+ : b.thunk(b.unary('void', expression), s.has_await);
92
+ }
93
+
94
+ return b.thunk(b.block([/** @type {ESTree.Statement} */ (transform(s.node))]), s.has_await);
95
+ });
96
+
97
+ // TODO get the `$$promises` ID from scope
98
+ statements.push(b.var('$$promises', b.call(runner, b.array(thunks))));
99
+ }
100
+
101
+ return statements;
102
+ }
@@ -1,5 +1,6 @@
1
1
  /** @import { Expression, PrivateIdentifier } from 'estree' */
2
2
  /** @import { AST, Binding } from '#compiler' */
3
+ import * as b from '#compiler/builders';
3
4
 
4
5
  /**
5
6
  * All nodes that can appear elsewhere than the top level, have attributes and can contain children
@@ -91,6 +92,29 @@ export class ExpressionMetadata {
91
92
  * @type {Set<Binding>}
92
93
  */
93
94
  references = new Set();
95
+
96
+ /** @type {null | Set<Expression>} */
97
+ #blockers = null;
98
+
99
+ #get_blockers() {
100
+ if (!this.#blockers) {
101
+ this.#blockers = new Set();
102
+
103
+ for (const d of this.dependencies) {
104
+ if (d.blocker) this.#blockers.add(d.blocker);
105
+ }
106
+ }
107
+
108
+ return this.#blockers;
109
+ }
110
+
111
+ blockers() {
112
+ return b.array([...this.#get_blockers()]);
113
+ }
114
+
115
+ is_async() {
116
+ return this.has_await || this.#get_blockers().size > 0;
117
+ }
94
118
  }
95
119
 
96
120
  /**
@@ -108,6 +108,9 @@ export class Binding {
108
108
  /** @type {Array<{ node: Identifier; path: AST.SvelteNode[] }>} */
109
109
  references = [];
110
110
 
111
+ /** @type {Array<{ value: Expression; scope: Scope }>} */
112
+ assignments = [];
113
+
111
114
  /**
112
115
  * For `legacy_reactive`: its reactive dependencies
113
116
  * @type {Binding[]}
@@ -129,6 +132,15 @@ export class Binding {
129
132
  mutated = false;
130
133
  reassigned = false;
131
134
 
135
+ /**
136
+ * Instance-level declarations may follow (or contain) a top-level `await`. In these cases,
137
+ * any reads that occur in the template must wait for the corresponding promise to resolve
138
+ * otherwise the initial value will not have been assigned
139
+ * TODO the blocker is set during transform which feels a bit grubby
140
+ * @type {Expression | null}
141
+ */
142
+ blocker = null;
143
+
132
144
  /**
133
145
  *
134
146
  * @param {Scope} scope
@@ -143,6 +155,10 @@ export class Binding {
143
155
  this.initial = initial;
144
156
  this.kind = kind;
145
157
  this.declaration_kind = declaration_kind;
158
+
159
+ if (initial) {
160
+ this.assignments.push({ value: /** @type {Expression} */ (initial), scope });
161
+ }
146
162
  }
147
163
 
148
164
  get updated() {
@@ -859,7 +875,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
859
875
  /** @type {[Scope, { node: Identifier; path: AST.SvelteNode[] }][]} */
860
876
  const references = [];
861
877
 
862
- /** @type {[Scope, Pattern | MemberExpression][]} */
878
+ /** @type {[Scope, Pattern | MemberExpression, Expression][]} */
863
879
  const updates = [];
864
880
 
865
881
  /**
@@ -1047,12 +1063,13 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
1047
1063
 
1048
1064
  // updates
1049
1065
  AssignmentExpression(node, { state, next }) {
1050
- updates.push([state.scope, node.left]);
1066
+ updates.push([state.scope, node.left, node.right]);
1051
1067
  next();
1052
1068
  },
1053
1069
 
1054
1070
  UpdateExpression(node, { state, next }) {
1055
- updates.push([state.scope, /** @type {Identifier | MemberExpression} */ (node.argument)]);
1071
+ const expression = /** @type {Identifier | MemberExpression} */ (node.argument);
1072
+ updates.push([state.scope, expression, expression]);
1056
1073
  next();
1057
1074
  },
1058
1075
 
@@ -1273,10 +1290,11 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
1273
1290
  },
1274
1291
 
1275
1292
  BindDirective(node, context) {
1276
- updates.push([
1277
- context.state.scope,
1278
- /** @type {Identifier | MemberExpression} */ (node.expression)
1279
- ]);
1293
+ if (node.expression.type !== 'SequenceExpression') {
1294
+ const expression = /** @type {Identifier | MemberExpression} */ (node.expression);
1295
+ updates.push([context.state.scope, expression, expression]);
1296
+ }
1297
+
1280
1298
  context.next();
1281
1299
  },
1282
1300
 
@@ -1311,7 +1329,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
1311
1329
  scope.reference(node, path);
1312
1330
  }
1313
1331
 
1314
- for (const [scope, node] of updates) {
1332
+ for (const [scope, node, value] of updates) {
1315
1333
  for (const expression of unwrap_pattern(node)) {
1316
1334
  const left = object(expression);
1317
1335
  const binding = left && scope.get(left.name);
@@ -1319,6 +1337,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
1319
1337
  if (binding !== null && left !== binding.node) {
1320
1338
  if (left === expression) {
1321
1339
  binding.reassigned = true;
1340
+ binding.assignments.push({ value, scope });
1322
1341
  } else {
1323
1342
  binding.mutated = true;
1324
1343
  }
@@ -1,4 +1,4 @@
1
- /** @import { AST } from '#compiler' */
1
+ /** @import { AST, Scope } from '#compiler' */
2
2
  /** @import * as ESTree from 'estree' */
3
3
  import { walk } from 'zimmerframe';
4
4
  import * as b from '#compiler/builders';
@@ -82,6 +82,17 @@ export function block(body) {
82
82
  return { type: 'BlockStatement', body };
83
83
  }
84
84
 
85
+ /**
86
+ * @param {ESTree.Identifier | null} id
87
+ * @param {ESTree.ClassBody} body
88
+ * @param {ESTree.Expression | null} [superClass]
89
+ * @param {ESTree.Decorator[]} [decorators]
90
+ * @returns {ESTree.ClassExpression}
91
+ */
92
+ export function class_expression(id, body, superClass, decorators = []) {
93
+ return { type: 'ClassExpression', body, superClass, decorators };
94
+ }
95
+
85
96
  /**
86
97
  * @param {string} name
87
98
  * @param {ESTree.Statement} body
@@ -184,7 +195,7 @@ export function declaration(kind, declarations) {
184
195
 
185
196
  /**
186
197
  * @param {ESTree.Pattern | string} pattern
187
- * @param {ESTree.Expression} [init]
198
+ * @param {ESTree.Expression | null} [init]
188
199
  * @returns {ESTree.VariableDeclarator}
189
200
  */
190
201
  export function declarator(pattern, init) {
@@ -520,7 +531,7 @@ const this_instance = {
520
531
 
521
532
  /**
522
533
  * @param {string | ESTree.Pattern} pattern
523
- * @param { ESTree.Expression} [init]
534
+ * @param {ESTree.Expression | null} [init]
524
535
  * @returns {ESTree.VariableDeclaration}
525
536
  */
526
537
  function let_builder(pattern, init) {
@@ -529,7 +540,7 @@ function let_builder(pattern, init) {
529
540
 
530
541
  /**
531
542
  * @param {string | ESTree.Pattern} pattern
532
- * @param { ESTree.Expression} init
543
+ * @param {ESTree.Expression | null} init
533
544
  * @returns {ESTree.VariableDeclaration}
534
545
  */
535
546
  function const_builder(pattern, init) {
@@ -538,7 +549,7 @@ function const_builder(pattern, init) {
538
549
 
539
550
  /**
540
551
  * @param {string | ESTree.Pattern} pattern
541
- * @param { ESTree.Expression} [init]
552
+ * @param {ESTree.Expression | null} [init]
542
553
  * @returns {ESTree.VariableDeclaration}
543
554
  */
544
555
  function var_builder(pattern, init) {
@@ -14,10 +14,11 @@ import { get_boundary } from './boundary.js';
14
14
 
15
15
  /**
16
16
  * @param {TemplateNode} node
17
+ * @param {Array<Promise<void>>} blockers
17
18
  * @param {Array<() => Promise<any>>} expressions
18
19
  * @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn
19
20
  */
20
- export function async(node, expressions, fn) {
21
+ export function async(node, blockers = [], expressions = [], fn) {
21
22
  var boundary = get_boundary();
22
23
  var batch = /** @type {Batch} */ (current_batch);
23
24
  var blocking = !boundary.is_pending();
@@ -35,7 +36,7 @@ export function async(node, expressions, fn) {
35
36
  set_hydrate_node(end);
36
37
  }
37
38
 
38
- flatten([], expressions, (values) => {
39
+ flatten(blockers, [], expressions, (values) => {
39
40
  if (was_hydrating) {
40
41
  set_hydrating(true);
41
42
  set_hydrate_node(previous_hydrate_node);
@@ -47,12 +48,12 @@ export function async(node, expressions, fn) {
47
48
 
48
49
  fn(node, ...values);
49
50
  } finally {
51
+ if (was_hydrating) {
52
+ set_hydrating(false);
53
+ }
54
+
50
55
  boundary.update_pending_count(-1);
51
56
  batch.decrement(blocking);
52
57
  }
53
-
54
- if (was_hydrating) {
55
- set_hydrating(false);
56
- }
57
58
  });
58
59
  }
@@ -12,7 +12,7 @@ import {
12
12
  import { queue_micro_task } from '../task.js';
13
13
  import { HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js';
14
14
  import { is_runes } from '../../context.js';
15
- import { flushSync, is_flushing_sync } from '../../reactivity/batch.js';
15
+ import { Batch, flushSync, is_flushing_sync } from '../../reactivity/batch.js';
16
16
  import { BranchManager } from './branches.js';
17
17
  import { capture, unset_context } from '../../reactivity/async.js';
18
18
 
@@ -69,7 +69,11 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
69
69
  if (destroyed) return;
70
70
 
71
71
  resolved = true;
72
- restore();
72
+ // We don't want to restore the previous batch here; {#await} blocks don't follow the async logic
73
+ // we have elsewhere, instead pending/resolve/fail states are each their own batch so to speak.
74
+ restore(false);
75
+ // Make sure we have a batch, since the branch manager expects one to exist
76
+ Batch.ensure();
73
77
 
74
78
  if (hydrating) {
75
79
  // `restore()` could set `hydrating` to `true`, which we very much
@@ -34,7 +34,7 @@ import { queue_micro_task } from '../task.js';
34
34
  import * as e from '../../errors.js';
35
35
  import * as w from '../../warnings.js';
36
36
  import { DEV } from 'esm-env';
37
- import { Batch, effect_pending_updates } from '../../reactivity/batch.js';
37
+ import { Batch } from '../../reactivity/batch.js';
38
38
  import { internal_set, source } from '../../reactivity/sources.js';
39
39
  import { tag } from '../../dev/tracing.js';
40
40
  import { createSubscriber } from '../../../../reactivity/create-subscriber.js';
@@ -110,12 +110,6 @@ export class Boundary {
110
110
  */
111
111
  #effect_pending = null;
112
112
 
113
- #effect_pending_update = () => {
114
- if (this.#effect_pending) {
115
- internal_set(this.#effect_pending, this.#local_pending_count);
116
- }
117
- };
118
-
119
113
  #effect_pending_subscriber = createSubscriber(() => {
120
114
  this.#effect_pending = source(this.#local_pending_count);
121
115
 
@@ -329,7 +323,10 @@ export class Boundary {
329
323
  this.#update_pending_count(d);
330
324
 
331
325
  this.#local_pending_count += d;
332
- effect_pending_updates.add(this.#effect_pending_update);
326
+
327
+ if (this.#effect_pending) {
328
+ internal_set(this.#effect_pending, this.#local_pending_count);
329
+ }
333
330
  }
334
331
 
335
332
  get_effect_pending() {
@@ -3,22 +3,13 @@ import { hydrate_node, hydrating, set_hydrate_node, set_hydrating } from '../hyd
3
3
  import { create_text, get_first_child, get_next_sibling } from '../operations.js';
4
4
  import { block } from '../../reactivity/effects.js';
5
5
  import { COMMENT_NODE, HEAD_EFFECT } from '#client/constants';
6
- import { HYDRATION_START } from '../../../../constants.js';
7
-
8
- /**
9
- * @type {Node | undefined}
10
- */
11
- let head_anchor;
12
-
13
- export function reset_head_anchor() {
14
- head_anchor = undefined;
15
- }
16
6
 
17
7
  /**
8
+ * @param {string} hash
18
9
  * @param {(anchor: Node) => void} render_fn
19
10
  * @returns {void}
20
11
  */
21
- export function head(render_fn) {
12
+ export function head(hash, render_fn) {
22
13
  // The head function may be called after the first hydration pass and ssr comment nodes may still be present,
23
14
  // therefore we need to skip that when we detect that we're not in hydration mode.
24
15
  let previous_hydrate_node = null;
@@ -30,15 +21,13 @@ export function head(render_fn) {
30
21
  if (hydrating) {
31
22
  previous_hydrate_node = hydrate_node;
32
23
 
33
- // There might be multiple head blocks in our app, so we need to account for each one needing independent hydration.
34
- if (head_anchor === undefined) {
35
- head_anchor = /** @type {TemplateNode} */ (get_first_child(document.head));
36
- }
24
+ var head_anchor = /** @type {TemplateNode} */ (get_first_child(document.head));
37
25
 
26
+ // There might be multiple head blocks in our app, and they could have been
27
+ // rendered in an arbitrary order — find one corresponding to this component
38
28
  while (
39
29
  head_anchor !== null &&
40
- (head_anchor.nodeType !== COMMENT_NODE ||
41
- /** @type {Comment} */ (head_anchor).data !== HYDRATION_START)
30
+ (head_anchor.nodeType !== COMMENT_NODE || /** @type {Comment} */ (head_anchor).data !== hash)
42
31
  ) {
43
32
  head_anchor = /** @type {TemplateNode} */ (get_next_sibling(head_anchor));
44
33
  }
@@ -48,7 +37,10 @@ export function head(render_fn) {
48
37
  if (head_anchor === null) {
49
38
  set_hydrating(false);
50
39
  } else {
51
- head_anchor = set_hydrate_node(/** @type {TemplateNode} */ (get_next_sibling(head_anchor)));
40
+ var start = /** @type {TemplateNode} */ (get_next_sibling(head_anchor));
41
+ head_anchor.remove(); // in case this component is repeated
42
+
43
+ set_hydrate_node(start);
52
44
  }
53
45
  }
54
46
 
@@ -61,7 +53,6 @@ export function head(render_fn) {
61
53
  } finally {
62
54
  if (was_hydrating) {
63
55
  set_hydrating(true);
64
- head_anchor = hydrate_node; // so that next head block starts from the correct node
65
56
  set_hydrate_node(/** @type {TemplateNode} */ (previous_hydrate_node));
66
57
  }
67
58
  }
@@ -483,6 +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
487
  * @param {string} [css_hash]
487
488
  * @param {boolean} [should_remove_defaults]
488
489
  * @param {boolean} [skip_warning]
@@ -492,11 +493,12 @@ export function attribute_effect(
492
493
  fn,
493
494
  sync = [],
494
495
  async = [],
496
+ blockers = [],
495
497
  css_hash,
496
498
  should_remove_defaults = false,
497
499
  skip_warning = false
498
500
  ) {
499
- flatten(sync, async, (values) => {
501
+ flatten(blockers, sync, async, (values) => {
500
502
  /** @type {Record<string | symbol, any> | undefined} */
501
503
  var prev = undefined;
502
504
 
@@ -100,6 +100,7 @@ export {
100
100
  export {
101
101
  async_body,
102
102
  for_await_track_reactivity_loss,
103
+ run,
103
104
  save,
104
105
  track_reactivity_loss
105
106
  } from './reactivity/async.js';
@@ -267,7 +267,7 @@ export function proxy(value) {
267
267
  if (other_s !== undefined) {
268
268
  set(other_s, UNINITIALIZED);
269
269
  } else if (i in target) {
270
- // If the item exists in the original, we need to create a uninitialized source,
270
+ // If the item exists in the original, we need to create an uninitialized source,
271
271
  // else a later read of the property would result in a source being created with
272
272
  // the value of the original item at that index.
273
273
  other_s = with_parent(() => source(UNINITIALIZED, stack));