svelte 5.43.4 → 5.43.5
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/visitors/BindDirective.js +13 -2
- package/src/compiler/phases/2-analyze/visitors/StyleDirective.js +4 -3
- package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +6 -3
- package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +2 -17
- package/src/compiler/phases/3-transform/client/visitors/shared/component.js +6 -3
- package/src/compiler/phases/3-transform/client/visitors/shared/element.js +3 -3
- package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +13 -5
- package/src/compiler/phases/3-transform/server/visitors/shared/component.js +3 -0
- package/src/compiler/phases/3-transform/server/visitors/shared/element.js +2 -0
- package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +12 -6
- package/src/compiler/phases/nodes.js +15 -0
- package/src/internal/client/dom/template.js +8 -2
- package/src/internal/client/index.js +2 -1
- package/src/internal/client/reactivity/async.js +8 -0
- package/src/internal/client/validate.js +27 -23
- package/src/version.js +1 -1
package/package.json
CHANGED
|
@@ -159,6 +159,16 @@ export function BindDirective(node, context) {
|
|
|
159
159
|
|
|
160
160
|
mark_subtree_dynamic(context.path);
|
|
161
161
|
|
|
162
|
+
const [get, set] = node.expression.expressions;
|
|
163
|
+
// We gotta jump across the getter/setter functions to avoid the expression metadata field being reset to null
|
|
164
|
+
context.visit(get.type === 'ArrowFunctionExpression' ? get.body : get, {
|
|
165
|
+
...context.state,
|
|
166
|
+
expression: node.metadata.expression
|
|
167
|
+
});
|
|
168
|
+
context.visit(set.type === 'ArrowFunctionExpression' ? set.body : set, {
|
|
169
|
+
...context.state,
|
|
170
|
+
expression: node.metadata.expression
|
|
171
|
+
});
|
|
162
172
|
return;
|
|
163
173
|
}
|
|
164
174
|
|
|
@@ -247,7 +257,8 @@ export function BindDirective(node, context) {
|
|
|
247
257
|
|
|
248
258
|
node.metadata = {
|
|
249
259
|
binding_group_name: group_name,
|
|
250
|
-
parent_each_blocks: each_blocks
|
|
260
|
+
parent_each_blocks: each_blocks,
|
|
261
|
+
expression: node.metadata.expression
|
|
251
262
|
};
|
|
252
263
|
}
|
|
253
264
|
|
|
@@ -255,5 +266,5 @@ export function BindDirective(node, context) {
|
|
|
255
266
|
w.bind_invalid_each_rest(binding.node, binding.node.name);
|
|
256
267
|
}
|
|
257
268
|
|
|
258
|
-
context.next();
|
|
269
|
+
context.next({ ...context.state, expression: node.metadata.expression });
|
|
259
270
|
}
|
|
@@ -23,6 +23,9 @@ export function StyleDirective(node, context) {
|
|
|
23
23
|
if (binding.kind !== 'normal') {
|
|
24
24
|
node.metadata.expression.has_state = true;
|
|
25
25
|
}
|
|
26
|
+
if (binding.blocker) {
|
|
27
|
+
node.metadata.expression.dependencies.add(binding);
|
|
28
|
+
}
|
|
26
29
|
}
|
|
27
30
|
} else {
|
|
28
31
|
context.next();
|
|
@@ -30,9 +33,7 @@ export function StyleDirective(node, context) {
|
|
|
30
33
|
for (const chunk of get_attribute_chunks(node.value)) {
|
|
31
34
|
if (chunk.type !== 'ExpressionTag') continue;
|
|
32
35
|
|
|
33
|
-
node.metadata.expression.
|
|
34
|
-
node.metadata.expression.has_call ||= chunk.metadata.expression.has_call;
|
|
35
|
-
node.metadata.expression.has_await ||= chunk.metadata.expression.has_await;
|
|
36
|
+
node.metadata.expression.merge(chunk.metadata.expression);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -250,10 +250,13 @@ export function BindDirective(node, context) {
|
|
|
250
250
|
|
|
251
251
|
let statement = defer ? b.stmt(b.call('$.effect', b.thunk(call))) : b.stmt(call);
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
if (node.metadata.binding?.blocker) {
|
|
253
|
+
if (node.metadata.expression.is_async()) {
|
|
255
254
|
statement = b.stmt(
|
|
256
|
-
b.call(
|
|
255
|
+
b.call(
|
|
256
|
+
'$.run_after_blockers',
|
|
257
|
+
node.metadata.expression.blockers(),
|
|
258
|
+
b.thunk(b.block([statement]))
|
|
259
|
+
)
|
|
257
260
|
);
|
|
258
261
|
}
|
|
259
262
|
|
|
@@ -484,21 +484,6 @@ function setup_select_synchronization(value_binding, context) {
|
|
|
484
484
|
);
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
/**
|
|
488
|
-
* @param {ExpressionMetadata} target
|
|
489
|
-
* @param {ExpressionMetadata} source
|
|
490
|
-
*/
|
|
491
|
-
function merge_metadata(target, source) {
|
|
492
|
-
target.has_assignment ||= source.has_assignment;
|
|
493
|
-
target.has_await ||= source.has_await;
|
|
494
|
-
target.has_call ||= source.has_call;
|
|
495
|
-
target.has_member_expression ||= source.has_member_expression;
|
|
496
|
-
target.has_state ||= source.has_state;
|
|
497
|
-
|
|
498
|
-
for (const r of source.references) target.references.add(r);
|
|
499
|
-
for (const b of source.dependencies) target.dependencies.add(b);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
487
|
/**
|
|
503
488
|
* @param {AST.ClassDirective[]} class_directives
|
|
504
489
|
* @param {ComponentContext} context
|
|
@@ -514,7 +499,7 @@ export function build_class_directives_object(
|
|
|
514
499
|
const metadata = new ExpressionMetadata();
|
|
515
500
|
|
|
516
501
|
for (const d of class_directives) {
|
|
517
|
-
|
|
502
|
+
metadata.merge(d.metadata.expression);
|
|
518
503
|
|
|
519
504
|
const expression = /** @type Expression */ (context.visit(d.expression));
|
|
520
505
|
properties.push(b.init(d.name, expression));
|
|
@@ -541,7 +526,7 @@ export function build_style_directives_object(
|
|
|
541
526
|
const metadata = new ExpressionMetadata();
|
|
542
527
|
|
|
543
528
|
for (const d of style_directives) {
|
|
544
|
-
|
|
529
|
+
metadata.merge(d.metadata.expression);
|
|
545
530
|
|
|
546
531
|
const expression =
|
|
547
532
|
d.value === true
|
|
@@ -171,8 +171,6 @@ export function build_component(node, component_name, context) {
|
|
|
171
171
|
attribute.value,
|
|
172
172
|
context,
|
|
173
173
|
(value, metadata) => {
|
|
174
|
-
if (!metadata.has_state && !metadata.has_await) return value;
|
|
175
|
-
|
|
176
174
|
// When we have a non-simple computation, anything other than an Identifier or Member expression,
|
|
177
175
|
// then there's a good chance it needs to be memoized to avoid over-firing when read within the
|
|
178
176
|
// child component (e.g. `active={i === index}`)
|
|
@@ -198,7 +196,12 @@ export function build_component(node, component_name, context) {
|
|
|
198
196
|
push_prop(b.init(attribute.name, value));
|
|
199
197
|
}
|
|
200
198
|
} else if (attribute.type === 'BindDirective') {
|
|
201
|
-
const expression = /** @type {Expression} */ (
|
|
199
|
+
const expression = /** @type {Expression} */ (
|
|
200
|
+
context.visit(attribute.expression, { ...context.state, memoizer })
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Bindings are a bit special: we don't want to add them to (async) deriveds but we need to check if they have blockers
|
|
204
|
+
memoizer.check_blockers(attribute.metadata.expression);
|
|
202
205
|
|
|
203
206
|
if (
|
|
204
207
|
dev &&
|
|
@@ -122,7 +122,7 @@ export function build_attribute_value(value, context, memoize = (value) => value
|
|
|
122
122
|
|
|
123
123
|
return {
|
|
124
124
|
value: memoize(expression, chunk.metadata.expression),
|
|
125
|
-
has_state: chunk.metadata.expression.has_state || chunk.metadata.expression.
|
|
125
|
+
has_state: chunk.metadata.expression.has_state || chunk.metadata.expression.is_async()
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -170,7 +170,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c
|
|
|
170
170
|
if (class_directives.length) {
|
|
171
171
|
next = build_class_directives_object(class_directives, context);
|
|
172
172
|
has_state ||= class_directives.some(
|
|
173
|
-
(d) => d.metadata.expression.has_state || d.metadata.expression.
|
|
173
|
+
(d) => d.metadata.expression.has_state || d.metadata.expression.is_async()
|
|
174
174
|
);
|
|
175
175
|
|
|
176
176
|
if (has_state) {
|
|
@@ -240,7 +240,7 @@ export function build_set_style(node_id, attribute, style_directives, context) {
|
|
|
240
240
|
if (style_directives.length) {
|
|
241
241
|
next = build_style_directives_object(style_directives, context);
|
|
242
242
|
has_state ||= style_directives.some(
|
|
243
|
-
(d) => d.metadata.expression.has_state || d.metadata.expression.
|
|
243
|
+
(d) => d.metadata.expression.has_state || d.metadata.expression.is_async()
|
|
244
244
|
);
|
|
245
245
|
|
|
246
246
|
if (has_state) {
|
|
@@ -31,11 +31,7 @@ export class Memoizer {
|
|
|
31
31
|
* @param {boolean} memoize_if_state
|
|
32
32
|
*/
|
|
33
33
|
add(expression, metadata, memoize_if_state = false) {
|
|
34
|
-
|
|
35
|
-
if (binding.blocker) {
|
|
36
|
-
this.#blockers.add(binding.blocker);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
34
|
+
this.check_blockers(metadata);
|
|
39
35
|
|
|
40
36
|
const should_memoize =
|
|
41
37
|
metadata.has_call || metadata.has_await || (memoize_if_state && metadata.has_state);
|
|
@@ -52,6 +48,17 @@ export class Memoizer {
|
|
|
52
48
|
return id;
|
|
53
49
|
}
|
|
54
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @param {ExpressionMetadata} metadata
|
|
53
|
+
*/
|
|
54
|
+
check_blockers(metadata) {
|
|
55
|
+
for (const binding of metadata.dependencies) {
|
|
56
|
+
if (binding.blocker) {
|
|
57
|
+
this.#blockers.add(binding.blocker);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
apply() {
|
|
56
63
|
return [...this.#sync, ...this.#async].map((memo, i) => {
|
|
57
64
|
memo.id.name = `$${i}`;
|
|
@@ -348,6 +355,7 @@ export function validate_binding(state, binding, expression) {
|
|
|
348
355
|
b.call(
|
|
349
356
|
'$.validate_binding',
|
|
350
357
|
b.literal(state.analysis.source.slice(binding.start, binding.end)),
|
|
358
|
+
binding.metadata.expression.blockers(),
|
|
351
359
|
b.thunk(
|
|
352
360
|
state.store_to_invalidate ? b.sequence([b.call('$.mark_store_binding'), obj]) : obj
|
|
353
361
|
),
|
|
@@ -107,6 +107,9 @@ export function build_inline_component(node, expression, context) {
|
|
|
107
107
|
|
|
108
108
|
push_prop(b.prop('init', b.key(attribute.name), value));
|
|
109
109
|
} else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
|
|
110
|
+
// Bindings are a bit special: we don't want to add them to (async) deriveds but we need to check if they have blockers
|
|
111
|
+
optimiser.check_blockers(attribute.metadata.expression);
|
|
112
|
+
|
|
110
113
|
if (attribute.expression.type === 'SequenceExpression') {
|
|
111
114
|
const [get, set] = /** @type {SequenceExpression} */ (context.visit(attribute.expression))
|
|
112
115
|
.expressions;
|
|
@@ -121,6 +121,8 @@ export function build_element_attributes(node, context, transform) {
|
|
|
121
121
|
expression = b.call(expression.expressions[0]);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
expression = transform(expression, attribute.metadata.expression);
|
|
125
|
+
|
|
124
126
|
if (is_content_editable_binding(attribute.name)) {
|
|
125
127
|
content = expression;
|
|
126
128
|
} else if (attribute.name === 'value' && node.name === 'textarea') {
|
|
@@ -347,16 +347,11 @@ export class PromiseOptimiser {
|
|
|
347
347
|
#blockers = new Set();
|
|
348
348
|
|
|
349
349
|
/**
|
|
350
|
-
*
|
|
351
350
|
* @param {Expression} expression
|
|
352
351
|
* @param {ExpressionMetadata} metadata
|
|
353
352
|
*/
|
|
354
353
|
transform = (expression, metadata) => {
|
|
355
|
-
|
|
356
|
-
if (binding.blocker) {
|
|
357
|
-
this.#blockers.add(binding.blocker);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
354
|
+
this.check_blockers(metadata);
|
|
360
355
|
|
|
361
356
|
if (metadata.has_await) {
|
|
362
357
|
this.has_await = true;
|
|
@@ -368,6 +363,17 @@ export class PromiseOptimiser {
|
|
|
368
363
|
return expression;
|
|
369
364
|
};
|
|
370
365
|
|
|
366
|
+
/**
|
|
367
|
+
* @param {ExpressionMetadata} metadata
|
|
368
|
+
*/
|
|
369
|
+
check_blockers(metadata) {
|
|
370
|
+
for (const binding of metadata.dependencies) {
|
|
371
|
+
if (binding.blocker) {
|
|
372
|
+
this.#blockers.add(binding.blocker);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
371
377
|
apply() {
|
|
372
378
|
if (this.expressions.length === 0) {
|
|
373
379
|
return b.empty;
|
|
@@ -115,6 +115,21 @@ export class ExpressionMetadata {
|
|
|
115
115
|
is_async() {
|
|
116
116
|
return this.has_await || this.#get_blockers().size > 0;
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {ExpressionMetadata} source
|
|
121
|
+
*/
|
|
122
|
+
merge(source) {
|
|
123
|
+
this.has_state ||= source.has_state;
|
|
124
|
+
this.has_call ||= source.has_call;
|
|
125
|
+
this.has_await ||= source.has_await;
|
|
126
|
+
this.has_member_expression ||= source.has_member_expression;
|
|
127
|
+
this.has_assignment ||= source.has_assignment;
|
|
128
|
+
this.#blockers = null; // so that blockers are recalculated
|
|
129
|
+
|
|
130
|
+
for (const r of source.references) this.references.add(r);
|
|
131
|
+
for (const b of source.dependencies) this.dependencies.add(b);
|
|
132
|
+
}
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
/**
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
TEMPLATE_USE_MATHML,
|
|
21
21
|
TEMPLATE_USE_SVG
|
|
22
22
|
} from '../../../constants.js';
|
|
23
|
-
import { COMMENT_NODE, DOCUMENT_FRAGMENT_NODE, TEXT_NODE } from '#client/constants';
|
|
23
|
+
import { COMMENT_NODE, DOCUMENT_FRAGMENT_NODE, EFFECT_RAN, TEXT_NODE } from '#client/constants';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* @param {TemplateNode} start
|
|
@@ -344,7 +344,13 @@ export function comment() {
|
|
|
344
344
|
*/
|
|
345
345
|
export function append(anchor, dom) {
|
|
346
346
|
if (hydrating) {
|
|
347
|
-
/** @type {Effect} */ (active_effect)
|
|
347
|
+
var effect = /** @type {Effect} */ (active_effect);
|
|
348
|
+
// When hydrating and outer component and an inner component is async, i.e. blocked on a promise,
|
|
349
|
+
// then by the time the inner resolves we have already advanced to the end of the hydrated nodes
|
|
350
|
+
// of the parent component. Check for defined for that reason to avoid rewinding the parent's end marker.
|
|
351
|
+
if ((effect.f & EFFECT_RAN) === 0 || effect.nodes_end === null) {
|
|
352
|
+
effect.nodes_end = hydrate_node;
|
|
353
|
+
}
|
|
348
354
|
hydrate_next();
|
|
349
355
|
return;
|
|
350
356
|
}
|
|
@@ -102,7 +102,8 @@ export {
|
|
|
102
102
|
for_await_track_reactivity_loss,
|
|
103
103
|
run,
|
|
104
104
|
save,
|
|
105
|
-
track_reactivity_loss
|
|
105
|
+
track_reactivity_loss,
|
|
106
|
+
run_after_blockers
|
|
106
107
|
} from './reactivity/async.js';
|
|
107
108
|
export { eager, flushSync as flush } from './reactivity/batch.js';
|
|
108
109
|
export {
|
|
@@ -84,6 +84,14 @@ export function flatten(blockers, sync, async, fn) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @param {Array<Promise<void>>} blockers
|
|
89
|
+
* @param {(values: Value[]) => any} fn
|
|
90
|
+
*/
|
|
91
|
+
export function run_after_blockers(blockers, fn) {
|
|
92
|
+
flatten(blockers, [], [], fn);
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
/**
|
|
88
96
|
* Captures the current effect context so that we can restore it after
|
|
89
97
|
* some asynchronous work has happened (so that e.g. `await a + b`
|
|
@@ -5,6 +5,7 @@ import { FILENAME } from '../../constants.js';
|
|
|
5
5
|
import { render_effect } from './reactivity/effects.js';
|
|
6
6
|
import * as w from './warnings.js';
|
|
7
7
|
import { capture_store_binding } from './reactivity/store.js';
|
|
8
|
+
import { run_after_blockers } from './reactivity/async.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @param {() => any} collection
|
|
@@ -40,44 +41,47 @@ export function validate_each_keys(collection, key_fn) {
|
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* @param {string} binding
|
|
44
|
+
* @param {Array<Promise<void>>} blockers
|
|
43
45
|
* @param {() => Record<string, any>} get_object
|
|
44
46
|
* @param {() => string} get_property
|
|
45
47
|
* @param {number} line
|
|
46
48
|
* @param {number} column
|
|
47
49
|
*/
|
|
48
|
-
export function validate_binding(binding, get_object, get_property, line, column) {
|
|
49
|
-
|
|
50
|
+
export function validate_binding(binding, blockers, get_object, get_property, line, column) {
|
|
51
|
+
run_after_blockers(blockers, () => {
|
|
52
|
+
var warned = false;
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
var filename = dev_current_component_function?.[FILENAME];
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
render_effect(() => {
|
|
57
|
+
if (warned) return;
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
var [object, is_store_sub] = capture_store_binding(get_object);
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
if (is_store_sub) return;
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
var property = get_property();
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
var ran = false;
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// by making the (possibly false, but it would be an extreme edge case) assumption
|
|
68
|
+
// that a getter has a corresponding setter, we can determine if a property is
|
|
69
|
+
// reactive by seeing if this effect has dependencies
|
|
70
|
+
var effect = render_effect(() => {
|
|
71
|
+
if (ran) return;
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
74
|
+
object[property];
|
|
75
|
+
});
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
ran = true;
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
if (effect.deps === null) {
|
|
80
|
+
var location = `${filename}:${line}:${column}`;
|
|
81
|
+
w.binding_property_non_reactive(binding, location);
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
warned = true;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
82
86
|
});
|
|
83
87
|
}
|
package/src/version.js
CHANGED