svelte 5.41.4 → 5.42.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/visitors/Attribute.js +5 -183
- package/src/compiler/phases/2-analyze/visitors/shared/function.js +0 -7
- package/src/compiler/phases/3-transform/client/utils.js +2 -124
- package/src/compiler/phases/3-transform/client/visitors/FunctionDeclaration.js +0 -11
- package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +0 -18
- package/src/compiler/phases/3-transform/client/visitors/shared/events.js +1 -29
- package/src/compiler/phases/3-transform/client/visitors/shared/function.js +0 -13
- package/src/compiler/phases/3-transform/utils.js +0 -19
- package/src/compiler/phases/nodes.js +1 -1
- package/src/compiler/utils/builders.js +3 -6
- package/src/index-client.js +1 -1
- package/src/index-server.js +4 -0
- package/src/internal/client/constants.js +2 -1
- package/src/internal/client/context.js +8 -1
- package/src/internal/client/dev/inspect.js +12 -3
- package/src/internal/client/dev/tracing.js +1 -2
- package/src/internal/client/dom/blocks/branches.js +18 -3
- package/src/internal/client/dom/blocks/each.js +1 -1
- package/src/internal/client/dom/elements/attributes.js +2 -2
- package/src/internal/client/dom/elements/events.js +2 -7
- package/src/internal/client/error-handling.js +2 -2
- package/src/internal/client/errors.js +48 -0
- package/src/internal/client/proxy.js +5 -5
- package/src/internal/client/reactivity/batch.js +160 -22
- package/src/internal/client/reactivity/deriveds.js +5 -5
- package/src/internal/client/reactivity/effects.js +4 -4
- package/src/internal/client/reactivity/sources.js +19 -19
- package/src/internal/client/runtime.js +2 -2
- package/src/utils.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +39 -5
- package/types/index.d.ts.map +5 -3
|
@@ -179,8 +179,7 @@ export function get_stack(label) {
|
|
|
179
179
|
});
|
|
180
180
|
|
|
181
181
|
define_property(error, 'name', {
|
|
182
|
-
|
|
183
|
-
value: `${label}Error`
|
|
182
|
+
value: label
|
|
184
183
|
});
|
|
185
184
|
|
|
186
185
|
return /** @type {Error & { stack: string }} */ (error);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/** @import { Effect, TemplateNode } from '#client' */
|
|
2
|
-
import { is_runes } from '../../context.js';
|
|
3
2
|
import { Batch, current_batch } from '../../reactivity/batch.js';
|
|
4
3
|
import {
|
|
5
4
|
branch,
|
|
@@ -8,7 +7,6 @@ import {
|
|
|
8
7
|
pause_effect,
|
|
9
8
|
resume_effect
|
|
10
9
|
} from '../../reactivity/effects.js';
|
|
11
|
-
import { set_should_intro, should_intro } from '../../render.js';
|
|
12
10
|
import { hydrate_node, hydrating } from '../hydration.js';
|
|
13
11
|
import { create_text, should_defer_append } from '../operations.js';
|
|
14
12
|
|
|
@@ -126,6 +124,22 @@ export class BranchManager {
|
|
|
126
124
|
}
|
|
127
125
|
};
|
|
128
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @param {Batch} batch
|
|
129
|
+
*/
|
|
130
|
+
#discard = (batch) => {
|
|
131
|
+
this.#batches.delete(batch);
|
|
132
|
+
|
|
133
|
+
const keys = Array.from(this.#batches.values());
|
|
134
|
+
|
|
135
|
+
for (const [k, branch] of this.#offscreen) {
|
|
136
|
+
if (!keys.includes(k)) {
|
|
137
|
+
destroy_effect(branch.effect);
|
|
138
|
+
this.#offscreen.delete(k);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
129
143
|
/**
|
|
130
144
|
*
|
|
131
145
|
* @param {any} key
|
|
@@ -173,7 +187,8 @@ export class BranchManager {
|
|
|
173
187
|
}
|
|
174
188
|
}
|
|
175
189
|
|
|
176
|
-
batch.
|
|
190
|
+
batch.oncommit(this.#commit);
|
|
191
|
+
batch.ondiscard(this.#discard);
|
|
177
192
|
} else {
|
|
178
193
|
if (hydrating) {
|
|
179
194
|
this.anchor = hydrate_node;
|
|
@@ -7,7 +7,7 @@ import { add_form_reset_listener, autofocus } from './misc.js';
|
|
|
7
7
|
import * as w from '../../warnings.js';
|
|
8
8
|
import { LOADING_ATTR_SYMBOL } from '#client/constants';
|
|
9
9
|
import { queue_micro_task } from '../task.js';
|
|
10
|
-
import { is_capture_event,
|
|
10
|
+
import { is_capture_event, can_delegate_event, normalize_attribute } from '../../../../utils.js';
|
|
11
11
|
import {
|
|
12
12
|
active_effect,
|
|
13
13
|
active_reaction,
|
|
@@ -378,7 +378,7 @@ function set_attributes(
|
|
|
378
378
|
const opts = {};
|
|
379
379
|
const event_handle_key = '$$' + key;
|
|
380
380
|
let event_name = key.slice(2);
|
|
381
|
-
var delegated =
|
|
381
|
+
var delegated = can_delegate_event(event_name);
|
|
382
382
|
|
|
383
383
|
if (is_capture_event(event_name)) {
|
|
384
384
|
event_name = event_name.slice(0, -7);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { teardown } from '../../reactivity/effects.js';
|
|
2
|
-
import { define_property
|
|
2
|
+
import { define_property } from '../../../shared/utils.js';
|
|
3
3
|
import { hydrating } from '../hydration.js';
|
|
4
4
|
import { queue_micro_task } from '../task.js';
|
|
5
5
|
import { FILENAME } from '../../../../constants.js';
|
|
@@ -258,12 +258,7 @@ export function handle_event_propagation(event) {
|
|
|
258
258
|
// -> the target could not have been disabled because it emits the event in the first place
|
|
259
259
|
event.target === current_target)
|
|
260
260
|
) {
|
|
261
|
-
|
|
262
|
-
var [fn, ...data] = delegated;
|
|
263
|
-
fn.apply(current_target, [event, ...data]);
|
|
264
|
-
} else {
|
|
265
|
-
delegated.call(current_target, event);
|
|
266
|
-
}
|
|
261
|
+
delegated.call(current_target, event);
|
|
267
262
|
}
|
|
268
263
|
} catch (error) {
|
|
269
264
|
if (throw_error) {
|
|
@@ -29,7 +29,7 @@ export function handle_error(error) {
|
|
|
29
29
|
// if the error occurred while creating this subtree, we let it
|
|
30
30
|
// bubble up until it hits a boundary that can handle it
|
|
31
31
|
if ((effect.f & BOUNDARY_EFFECT) === 0) {
|
|
32
|
-
if (!effect.parent && error instanceof Error) {
|
|
32
|
+
if (DEV && !effect.parent && error instanceof Error) {
|
|
33
33
|
apply_adjustments(error);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -61,7 +61,7 @@ export function invoke_error_boundary(error, effect) {
|
|
|
61
61
|
effect = effect.parent;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
if (error instanceof Error) {
|
|
64
|
+
if (DEV && error instanceof Error) {
|
|
65
65
|
apply_adjustments(error);
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -229,6 +229,22 @@ export function effect_update_depth_exceeded() {
|
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Cannot use `fork(...)` unless the `experimental.async` compiler option is `true`
|
|
234
|
+
* @returns {never}
|
|
235
|
+
*/
|
|
236
|
+
export function experimental_async_fork() {
|
|
237
|
+
if (DEV) {
|
|
238
|
+
const error = new Error(`experimental_async_fork\nCannot use \`fork(...)\` unless the \`experimental.async\` compiler option is \`true\`\nhttps://svelte.dev/e/experimental_async_fork`);
|
|
239
|
+
|
|
240
|
+
error.name = 'Svelte error';
|
|
241
|
+
|
|
242
|
+
throw error;
|
|
243
|
+
} else {
|
|
244
|
+
throw new Error(`https://svelte.dev/e/experimental_async_fork`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
232
248
|
/**
|
|
233
249
|
* Cannot use `flushSync` inside an effect
|
|
234
250
|
* @returns {never}
|
|
@@ -245,6 +261,38 @@ export function flush_sync_in_effect() {
|
|
|
245
261
|
}
|
|
246
262
|
}
|
|
247
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Cannot commit a fork that was already committed or discarded
|
|
266
|
+
* @returns {never}
|
|
267
|
+
*/
|
|
268
|
+
export function fork_discarded() {
|
|
269
|
+
if (DEV) {
|
|
270
|
+
const error = new Error(`fork_discarded\nCannot commit a fork that was already committed or discarded\nhttps://svelte.dev/e/fork_discarded`);
|
|
271
|
+
|
|
272
|
+
error.name = 'Svelte error';
|
|
273
|
+
|
|
274
|
+
throw error;
|
|
275
|
+
} else {
|
|
276
|
+
throw new Error(`https://svelte.dev/e/fork_discarded`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Cannot create a fork inside an effect or when state changes are pending
|
|
282
|
+
* @returns {never}
|
|
283
|
+
*/
|
|
284
|
+
export function fork_timing() {
|
|
285
|
+
if (DEV) {
|
|
286
|
+
const error = new Error(`fork_timing\nCannot create a fork inside an effect or when state changes are pending\nhttps://svelte.dev/e/fork_timing`);
|
|
287
|
+
|
|
288
|
+
error.name = 'Svelte error';
|
|
289
|
+
|
|
290
|
+
throw error;
|
|
291
|
+
} else {
|
|
292
|
+
throw new Error(`https://svelte.dev/e/fork_timing`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
248
296
|
/**
|
|
249
297
|
* `getAbortSignal()` can only be called inside an effect or derived
|
|
250
298
|
* @returns {never}
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
state as source,
|
|
20
20
|
set,
|
|
21
21
|
increment,
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
flush_eager_effects,
|
|
23
|
+
set_eager_effects_deferred
|
|
24
24
|
} from './reactivity/sources.js';
|
|
25
25
|
import { PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants';
|
|
26
26
|
import { UNINITIALIZED } from '../../constants.js';
|
|
@@ -53,7 +53,7 @@ export function proxy(value) {
|
|
|
53
53
|
var is_proxied_array = is_array(value);
|
|
54
54
|
var version = source(0);
|
|
55
55
|
|
|
56
|
-
var stack = DEV && tracing_mode_flag ? get_stack('
|
|
56
|
+
var stack = DEV && tracing_mode_flag ? get_stack('created at') : null;
|
|
57
57
|
var parent_version = update_version;
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -421,9 +421,9 @@ function inspectable_array(array) {
|
|
|
421
421
|
* @param {any[]} args
|
|
422
422
|
*/
|
|
423
423
|
return function (...args) {
|
|
424
|
-
|
|
424
|
+
set_eager_effects_deferred();
|
|
425
425
|
var result = value.apply(this, args);
|
|
426
|
-
|
|
426
|
+
flush_eager_effects();
|
|
427
427
|
return result;
|
|
428
428
|
};
|
|
429
429
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @import { Fork } from 'svelte' */
|
|
1
2
|
/** @import { Derived, Effect, Reaction, Source, Value } from '#client' */
|
|
2
3
|
import {
|
|
3
4
|
BLOCK_EFFECT,
|
|
@@ -12,25 +13,35 @@ import {
|
|
|
12
13
|
ROOT_EFFECT,
|
|
13
14
|
MAYBE_DIRTY,
|
|
14
15
|
DERIVED,
|
|
15
|
-
BOUNDARY_EFFECT
|
|
16
|
+
BOUNDARY_EFFECT,
|
|
17
|
+
EAGER_EFFECT
|
|
16
18
|
} from '#client/constants';
|
|
17
19
|
import { async_mode_flag } from '../../flags/index.js';
|
|
18
20
|
import { deferred, define_property } from '../../shared/utils.js';
|
|
19
21
|
import {
|
|
20
22
|
active_effect,
|
|
21
23
|
get,
|
|
24
|
+
increment_write_version,
|
|
22
25
|
is_dirty,
|
|
23
26
|
is_updating_effect,
|
|
24
27
|
set_is_updating_effect,
|
|
25
28
|
set_signal_status,
|
|
29
|
+
tick,
|
|
26
30
|
update_effect
|
|
27
31
|
} from '../runtime.js';
|
|
28
32
|
import * as e from '../errors.js';
|
|
29
33
|
import { flush_tasks, queue_micro_task } from '../dom/task.js';
|
|
30
34
|
import { DEV } from 'esm-env';
|
|
31
35
|
import { invoke_error_boundary } from '../error-handling.js';
|
|
32
|
-
import {
|
|
33
|
-
|
|
36
|
+
import {
|
|
37
|
+
flush_eager_effects,
|
|
38
|
+
eager_effects,
|
|
39
|
+
old_values,
|
|
40
|
+
set_eager_effects,
|
|
41
|
+
source,
|
|
42
|
+
update
|
|
43
|
+
} from './sources.js';
|
|
44
|
+
import { eager_effect, unlink_effect } from './effects.js';
|
|
34
45
|
|
|
35
46
|
/**
|
|
36
47
|
* @typedef {{
|
|
@@ -90,14 +101,20 @@ export class Batch {
|
|
|
90
101
|
* They keys of this map are identical to `this.#current`
|
|
91
102
|
* @type {Map<Source, any>}
|
|
92
103
|
*/
|
|
93
|
-
|
|
104
|
+
previous = new Map();
|
|
94
105
|
|
|
95
106
|
/**
|
|
96
107
|
* When the batch is committed (and the DOM is updated), we need to remove old branches
|
|
97
108
|
* and append new ones by calling the functions added inside (if/each/key/etc) blocks
|
|
98
109
|
* @type {Set<() => void>}
|
|
99
110
|
*/
|
|
100
|
-
#
|
|
111
|
+
#commit_callbacks = new Set();
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* If a fork is discarded, we need to destroy any effects that are no longer needed
|
|
115
|
+
* @type {Set<(batch: Batch) => void>}
|
|
116
|
+
*/
|
|
117
|
+
#discard_callbacks = new Set();
|
|
101
118
|
|
|
102
119
|
/**
|
|
103
120
|
* The number of async effects that are currently in flight
|
|
@@ -135,6 +152,8 @@ export class Batch {
|
|
|
135
152
|
*/
|
|
136
153
|
skipped_effects = new Set();
|
|
137
154
|
|
|
155
|
+
is_fork = false;
|
|
156
|
+
|
|
138
157
|
/**
|
|
139
158
|
*
|
|
140
159
|
* @param {Effect[]} root_effects
|
|
@@ -159,15 +178,15 @@ export class Batch {
|
|
|
159
178
|
this.#traverse_effect_tree(root, target);
|
|
160
179
|
}
|
|
161
180
|
|
|
162
|
-
this
|
|
181
|
+
if (!this.is_fork) {
|
|
182
|
+
this.#resolve();
|
|
183
|
+
}
|
|
163
184
|
|
|
164
|
-
if (this.#blocking_pending > 0) {
|
|
185
|
+
if (this.#blocking_pending > 0 || this.is_fork) {
|
|
165
186
|
this.#defer_effects(target.effects);
|
|
166
187
|
this.#defer_effects(target.render_effects);
|
|
167
188
|
this.#defer_effects(target.block_effects);
|
|
168
189
|
} else {
|
|
169
|
-
// TODO append/detach blocks here, not in #commit
|
|
170
|
-
|
|
171
190
|
// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
|
|
172
191
|
// newly updated sources, which could lead to infinite loops when effects run over and over again.
|
|
173
192
|
previous_batch = this;
|
|
@@ -271,8 +290,8 @@ export class Batch {
|
|
|
271
290
|
* @param {any} value
|
|
272
291
|
*/
|
|
273
292
|
capture(source, value) {
|
|
274
|
-
if (!this
|
|
275
|
-
this
|
|
293
|
+
if (!this.previous.has(source)) {
|
|
294
|
+
this.previous.set(source, value);
|
|
276
295
|
}
|
|
277
296
|
|
|
278
297
|
this.current.set(source, source.v);
|
|
@@ -289,16 +308,17 @@ export class Batch {
|
|
|
289
308
|
}
|
|
290
309
|
|
|
291
310
|
flush() {
|
|
311
|
+
this.activate();
|
|
312
|
+
|
|
292
313
|
if (queued_root_effects.length > 0) {
|
|
293
|
-
this.activate();
|
|
294
314
|
flush_effects();
|
|
295
315
|
|
|
296
316
|
if (current_batch !== null && current_batch !== this) {
|
|
297
317
|
// this can happen if a new batch was created during `flush_effects()`
|
|
298
318
|
return;
|
|
299
319
|
}
|
|
300
|
-
} else {
|
|
301
|
-
this
|
|
320
|
+
} else if (this.#pending === 0) {
|
|
321
|
+
this.process([]); // TODO this feels awkward
|
|
302
322
|
}
|
|
303
323
|
|
|
304
324
|
this.deactivate();
|
|
@@ -314,11 +334,16 @@ export class Batch {
|
|
|
314
334
|
}
|
|
315
335
|
}
|
|
316
336
|
|
|
337
|
+
discard() {
|
|
338
|
+
for (const fn of this.#discard_callbacks) fn(this);
|
|
339
|
+
this.#discard_callbacks.clear();
|
|
340
|
+
}
|
|
341
|
+
|
|
317
342
|
#resolve() {
|
|
318
343
|
if (this.#blocking_pending === 0) {
|
|
319
344
|
// append/remove branches
|
|
320
|
-
for (const fn of this.#
|
|
321
|
-
this.#
|
|
345
|
+
for (const fn of this.#commit_callbacks) fn();
|
|
346
|
+
this.#commit_callbacks.clear();
|
|
322
347
|
}
|
|
323
348
|
|
|
324
349
|
if (this.#pending === 0) {
|
|
@@ -332,7 +357,7 @@ export class Batch {
|
|
|
332
357
|
// committed state, unless the batch in question has a more
|
|
333
358
|
// recent value for a given source
|
|
334
359
|
if (batches.size > 1) {
|
|
335
|
-
this
|
|
360
|
+
this.previous.clear();
|
|
336
361
|
|
|
337
362
|
var previous_batch_values = batch_values;
|
|
338
363
|
var is_earlier = true;
|
|
@@ -428,6 +453,10 @@ export class Batch {
|
|
|
428
453
|
this.#pending -= 1;
|
|
429
454
|
if (blocking) this.#blocking_pending -= 1;
|
|
430
455
|
|
|
456
|
+
this.revive();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
revive() {
|
|
431
460
|
for (const e of this.#dirty_effects) {
|
|
432
461
|
set_signal_status(e, DIRTY);
|
|
433
462
|
schedule_effect(e);
|
|
@@ -445,8 +474,13 @@ export class Batch {
|
|
|
445
474
|
}
|
|
446
475
|
|
|
447
476
|
/** @param {() => void} fn */
|
|
448
|
-
|
|
449
|
-
this.#
|
|
477
|
+
oncommit(fn) {
|
|
478
|
+
this.#commit_callbacks.add(fn);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** @param {(batch: Batch) => void} fn */
|
|
482
|
+
ondiscard(fn) {
|
|
483
|
+
this.#discard_callbacks.add(fn);
|
|
450
484
|
}
|
|
451
485
|
|
|
452
486
|
settled() {
|
|
@@ -489,7 +523,7 @@ export class Batch {
|
|
|
489
523
|
for (const batch of batches) {
|
|
490
524
|
if (batch === this) continue;
|
|
491
525
|
|
|
492
|
-
for (const [source, previous] of batch
|
|
526
|
+
for (const [source, previous] of batch.previous) {
|
|
493
527
|
if (!batch_values.has(source)) {
|
|
494
528
|
batch_values.set(source, previous);
|
|
495
529
|
}
|
|
@@ -717,6 +751,28 @@ function mark_effects(value, sources, marked, checked) {
|
|
|
717
751
|
}
|
|
718
752
|
}
|
|
719
753
|
|
|
754
|
+
/**
|
|
755
|
+
* When committing a fork, we need to trigger eager effects so that
|
|
756
|
+
* any `$state.eager(...)` expressions update immediately. This
|
|
757
|
+
* function allows us to discover them
|
|
758
|
+
* @param {Value} value
|
|
759
|
+
* @param {Set<Effect>} effects
|
|
760
|
+
*/
|
|
761
|
+
function mark_eager_effects(value, effects) {
|
|
762
|
+
if (value.reactions === null) return;
|
|
763
|
+
|
|
764
|
+
for (const reaction of value.reactions) {
|
|
765
|
+
const flags = reaction.f;
|
|
766
|
+
|
|
767
|
+
if ((flags & DERIVED) !== 0) {
|
|
768
|
+
mark_eager_effects(/** @type {Derived} */ (reaction), effects);
|
|
769
|
+
} else if ((flags & EAGER_EFFECT) !== 0) {
|
|
770
|
+
set_signal_status(reaction, DIRTY);
|
|
771
|
+
effects.add(/** @type {Effect} */ (reaction));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
720
776
|
/**
|
|
721
777
|
* @param {Reaction} reaction
|
|
722
778
|
* @param {Source[]} sources
|
|
@@ -798,9 +854,9 @@ export function eager(fn) {
|
|
|
798
854
|
|
|
799
855
|
get(version);
|
|
800
856
|
|
|
801
|
-
|
|
857
|
+
eager_effect(() => {
|
|
802
858
|
if (initial) {
|
|
803
|
-
// the first time this runs, we create an
|
|
859
|
+
// the first time this runs, we create an eager effect
|
|
804
860
|
// that will run eagerly whenever the expression changes
|
|
805
861
|
var previous_batch_values = batch_values;
|
|
806
862
|
|
|
@@ -829,6 +885,88 @@ export function eager(fn) {
|
|
|
829
885
|
return value;
|
|
830
886
|
}
|
|
831
887
|
|
|
888
|
+
/**
|
|
889
|
+
* Creates a 'fork', in which state changes are evaluated but not applied to the DOM.
|
|
890
|
+
* This is useful for speculatively loading data (for example) when you suspect that
|
|
891
|
+
* the user is about to take some action.
|
|
892
|
+
*
|
|
893
|
+
* Frameworks like SvelteKit can use this to preload data when the user touches or
|
|
894
|
+
* hovers over a link, making any subsequent navigation feel instantaneous.
|
|
895
|
+
*
|
|
896
|
+
* The `fn` parameter is a synchronous function that modifies some state. The
|
|
897
|
+
* state changes will be reverted after the fork is initialised, then reapplied
|
|
898
|
+
* if and when the fork is eventually committed.
|
|
899
|
+
*
|
|
900
|
+
* When it becomes clear that a fork will _not_ be committed (e.g. because the
|
|
901
|
+
* user navigated elsewhere), it must be discarded to avoid leaking memory.
|
|
902
|
+
*
|
|
903
|
+
* @param {() => void} fn
|
|
904
|
+
* @returns {Fork}
|
|
905
|
+
* @since 5.42
|
|
906
|
+
*/
|
|
907
|
+
export function fork(fn) {
|
|
908
|
+
if (!async_mode_flag) {
|
|
909
|
+
e.experimental_async_fork();
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (current_batch !== null) {
|
|
913
|
+
e.fork_timing();
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const batch = Batch.ensure();
|
|
917
|
+
batch.is_fork = true;
|
|
918
|
+
|
|
919
|
+
const settled = batch.settled();
|
|
920
|
+
|
|
921
|
+
flushSync(fn);
|
|
922
|
+
|
|
923
|
+
// revert state changes
|
|
924
|
+
for (const [source, value] of batch.previous) {
|
|
925
|
+
source.v = value;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
return {
|
|
929
|
+
commit: async () => {
|
|
930
|
+
if (!batches.has(batch)) {
|
|
931
|
+
e.fork_discarded();
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
batch.is_fork = false;
|
|
935
|
+
|
|
936
|
+
// apply changes
|
|
937
|
+
for (const [source, value] of batch.current) {
|
|
938
|
+
source.v = value;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// trigger any `$state.eager(...)` expressions with the new state.
|
|
942
|
+
// eager effects don't get scheduled like other effects, so we
|
|
943
|
+
// can't just encounter them during traversal, we need to
|
|
944
|
+
// proactively flush them
|
|
945
|
+
// TODO maybe there's a better implementation?
|
|
946
|
+
flushSync(() => {
|
|
947
|
+
/** @type {Set<Effect>} */
|
|
948
|
+
const eager_effects = new Set();
|
|
949
|
+
|
|
950
|
+
for (const source of batch.current.keys()) {
|
|
951
|
+
mark_eager_effects(source, eager_effects);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
set_eager_effects(eager_effects);
|
|
955
|
+
flush_eager_effects();
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
batch.revive();
|
|
959
|
+
await settled;
|
|
960
|
+
},
|
|
961
|
+
discard: () => {
|
|
962
|
+
if (batches.has(batch)) {
|
|
963
|
+
batches.delete(batch);
|
|
964
|
+
batch.discard();
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
832
970
|
/**
|
|
833
971
|
* Forcibly remove all current batches, to prevent cross-talk between tests
|
|
834
972
|
*/
|
|
@@ -28,7 +28,7 @@ import { equals, safe_equals } from './equality.js';
|
|
|
28
28
|
import * as e from '../errors.js';
|
|
29
29
|
import * as w from '../warnings.js';
|
|
30
30
|
import { async_effect, destroy_effect, teardown } from './effects.js';
|
|
31
|
-
import {
|
|
31
|
+
import { eager_effects, internal_set, set_eager_effects, source } from './sources.js';
|
|
32
32
|
import { get_stack } from '../dev/tracing.js';
|
|
33
33
|
import { async_mode_flag, tracing_mode_flag } from '../../flags/index.js';
|
|
34
34
|
import { Boundary } from '../dom/blocks/boundary.js';
|
|
@@ -86,7 +86,7 @@ export function derived(fn) {
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
if (DEV && tracing_mode_flag) {
|
|
89
|
-
signal.created = get_stack('
|
|
89
|
+
signal.created = get_stack('created at');
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
return signal;
|
|
@@ -318,8 +318,8 @@ export function execute_derived(derived) {
|
|
|
318
318
|
set_active_effect(get_derived_parent_effect(derived));
|
|
319
319
|
|
|
320
320
|
if (DEV) {
|
|
321
|
-
let
|
|
322
|
-
|
|
321
|
+
let prev_eager_effects = eager_effects;
|
|
322
|
+
set_eager_effects(new Set());
|
|
323
323
|
try {
|
|
324
324
|
if (stack.includes(derived)) {
|
|
325
325
|
e.derived_references_self();
|
|
@@ -332,7 +332,7 @@ export function execute_derived(derived) {
|
|
|
332
332
|
value = update_reaction(derived);
|
|
333
333
|
} finally {
|
|
334
334
|
set_active_effect(prev_active_effect);
|
|
335
|
-
|
|
335
|
+
set_eager_effects(prev_eager_effects);
|
|
336
336
|
stack.pop();
|
|
337
337
|
}
|
|
338
338
|
} else {
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
DERIVED,
|
|
28
28
|
UNOWNED,
|
|
29
29
|
CLEAN,
|
|
30
|
-
|
|
30
|
+
EAGER_EFFECT,
|
|
31
31
|
HEAD_EFFECT,
|
|
32
32
|
MAYBE_DIRTY,
|
|
33
33
|
EFFECT_PRESERVED,
|
|
@@ -88,7 +88,7 @@ function create_effect(type, fn, sync, push = true) {
|
|
|
88
88
|
|
|
89
89
|
if (DEV) {
|
|
90
90
|
// Ensure the parent is never an inspect effect
|
|
91
|
-
while (parent !== null && (parent.f &
|
|
91
|
+
while (parent !== null && (parent.f & EAGER_EFFECT) !== 0) {
|
|
92
92
|
parent = parent.parent;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -245,8 +245,8 @@ export function user_pre_effect(fn) {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
/** @param {() => void | (() => void)} fn */
|
|
248
|
-
export function
|
|
249
|
-
return create_effect(
|
|
248
|
+
export function eager_effect(fn) {
|
|
249
|
+
return create_effect(EAGER_EFFECT, fn, true);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
/**
|