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.
- package/compiler/index.js +1 -1
- package/package.json +1 -1
- package/src/compiler/phases/2-analyze/index.js +201 -2
- package/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +6 -6
- package/src/compiler/phases/2-analyze/visitors/BindDirective.js +1 -0
- package/src/compiler/phases/2-analyze/visitors/SnippetBlock.js +6 -1
- package/src/compiler/phases/3-transform/client/transform-client.js +13 -34
- package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +18 -7
- package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +2 -2
- package/src/compiler/phases/3-transform/client/visitors/EachBlock.js +6 -4
- package/src/compiler/phases/3-transform/client/visitors/ExpressionStatement.js +1 -1
- package/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +6 -4
- package/src/compiler/phases/3-transform/client/visitors/IfBlock.js +6 -4
- package/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +5 -4
- package/src/compiler/phases/3-transform/client/visitors/Program.js +15 -3
- package/src/compiler/phases/3-transform/client/visitors/RenderTag.js +6 -4
- package/src/compiler/phases/3-transform/client/visitors/SlotElement.js +3 -1
- package/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +5 -4
- package/src/compiler/phases/3-transform/client/visitors/SvelteHead.js +3 -0
- package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +13 -2
- package/src/compiler/phases/3-transform/client/visitors/shared/component.js +20 -16
- package/src/compiler/phases/3-transform/client/visitors/shared/element.js +1 -0
- package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +15 -1
- package/src/compiler/phases/3-transform/server/transform-server.js +9 -9
- package/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js +6 -2
- package/src/compiler/phases/3-transform/server/visitors/CallExpression.js +8 -1
- package/src/compiler/phases/3-transform/server/visitors/EachBlock.js +14 -3
- package/src/compiler/phases/3-transform/server/visitors/HtmlTag.js +3 -5
- package/src/compiler/phases/3-transform/server/visitors/IfBlock.js +12 -4
- package/src/compiler/phases/3-transform/server/visitors/KeyBlock.js +7 -1
- package/src/compiler/phases/3-transform/server/visitors/Program.js +25 -0
- package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +14 -7
- package/src/compiler/phases/3-transform/server/visitors/RenderTag.js +27 -11
- package/src/compiler/phases/3-transform/server/visitors/SlotElement.js +7 -4
- package/src/compiler/phases/3-transform/server/visitors/SvelteElement.js +24 -4
- package/src/compiler/phases/3-transform/server/visitors/SvelteHead.js +10 -1
- package/src/compiler/phases/3-transform/server/visitors/shared/component.js +10 -4
- package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +79 -8
- package/src/compiler/phases/3-transform/shared/transform-async.js +102 -0
- package/src/compiler/phases/nodes.js +24 -0
- package/src/compiler/phases/scope.js +27 -8
- package/src/compiler/utils/ast.js +1 -1
- package/src/compiler/utils/builders.js +15 -4
- package/src/internal/client/dom/blocks/async.js +7 -6
- package/src/internal/client/dom/blocks/await.js +6 -2
- package/src/internal/client/dom/blocks/boundary.js +5 -8
- package/src/internal/client/dom/blocks/svelte-head.js +10 -19
- package/src/internal/client/dom/elements/attributes.js +3 -1
- package/src/internal/client/index.js +1 -0
- package/src/internal/client/proxy.js +1 -1
- package/src/internal/client/reactivity/async.js +107 -49
- package/src/internal/client/reactivity/batch.js +2 -15
- package/src/internal/client/reactivity/effects.js +3 -2
- package/src/internal/client/render.js +1 -9
- package/src/internal/client/runtime.js +12 -11
- package/src/internal/server/index.js +4 -3
- package/src/internal/server/renderer.js +58 -3
- package/src/version.js +1 -1
- package/types/index.d.ts.map +1 -1
- 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.
|
|
80
|
+
if (node.type === 'ExpressionTag' && node.metadata.expression.is_async()) {
|
|
81
81
|
flush();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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
|
}
|
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
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));
|