svelte 5.46.1 → 5.46.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compiler/index.js +1 -1
- package/package.json +1 -1
- package/src/compiler/phases/1-parse/read/script.js +8 -1
- package/src/compiler/phases/2-analyze/index.js +2 -1
- package/src/compiler/phases/2-analyze/visitors/EachBlock.js +3 -1
- package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +5 -6
- package/src/compiler/phases/3-transform/shared/transform-async.js +8 -1
- package/src/internal/client/dom/blocks/async.js +1 -1
- package/src/internal/client/dom/blocks/boundary.js +52 -17
- package/src/internal/client/index.js +0 -1
- package/src/internal/client/reactivity/async.js +4 -54
- package/src/internal/client/reactivity/batch.js +55 -87
- package/src/internal/client/reactivity/deriveds.js +18 -12
- package/src/internal/client/reactivity/effects.js +2 -2
- package/src/internal/client/reactivity/sources.js +5 -3
- package/src/internal/client/reactivity/status.js +25 -0
- package/src/internal/client/reactivity/utils.js +40 -0
- package/src/internal/client/runtime.js +45 -45
- package/src/legacy/legacy-client.js +2 -1
- package/src/version.js +1 -1
- package/types/index.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import { regex_not_newline_characters } from '../../patterns.js';
|
|
|
6
6
|
import * as e from '../../../errors.js';
|
|
7
7
|
import * as w from '../../../warnings.js';
|
|
8
8
|
import { is_text_attribute } from '../../../utils/ast.js';
|
|
9
|
+
import { locator } from '../../../state.js';
|
|
9
10
|
|
|
10
11
|
const regex_closing_script_tag = /<\/script\s*>/;
|
|
11
12
|
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
|
|
@@ -39,9 +40,15 @@ export function read_script(parser, start, attributes) {
|
|
|
39
40
|
parser.acorn_error(err);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
// TODO is this necessary?
|
|
43
43
|
ast.start = script_start;
|
|
44
44
|
|
|
45
|
+
if (ast.loc) {
|
|
46
|
+
// Acorn always uses `0` as the start of a `Program`, but for sourcemap purposes
|
|
47
|
+
// we need it to be the start of the `<script>` contents
|
|
48
|
+
({ line: ast.loc.start.line, column: ast.loc.start.column } = locator(start));
|
|
49
|
+
({ line: ast.loc.end.line, column: ast.loc.end.column } = locator(parser.index));
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
/** @type {'default' | 'module'} */
|
|
46
53
|
let context = 'default';
|
|
47
54
|
|
|
@@ -657,7 +657,8 @@ export function analyze_component(root, source, options) {
|
|
|
657
657
|
if (
|
|
658
658
|
binding &&
|
|
659
659
|
binding.kind === 'normal' &&
|
|
660
|
-
binding.declaration_kind !== 'import'
|
|
660
|
+
binding.declaration_kind !== 'import' &&
|
|
661
|
+
binding.declaration_kind !== 'function'
|
|
661
662
|
) {
|
|
662
663
|
binding.kind = 'state';
|
|
663
664
|
binding.mutated = true;
|
|
@@ -54,7 +54,9 @@ export function EachBlock(node, context) {
|
|
|
54
54
|
|
|
55
55
|
// collect transitive dependencies...
|
|
56
56
|
for (const binding of node.metadata.expression.dependencies) {
|
|
57
|
-
|
|
57
|
+
if (binding.declaration_kind !== 'function') {
|
|
58
|
+
collect_transitive_dependencies(binding, node.metadata.transitive_deps);
|
|
59
|
+
}
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// ...and ensure they are marked as state, so they can be turned
|
|
@@ -209,11 +209,11 @@ export function VariableDeclaration(node, context) {
|
|
|
209
209
|
let call = b.call(
|
|
210
210
|
'$.async_derived',
|
|
211
211
|
b.thunk(expression, true),
|
|
212
|
+
dev && b.literal(declarator.id.name),
|
|
212
213
|
location ? b.literal(location) : undefined
|
|
213
214
|
);
|
|
214
215
|
|
|
215
216
|
call = should_save ? save(call) : b.await(call);
|
|
216
|
-
if (dev) call = b.call('$.tag', call, b.literal(declarator.id.name));
|
|
217
217
|
|
|
218
218
|
declarations.push(b.declarator(declarator.id, call));
|
|
219
219
|
} else {
|
|
@@ -244,17 +244,16 @@ export function VariableDeclaration(node, context) {
|
|
|
244
244
|
call = b.call(
|
|
245
245
|
'$.async_derived',
|
|
246
246
|
b.thunk(expression, true),
|
|
247
|
+
dev &&
|
|
248
|
+
b.literal(
|
|
249
|
+
`[$derived ${declarator.id.type === 'ArrayPattern' ? 'iterable' : 'object'}]`
|
|
250
|
+
),
|
|
247
251
|
location ? b.literal(location) : undefined
|
|
248
252
|
);
|
|
249
253
|
|
|
250
254
|
call = should_save ? save(call) : b.await(call);
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
if (dev) {
|
|
254
|
-
const label = `[$derived ${declarator.id.type === 'ArrayPattern' ? 'iterable' : 'object'}]`;
|
|
255
|
-
call = b.call('$.tag', call, b.literal(label));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
257
|
declarations.push(b.declarator(id, call));
|
|
259
258
|
}
|
|
260
259
|
|
|
@@ -86,7 +86,14 @@ export function transform_body(instance_body, runner, transform) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
if (s.node.type === 'ExpressionStatement') {
|
|
89
|
-
|
|
89
|
+
// the expression may be a $inspect call, which will be transformed into an empty statement
|
|
90
|
+
const expression = /** @type {ESTree.Expression | ESTree.EmptyStatement} */ (
|
|
91
|
+
transform(s.node.expression)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (expression.type === 'EmptyStatement') {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
90
97
|
|
|
91
98
|
return expression.type === 'AwaitExpression'
|
|
92
99
|
? b.thunk(expression, true)
|
|
@@ -21,7 +21,7 @@ import { get_boundary } from './boundary.js';
|
|
|
21
21
|
export function async(node, blockers = [], expressions = [], fn) {
|
|
22
22
|
var boundary = get_boundary();
|
|
23
23
|
var batch = /** @type {Batch} */ (current_batch);
|
|
24
|
-
var blocking =
|
|
24
|
+
var blocking = boundary.is_rendered();
|
|
25
25
|
|
|
26
26
|
boundary.update_pending_count(1);
|
|
27
27
|
batch.increment(blocking);
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
BOUNDARY_EFFECT,
|
|
4
4
|
COMMENT_NODE,
|
|
5
|
+
DIRTY,
|
|
5
6
|
EFFECT_PRESERVED,
|
|
6
|
-
EFFECT_TRANSPARENT
|
|
7
|
+
EFFECT_TRANSPARENT,
|
|
8
|
+
MAYBE_DIRTY
|
|
7
9
|
} from '#client/constants';
|
|
8
10
|
import { HYDRATION_START_ELSE } from '../../../../constants.js';
|
|
9
11
|
import { component_context, set_component_context } from '../../context.js';
|
|
@@ -34,11 +36,13 @@ import { queue_micro_task } from '../task.js';
|
|
|
34
36
|
import * as e from '../../errors.js';
|
|
35
37
|
import * as w from '../../warnings.js';
|
|
36
38
|
import { DEV } from 'esm-env';
|
|
37
|
-
import { Batch } from '../../reactivity/batch.js';
|
|
39
|
+
import { Batch, schedule_effect } from '../../reactivity/batch.js';
|
|
38
40
|
import { internal_set, source } from '../../reactivity/sources.js';
|
|
39
41
|
import { tag } from '../../dev/tracing.js';
|
|
40
42
|
import { createSubscriber } from '../../../../reactivity/create-subscriber.js';
|
|
41
43
|
import { create_text } from '../operations.js';
|
|
44
|
+
import { defer_effect } from '../../reactivity/utils.js';
|
|
45
|
+
import { set_signal_status } from '../../reactivity/status.js';
|
|
42
46
|
|
|
43
47
|
/**
|
|
44
48
|
* @typedef {{
|
|
@@ -64,7 +68,7 @@ export class Boundary {
|
|
|
64
68
|
/** @type {Boundary | null} */
|
|
65
69
|
parent;
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
is_pending = false;
|
|
68
72
|
|
|
69
73
|
/** @type {TemplateNode} */
|
|
70
74
|
#anchor;
|
|
@@ -101,6 +105,12 @@ export class Boundary {
|
|
|
101
105
|
|
|
102
106
|
#is_creating_fallback = false;
|
|
103
107
|
|
|
108
|
+
/** @type {Set<Effect>} */
|
|
109
|
+
#dirty_effects = new Set();
|
|
110
|
+
|
|
111
|
+
/** @type {Set<Effect>} */
|
|
112
|
+
#maybe_dirty_effects = new Set();
|
|
113
|
+
|
|
104
114
|
/**
|
|
105
115
|
* A source containing the number of pending async deriveds/expressions.
|
|
106
116
|
* Only created if `$effect.pending()` is used inside the boundary,
|
|
@@ -134,7 +144,7 @@ export class Boundary {
|
|
|
134
144
|
|
|
135
145
|
this.parent = /** @type {Effect} */ (active_effect).b;
|
|
136
146
|
|
|
137
|
-
this
|
|
147
|
+
this.is_pending = !!this.#props.pending;
|
|
138
148
|
|
|
139
149
|
this.#effect = block(() => {
|
|
140
150
|
/** @type {Effect} */ (active_effect).b = this;
|
|
@@ -151,6 +161,10 @@ export class Boundary {
|
|
|
151
161
|
this.#hydrate_pending_content();
|
|
152
162
|
} else {
|
|
153
163
|
this.#hydrate_resolved_content();
|
|
164
|
+
|
|
165
|
+
if (this.#pending_count === 0) {
|
|
166
|
+
this.is_pending = false;
|
|
167
|
+
}
|
|
154
168
|
}
|
|
155
169
|
} else {
|
|
156
170
|
var anchor = this.#get_anchor();
|
|
@@ -164,7 +178,7 @@ export class Boundary {
|
|
|
164
178
|
if (this.#pending_count > 0) {
|
|
165
179
|
this.#show_pending_snippet();
|
|
166
180
|
} else {
|
|
167
|
-
this
|
|
181
|
+
this.is_pending = false;
|
|
168
182
|
}
|
|
169
183
|
}
|
|
170
184
|
|
|
@@ -184,10 +198,6 @@ export class Boundary {
|
|
|
184
198
|
} catch (error) {
|
|
185
199
|
this.error(error);
|
|
186
200
|
}
|
|
187
|
-
|
|
188
|
-
// Since server rendered resolved content, we never show pending state
|
|
189
|
-
// Even if client-side async operations are still running, the content is already displayed
|
|
190
|
-
this.#pending = false;
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
#hydrate_pending_content() {
|
|
@@ -212,7 +222,7 @@ export class Boundary {
|
|
|
212
222
|
this.#pending_effect = null;
|
|
213
223
|
});
|
|
214
224
|
|
|
215
|
-
this
|
|
225
|
+
this.is_pending = false;
|
|
216
226
|
}
|
|
217
227
|
});
|
|
218
228
|
}
|
|
@@ -220,7 +230,7 @@ export class Boundary {
|
|
|
220
230
|
#get_anchor() {
|
|
221
231
|
var anchor = this.#anchor;
|
|
222
232
|
|
|
223
|
-
if (this
|
|
233
|
+
if (this.is_pending) {
|
|
224
234
|
this.#pending_anchor = create_text();
|
|
225
235
|
this.#anchor.before(this.#pending_anchor);
|
|
226
236
|
|
|
@@ -231,11 +241,19 @@ export class Boundary {
|
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
/**
|
|
234
|
-
*
|
|
244
|
+
* Defer an effect inside a pending boundary until the boundary resolves
|
|
245
|
+
* @param {Effect} effect
|
|
246
|
+
*/
|
|
247
|
+
defer_effect(effect) {
|
|
248
|
+
defer_effect(effect, this.#dirty_effects, this.#maybe_dirty_effects);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Returns `false` if the effect exists inside a boundary whose pending snippet is shown
|
|
235
253
|
* @returns {boolean}
|
|
236
254
|
*/
|
|
237
|
-
|
|
238
|
-
return this
|
|
255
|
+
is_rendered() {
|
|
256
|
+
return !this.is_pending && (!this.parent || this.parent.is_rendered());
|
|
239
257
|
}
|
|
240
258
|
|
|
241
259
|
has_pending_snippet() {
|
|
@@ -298,7 +316,24 @@ export class Boundary {
|
|
|
298
316
|
this.#pending_count += d;
|
|
299
317
|
|
|
300
318
|
if (this.#pending_count === 0) {
|
|
301
|
-
this
|
|
319
|
+
this.is_pending = false;
|
|
320
|
+
|
|
321
|
+
// any effects that were encountered and deferred during traversal
|
|
322
|
+
// should be rescheduled — after the next traversal (which will happen
|
|
323
|
+
// immediately, due to the same update that brought us here)
|
|
324
|
+
// the effects will be flushed
|
|
325
|
+
for (const e of this.#dirty_effects) {
|
|
326
|
+
set_signal_status(e, DIRTY);
|
|
327
|
+
schedule_effect(e);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
for (const e of this.#maybe_dirty_effects) {
|
|
331
|
+
set_signal_status(e, MAYBE_DIRTY);
|
|
332
|
+
schedule_effect(e);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.#dirty_effects.clear();
|
|
336
|
+
this.#maybe_dirty_effects.clear();
|
|
302
337
|
|
|
303
338
|
if (this.#pending_effect) {
|
|
304
339
|
pause_effect(this.#pending_effect, () => {
|
|
@@ -394,7 +429,7 @@ export class Boundary {
|
|
|
394
429
|
|
|
395
430
|
// we intentionally do not try to find the nearest pending boundary. If this boundary has one, we'll render it on reset
|
|
396
431
|
// but it would be really weird to show the parent's boundary on a child reset.
|
|
397
|
-
this
|
|
432
|
+
this.is_pending = this.has_pending_snippet();
|
|
398
433
|
|
|
399
434
|
this.#main_effect = this.#run(() => {
|
|
400
435
|
this.#is_creating_fallback = false;
|
|
@@ -404,7 +439,7 @@ export class Boundary {
|
|
|
404
439
|
if (this.#pending_count > 0) {
|
|
405
440
|
this.#show_pending_snippet();
|
|
406
441
|
} else {
|
|
407
|
-
this
|
|
442
|
+
this.is_pending = false;
|
|
408
443
|
}
|
|
409
444
|
};
|
|
410
445
|
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
set_from_async_derived
|
|
26
26
|
} from './deriveds.js';
|
|
27
27
|
import { aborted } from './effects.js';
|
|
28
|
-
import { hydrate_next, hydrating, set_hydrate_node, skip_nodes } from '../dom/hydration.js';
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* @param {Array<Promise<void>>} blockers
|
|
@@ -211,51 +210,6 @@ export function unset_context() {
|
|
|
211
210
|
}
|
|
212
211
|
}
|
|
213
212
|
|
|
214
|
-
/**
|
|
215
|
-
* @param {TemplateNode} anchor
|
|
216
|
-
* @param {(target: TemplateNode) => Promise<void>} fn
|
|
217
|
-
*/
|
|
218
|
-
export async function async_body(anchor, fn) {
|
|
219
|
-
var boundary = get_boundary();
|
|
220
|
-
var batch = /** @type {Batch} */ (current_batch);
|
|
221
|
-
var blocking = !boundary.is_pending();
|
|
222
|
-
|
|
223
|
-
boundary.update_pending_count(1);
|
|
224
|
-
batch.increment(blocking);
|
|
225
|
-
|
|
226
|
-
var active = /** @type {Effect} */ (active_effect);
|
|
227
|
-
|
|
228
|
-
var was_hydrating = hydrating;
|
|
229
|
-
var next_hydrate_node = undefined;
|
|
230
|
-
|
|
231
|
-
if (was_hydrating) {
|
|
232
|
-
hydrate_next();
|
|
233
|
-
next_hydrate_node = skip_nodes(false);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
var promise = fn(anchor);
|
|
238
|
-
} finally {
|
|
239
|
-
if (next_hydrate_node) {
|
|
240
|
-
set_hydrate_node(next_hydrate_node);
|
|
241
|
-
hydrate_next();
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
await promise;
|
|
247
|
-
} catch (error) {
|
|
248
|
-
if (!aborted(active)) {
|
|
249
|
-
invoke_error_boundary(error, active);
|
|
250
|
-
}
|
|
251
|
-
} finally {
|
|
252
|
-
boundary.update_pending_count(-1);
|
|
253
|
-
batch.decrement(blocking);
|
|
254
|
-
|
|
255
|
-
unset_context();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
213
|
/**
|
|
260
214
|
* @param {Array<() => void | Promise<void>>} thunks
|
|
261
215
|
*/
|
|
@@ -264,7 +218,7 @@ export function run(thunks) {
|
|
|
264
218
|
|
|
265
219
|
var boundary = get_boundary();
|
|
266
220
|
var batch = /** @type {Batch} */ (current_batch);
|
|
267
|
-
var blocking =
|
|
221
|
+
var blocking = boundary.is_rendered();
|
|
268
222
|
|
|
269
223
|
boundary.update_pending_count(1);
|
|
270
224
|
batch.increment(blocking);
|
|
@@ -298,17 +252,13 @@ export function run(thunks) {
|
|
|
298
252
|
throw STALE_REACTION;
|
|
299
253
|
}
|
|
300
254
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return fn();
|
|
304
|
-
} finally {
|
|
305
|
-
// TODO do we need it here as well as below?
|
|
306
|
-
unset_context();
|
|
307
|
-
}
|
|
255
|
+
restore();
|
|
256
|
+
return fn();
|
|
308
257
|
})
|
|
309
258
|
.catch(handle_error)
|
|
310
259
|
.finally(() => {
|
|
311
260
|
unset_context();
|
|
261
|
+
current_batch?.deactivate();
|
|
312
262
|
});
|
|
313
263
|
|
|
314
264
|
promises.push(promise);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** @import { Fork } from 'svelte' */
|
|
2
2
|
/** @import { Derived, Effect, Reaction, Source, Value } from '#client' */
|
|
3
|
+
/** @import { Boundary } from '../dom/blocks/boundary' */
|
|
3
4
|
import {
|
|
4
5
|
BLOCK_EFFECT,
|
|
5
6
|
BRANCH_EFFECT,
|
|
@@ -17,7 +18,6 @@ import {
|
|
|
17
18
|
EAGER_EFFECT,
|
|
18
19
|
HEAD_EFFECT,
|
|
19
20
|
ERROR_VALUE,
|
|
20
|
-
WAS_MARKED,
|
|
21
21
|
MANAGED_EFFECT
|
|
22
22
|
} from '#client/constants';
|
|
23
23
|
import { async_mode_flag } from '../../flags/index.js';
|
|
@@ -25,10 +25,10 @@ import { deferred, define_property } from '../../shared/utils.js';
|
|
|
25
25
|
import {
|
|
26
26
|
active_effect,
|
|
27
27
|
get,
|
|
28
|
+
increment_write_version,
|
|
28
29
|
is_dirty,
|
|
29
30
|
is_updating_effect,
|
|
30
31
|
set_is_updating_effect,
|
|
31
|
-
set_signal_status,
|
|
32
32
|
update_effect
|
|
33
33
|
} from '../runtime.js';
|
|
34
34
|
import * as e from '../errors.js';
|
|
@@ -37,15 +37,9 @@ import { DEV } from 'esm-env';
|
|
|
37
37
|
import { invoke_error_boundary } from '../error-handling.js';
|
|
38
38
|
import { flush_eager_effects, old_values, set_eager_effects, source, update } from './sources.js';
|
|
39
39
|
import { eager_effect, unlink_effect } from './effects.js';
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* parent: EffectTarget | null;
|
|
44
|
-
* effect: Effect | null;
|
|
45
|
-
* effects: Effect[];
|
|
46
|
-
* render_effects: Effect[];
|
|
47
|
-
* }} EffectTarget
|
|
48
|
-
*/
|
|
40
|
+
import { defer_effect } from './utils.js';
|
|
41
|
+
import { UNINITIALIZED } from '../../../constants.js';
|
|
42
|
+
import { set_signal_status } from './status.js';
|
|
49
43
|
|
|
50
44
|
/** @type {Set<Batch>} */
|
|
51
45
|
const batches = new Set();
|
|
@@ -161,16 +155,14 @@ export class Batch {
|
|
|
161
155
|
|
|
162
156
|
this.apply();
|
|
163
157
|
|
|
164
|
-
/** @type {
|
|
165
|
-
var
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
render_effects: []
|
|
170
|
-
};
|
|
158
|
+
/** @type {Effect[]} */
|
|
159
|
+
var effects = [];
|
|
160
|
+
|
|
161
|
+
/** @type {Effect[]} */
|
|
162
|
+
var render_effects = [];
|
|
171
163
|
|
|
172
164
|
for (const root of root_effects) {
|
|
173
|
-
this.#traverse_effect_tree(root,
|
|
165
|
+
this.#traverse_effect_tree(root, effects, render_effects);
|
|
174
166
|
// Note: #traverse_effect_tree runs block effects eagerly, which can schedule effects,
|
|
175
167
|
// which means queued_root_effects now may be filled again.
|
|
176
168
|
|
|
@@ -183,16 +175,16 @@ export class Batch {
|
|
|
183
175
|
}
|
|
184
176
|
|
|
185
177
|
if (this.is_deferred()) {
|
|
186
|
-
this.#defer_effects(
|
|
187
|
-
this.#defer_effects(
|
|
178
|
+
this.#defer_effects(render_effects);
|
|
179
|
+
this.#defer_effects(effects);
|
|
188
180
|
} else {
|
|
189
181
|
// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
|
|
190
182
|
// newly updated sources, which could lead to infinite loops when effects run over and over again.
|
|
191
183
|
previous_batch = this;
|
|
192
184
|
current_batch = null;
|
|
193
185
|
|
|
194
|
-
flush_queued_effects(
|
|
195
|
-
flush_queued_effects(
|
|
186
|
+
flush_queued_effects(render_effects);
|
|
187
|
+
flush_queued_effects(effects);
|
|
196
188
|
|
|
197
189
|
previous_batch = null;
|
|
198
190
|
|
|
@@ -206,13 +198,17 @@ export class Batch {
|
|
|
206
198
|
* Traverse the effect tree, executing effects or stashing
|
|
207
199
|
* them for later execution as appropriate
|
|
208
200
|
* @param {Effect} root
|
|
209
|
-
* @param {
|
|
201
|
+
* @param {Effect[]} effects
|
|
202
|
+
* @param {Effect[]} render_effects
|
|
210
203
|
*/
|
|
211
|
-
#traverse_effect_tree(root,
|
|
204
|
+
#traverse_effect_tree(root, effects, render_effects) {
|
|
212
205
|
root.f ^= CLEAN;
|
|
213
206
|
|
|
214
207
|
var effect = root.first;
|
|
215
208
|
|
|
209
|
+
/** @type {Effect | null} */
|
|
210
|
+
var pending_boundary = null;
|
|
211
|
+
|
|
216
212
|
while (effect !== null) {
|
|
217
213
|
var flags = effect.f;
|
|
218
214
|
var is_branch = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0;
|
|
@@ -220,24 +216,32 @@ export class Batch {
|
|
|
220
216
|
|
|
221
217
|
var skip = is_skippable_branch || (flags & INERT) !== 0 || this.skipped_effects.has(effect);
|
|
222
218
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
219
|
+
// Inside a `<svelte:boundary>` with a pending snippet,
|
|
220
|
+
// all effects are deferred until the boundary resolves
|
|
221
|
+
// (except block/async effects, which run immediately)
|
|
222
|
+
if (
|
|
223
|
+
async_mode_flag &&
|
|
224
|
+
pending_boundary === null &&
|
|
225
|
+
(flags & BOUNDARY_EFFECT) !== 0 &&
|
|
226
|
+
effect.b?.is_pending
|
|
227
|
+
) {
|
|
228
|
+
pending_boundary = effect;
|
|
230
229
|
}
|
|
231
230
|
|
|
232
231
|
if (!skip && effect.fn !== null) {
|
|
233
232
|
if (is_branch) {
|
|
234
233
|
effect.f ^= CLEAN;
|
|
234
|
+
} else if (
|
|
235
|
+
pending_boundary !== null &&
|
|
236
|
+
(flags & (EFFECT | RENDER_EFFECT | MANAGED_EFFECT)) !== 0
|
|
237
|
+
) {
|
|
238
|
+
/** @type {Boundary} */ (pending_boundary.b).defer_effect(effect);
|
|
235
239
|
} else if ((flags & EFFECT) !== 0) {
|
|
236
|
-
|
|
240
|
+
effects.push(effect);
|
|
237
241
|
} else if (async_mode_flag && (flags & (RENDER_EFFECT | MANAGED_EFFECT)) !== 0) {
|
|
238
|
-
|
|
242
|
+
render_effects.push(effect);
|
|
239
243
|
} else if (is_dirty(effect)) {
|
|
240
|
-
if ((
|
|
244
|
+
if ((flags & BLOCK_EFFECT) !== 0) this.#dirty_effects.add(effect);
|
|
241
245
|
update_effect(effect);
|
|
242
246
|
}
|
|
243
247
|
|
|
@@ -253,14 +257,8 @@ export class Batch {
|
|
|
253
257
|
effect = effect.next;
|
|
254
258
|
|
|
255
259
|
while (effect === null && parent !== null) {
|
|
256
|
-
if (parent ===
|
|
257
|
-
|
|
258
|
-
// could we just attach the effects _to_ the pending boundary and schedule them
|
|
259
|
-
// once the boundary is ready?
|
|
260
|
-
this.#defer_effects(target.effects);
|
|
261
|
-
this.#defer_effects(target.render_effects);
|
|
262
|
-
|
|
263
|
-
target = /** @type {EffectTarget} */ (target.parent);
|
|
260
|
+
if (parent === pending_boundary) {
|
|
261
|
+
pending_boundary = null;
|
|
264
262
|
}
|
|
265
263
|
|
|
266
264
|
effect = parent.next;
|
|
@@ -273,36 +271,8 @@ export class Batch {
|
|
|
273
271
|
* @param {Effect[]} effects
|
|
274
272
|
*/
|
|
275
273
|
#defer_effects(effects) {
|
|
276
|
-
for (
|
|
277
|
-
|
|
278
|
-
this.#dirty_effects.add(e);
|
|
279
|
-
} else if ((e.f & MAYBE_DIRTY) !== 0) {
|
|
280
|
-
this.#maybe_dirty_effects.add(e);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Since we're not executing these effects now, we need to clear any WAS_MARKED flags
|
|
284
|
-
// so that other batches can correctly reach these effects during their own traversal
|
|
285
|
-
this.#clear_marked(e.deps);
|
|
286
|
-
|
|
287
|
-
// mark as clean so they get scheduled if they depend on pending async state
|
|
288
|
-
set_signal_status(e, CLEAN);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* @param {Value[] | null} deps
|
|
294
|
-
*/
|
|
295
|
-
#clear_marked(deps) {
|
|
296
|
-
if (deps === null) return;
|
|
297
|
-
|
|
298
|
-
for (const dep of deps) {
|
|
299
|
-
if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
dep.f ^= WAS_MARKED;
|
|
304
|
-
|
|
305
|
-
this.#clear_marked(/** @type {Derived} */ (dep).deps);
|
|
274
|
+
for (var i = 0; i < effects.length; i += 1) {
|
|
275
|
+
defer_effect(effects[i], this.#dirty_effects, this.#maybe_dirty_effects);
|
|
306
276
|
}
|
|
307
277
|
}
|
|
308
278
|
|
|
@@ -313,7 +283,7 @@ export class Batch {
|
|
|
313
283
|
* @param {any} value
|
|
314
284
|
*/
|
|
315
285
|
capture(source, value) {
|
|
316
|
-
if (!this.previous.has(source)) {
|
|
286
|
+
if (value !== UNINITIALIZED && !this.previous.has(source)) {
|
|
317
287
|
this.previous.set(source, value);
|
|
318
288
|
}
|
|
319
289
|
|
|
@@ -383,14 +353,6 @@ export class Batch {
|
|
|
383
353
|
var previous_batch_values = batch_values;
|
|
384
354
|
var is_earlier = true;
|
|
385
355
|
|
|
386
|
-
/** @type {EffectTarget} */
|
|
387
|
-
var dummy_target = {
|
|
388
|
-
parent: null,
|
|
389
|
-
effect: null,
|
|
390
|
-
effects: [],
|
|
391
|
-
render_effects: []
|
|
392
|
-
};
|
|
393
|
-
|
|
394
356
|
for (const batch of batches) {
|
|
395
357
|
if (batch === this) {
|
|
396
358
|
is_earlier = false;
|
|
@@ -439,10 +401,10 @@ export class Batch {
|
|
|
439
401
|
batch.apply();
|
|
440
402
|
|
|
441
403
|
for (const root of queued_root_effects) {
|
|
442
|
-
batch.#traverse_effect_tree(root,
|
|
404
|
+
batch.#traverse_effect_tree(root, [], []);
|
|
443
405
|
}
|
|
444
406
|
|
|
445
|
-
// TODO do we need to do anything with
|
|
407
|
+
// TODO do we need to do anything with the dummy effect arrays?
|
|
446
408
|
|
|
447
409
|
batch.deactivate();
|
|
448
410
|
}
|
|
@@ -959,13 +921,18 @@ export function fork(fn) {
|
|
|
959
921
|
|
|
960
922
|
flushSync(fn);
|
|
961
923
|
|
|
962
|
-
batch_values = null;
|
|
963
|
-
|
|
964
924
|
// revert state changes
|
|
965
925
|
for (var [source, value] of batch.previous) {
|
|
966
926
|
source.v = value;
|
|
967
927
|
}
|
|
968
928
|
|
|
929
|
+
// make writable deriveds dirty, so they recalculate correctly
|
|
930
|
+
for (source of batch.current.keys()) {
|
|
931
|
+
if ((source.f & DERIVED) !== 0) {
|
|
932
|
+
set_signal_status(source, DIRTY);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
969
936
|
return {
|
|
970
937
|
commit: async () => {
|
|
971
938
|
if (committed) {
|
|
@@ -981,9 +948,10 @@ export function fork(fn) {
|
|
|
981
948
|
|
|
982
949
|
batch.is_fork = false;
|
|
983
950
|
|
|
984
|
-
// apply changes
|
|
951
|
+
// apply changes and update write versions so deriveds see the change
|
|
985
952
|
for (var [source, value] of batch.current) {
|
|
986
953
|
source.v = value;
|
|
954
|
+
source.wv = increment_write_version();
|
|
987
955
|
}
|
|
988
956
|
|
|
989
957
|
// trigger any `$state.eager(...)` expressions with the new state.
|