Skip to content

Commit f3510c8

Browse files
committed
Merge branch 'main' into fix-memory-leak-3
2 parents 1ade64e + 63456f1 commit f3510c8

File tree

18 files changed

+148
-28
lines changed

18 files changed

+148
-28
lines changed

.changeset/itchy-eels-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: remove memory leak from bind:this

.changeset/lazy-knives-happen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: make snippet effects transparent for transitions

.changeset/pre.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@
179179
"large-clouds-carry",
180180
"large-turkeys-deny",
181181
"late-crabs-lay",
182+
"late-grapes-judge",
182183
"late-peaches-mate",
184+
"lazy-knives-happen",
183185
"lazy-masks-sit",
184186
"lazy-months-knock",
185187
"lazy-spiders-think",
@@ -252,6 +254,7 @@
252254
"polite-pumpkins-guess",
253255
"polite-ravens-study",
254256
"poor-eggs-enjoy",
257+
"poor-hats-design",
255258
"poor-seahorses-flash",
256259
"popular-ligers-perform",
257260
"popular-mangos-rest",
@@ -410,6 +413,7 @@
410413
"unlucky-steaks-warn",
411414
"unlucky-trees-lick",
412415
"violet-pigs-jam",
416+
"weak-drinks-speak",
413417
"weak-terms-destroy",
414418
"wet-games-fly",
415419
"wet-wombats-repeat",

packages/svelte/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# svelte
22

3+
## 5.0.0-next.106
4+
5+
### Patch Changes
6+
7+
- feat: use state proxy ancestry for ownership validation ([#11184](https://github.com/sveltejs/svelte/pull/11184))
8+
9+
- fix: make snippet effects transparent for transitions ([#11195](https://github.com/sveltejs/svelte/pull/11195))
10+
11+
- fix: return ast from `compile` (like Svelte 4 does) ([#11191](https://github.com/sveltejs/svelte/pull/11191))
12+
13+
- fix: ensure bind:this unmount behavior for members is conditional ([#11193](https://github.com/sveltejs/svelte/pull/11193))
14+
315
## 5.0.0-next.105
416

517
### Patch Changes

packages/svelte/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "svelte",
33
"description": "Cybernetically enhanced web apps",
44
"license": "MIT",
5-
"version": "5.0.0-next.105",
5+
"version": "5.0.0-next.106",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {

packages/svelte/src/compiler/phases/1-parse/state/tag.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ function special(parser) {
565565
type: 'VariableDeclaration',
566566
kind: 'const',
567567
declarations: [{ type: 'VariableDeclarator', id, init }],
568-
start: start + 1,
568+
start: start + 2, // start at const, not at @const
569569
end: parser.index - 1
570570
}
571571
});

packages/svelte/src/internal/client/constants.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ export const DIRTY = 1 << 9;
1010
export const MAYBE_DIRTY = 1 << 10;
1111
export const INERT = 1 << 11;
1212
export const DESTROYED = 1 << 12;
13-
export const IS_ELSEIF = 1 << 13;
14-
export const EFFECT_RAN = 1 << 14;
13+
export const EFFECT_RAN = 1 << 13;
14+
15+
/** 'Transparent' effects do not create a transition boundary */
16+
export const EFFECT_TRANSPARENT = 1 << 14;
1517

1618
export const STATE_SYMBOL = Symbol('$state');

packages/svelte/src/internal/client/dom/blocks/if.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IS_ELSEIF } from '../../constants.js';
1+
import { EFFECT_TRANSPARENT } from '../../constants.js';
22
import { hydrate_nodes, hydrating, set_hydrating } from '../hydration.js';
33
import { remove } from '../reconciler.js';
44
import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js';
@@ -79,6 +79,6 @@ export function if_block(
7979
});
8080

8181
if (elseif) {
82-
effect.f |= IS_ELSEIF;
82+
effect.f |= EFFECT_TRANSPARENT;
8383
}
8484
}

packages/svelte/src/internal/client/dom/blocks/snippet.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { EFFECT_TRANSPARENT } from '../../constants.js';
12
import { branch, render_effect } from '../../reactivity/effects.js';
23

34
/**
@@ -11,11 +12,13 @@ export function snippet(get_snippet, node, ...args) {
1112
/** @type {SnippetFn | null | undefined} */
1213
var snippet;
1314

14-
render_effect(() => {
15+
var effect = render_effect(() => {
1516
if (snippet === (snippet = get_snippet())) return;
1617

1718
if (snippet) {
1819
branch(() => /** @type {SnippetFn} */ (snippet)(node, ...args));
1920
}
2021
});
22+
23+
effect.f |= EFFECT_TRANSPARENT;
2124
}

packages/svelte/src/internal/client/dom/elements/bindings/this.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { STATE_SYMBOL } from '../../../constants.js';
22
import { effect, render_effect } from '../../../reactivity/effects.js';
33
import { untrack } from '../../../runtime.js';
4+
import { queue_task } from '../../task.js';
45

56
/**
67
* @param {any} bound_value
@@ -47,7 +48,8 @@ export function bind_this(element_or_component, update, get_value, get_parts) {
4748
});
4849

4950
return () => {
50-
effect(() => {
51+
// We cannot use effects in the teardown phase, we we use a microtask instead.
52+
queue_task(() => {
5153
if (parts && is_bound_this(get_value(...parts), element_or_component)) {
5254
update(null, ...parts);
5355
}

packages/svelte/src/internal/client/dom/elements/transitions.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { should_intro } from '../../render.js';
77
import { is_function } from '../../utils.js';
88
import { current_each_item } from '../blocks/each.js';
99
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
10-
import { EFFECT_RAN } from '../../constants.js';
10+
import { BLOCK_EFFECT, EFFECT_RAN } from '../../constants.js';
1111

1212
/**
1313
* @template T
@@ -212,6 +212,11 @@ export function transition(flags, element, get_fn, get_params) {
212212
if (is_intro && should_intro) {
213213
var parent = /** @type {import('#client').Effect} */ (e.parent);
214214

215+
// e.g snippets are implemented as render effects — keep going until we find the parent block
216+
while ((parent.f & BLOCK_EFFECT) === 0 && parent.parent) {
217+
parent = parent.parent;
218+
}
219+
215220
if (is_global || (parent.f & EFFECT_RAN) !== 0) {
216221
effect(() => {
217222
untrack(() => transition.in());
Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { run_all } from '../../shared/utils.js';
22

33
let is_task_queued = false;
4-
let is_raf_queued = false;
54

65
/** @type {Array<() => void>} */
76
let current_queued_tasks = [];
8-
/** @type {Array<() => void>} */
9-
let current_raf_tasks = [];
107

118
function process_task() {
129
is_task_queued = false;
@@ -15,11 +12,15 @@ function process_task() {
1512
run_all(tasks);
1613
}
1714

18-
function process_raf_task() {
19-
is_raf_queued = false;
20-
const tasks = current_raf_tasks.slice();
21-
current_raf_tasks = [];
22-
run_all(tasks);
15+
/**
16+
* @param {() => void} fn
17+
*/
18+
export function queue_task(fn) {
19+
if (!is_task_queued) {
20+
is_task_queued = true;
21+
queueMicrotask(process_task);
22+
}
23+
current_queued_tasks.push(fn);
2324
}
2425

2526
/**
@@ -29,7 +30,4 @@ export function flush_tasks() {
2930
if (is_task_queued) {
3031
process_task();
3132
}
32-
if (is_raf_queued) {
33-
process_raf_task();
34-
}
3533
}

packages/svelte/src/internal/client/reactivity/effects.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import {
77
destroy_effect_children,
88
execute_effect,
99
get,
10+
is_destroying_effect,
1011
is_flushing_effect,
1112
remove_reactions,
1213
schedule_effect,
14+
set_is_destroying_effect,
1315
set_is_flushing_effect,
1416
set_signal_status,
1517
untrack
@@ -24,7 +26,7 @@ import {
2426
EFFECT_RAN,
2527
BLOCK_EFFECT,
2628
ROOT_EFFECT,
27-
IS_ELSEIF
29+
EFFECT_TRANSPARENT
2830
} from '../constants.js';
2931
import { set } from './sources.js';
3032
import { remove } from '../dom/reconciler.js';
@@ -109,6 +111,12 @@ export function user_effect(fn) {
109111
(DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '')
110112
);
111113
}
114+
if (is_destroying_effect) {
115+
throw new Error(
116+
'ERR_SVELTE_EFFECT_IN_TEARDOWN' +
117+
(DEV ? ': The Svelte $effect rune can not be used in the teardown phase of an effect.' : '')
118+
);
119+
}
112120

113121
// Non-nested `$effect(...)` in a component should be deferred
114122
// until the component is mounted
@@ -140,6 +148,14 @@ export function user_pre_effect(fn) {
140148
: '')
141149
);
142150
}
151+
if (is_destroying_effect) {
152+
throw new Error(
153+
'ERR_SVELTE_EFFECT_IN_TEARDOWN' +
154+
(DEV
155+
? ': The Svelte $effect.pre rune can not be used in the teardown phase of an effect.'
156+
: '')
157+
);
158+
}
143159

144160
return render_effect(fn);
145161
}
@@ -228,6 +244,22 @@ export function branch(fn) {
228244
return create_effect(RENDER_EFFECT | BRANCH_EFFECT, fn, true);
229245
}
230246

247+
/**
248+
* @param {import("#client").Effect} effect
249+
*/
250+
export function execute_effect_teardown(effect) {
251+
var teardown = effect.teardown;
252+
if (teardown !== null) {
253+
const previously_destroying_effect = is_destroying_effect;
254+
set_is_destroying_effect(true);
255+
try {
256+
teardown.call(null);
257+
} finally {
258+
set_is_destroying_effect(previously_destroying_effect);
259+
}
260+
}
261+
}
262+
231263
/**
232264
* @param {import('#client').Effect} effect
233265
* @returns {void}
@@ -249,7 +281,7 @@ export function destroy_effect(effect) {
249281
}
250282
}
251283

252-
effect.teardown?.call(null);
284+
execute_effect_teardown(effect);
253285

254286
var parent = effect.parent;
255287

@@ -345,7 +377,7 @@ export function pause_children(effect, transitions, local) {
345377

346378
while (child !== null) {
347379
var sibling = child.next;
348-
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
380+
var transparent = (child.f & EFFECT_TRANSPARENT) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
349381
// TODO we don't need to call pause_children recursively with a linked list in place
350382
// it's slightly more involved though as we have to account for `transparent` changing
351383
// through the tree.
@@ -381,7 +413,7 @@ function resume_children(effect, local) {
381413

382414
while (child !== null) {
383415
var sibling = child.next;
384-
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
416+
var transparent = (child.f & EFFECT_TRANSPARENT) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
385417
// TODO we don't need to call resume_children recursively with a linked list in place
386418
// it's slightly more involved though as we have to account for `transparent` changing
387419
// through the tree.

packages/svelte/src/internal/client/runtime.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import {
88
object_prototype
99
} from './utils.js';
1010
import { snapshot } from './proxy.js';
11-
import { destroy_effect, effect, user_pre_effect } from './reactivity/effects.js';
11+
import {
12+
destroy_effect,
13+
effect,
14+
execute_effect_teardown,
15+
user_pre_effect
16+
} from './reactivity/effects.js';
1217
import {
1318
EFFECT,
1419
RENDER_EFFECT,
@@ -37,12 +42,18 @@ let current_scheduler_mode = FLUSH_MICROTASK;
3742
// Used for handling scheduling
3843
let is_micro_task_queued = false;
3944
export let is_flushing_effect = false;
45+
export let is_destroying_effect = false;
4046

4147
/** @param {boolean} value */
4248
export function set_is_flushing_effect(value) {
4349
is_flushing_effect = value;
4450
}
4551

52+
/** @param {boolean} value */
53+
export function set_is_destroying_effect(value) {
54+
is_destroying_effect = value;
55+
}
56+
4657
// Used for $inspect
4758
export let is_batching_effect = false;
4859
let is_inspecting_signal = false;
@@ -406,7 +417,7 @@ export function execute_effect(effect) {
406417
destroy_effect_children(effect);
407418
}
408419

409-
effect.teardown?.call(null);
420+
execute_effect_teardown(effect);
410421
var teardown = execute_reaction_fn(effect);
411422
effect.teardown = typeof teardown === 'function' ? teardown : null;
412423
} finally {
@@ -658,11 +669,11 @@ export function flush_sync(fn, flush_previous = true) {
658669

659670
var result = fn?.();
660671

672+
flush_tasks();
661673
if (current_queued_root_effects.length > 0 || root_effects.length > 0) {
662674
flush_sync();
663675
}
664676

665-
flush_tasks();
666677
flush_count = 0;
667678

668679
return result;

packages/svelte/src/version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
* https://svelte.dev/docs/svelte-compiler#svelte-version
77
* @type {string}
88
*/
9-
export const VERSION = '5.0.0-next.105';
9+
export const VERSION = '5.0.0-next.106';
1010
export const PUBLIC_VERSION = '5';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
/** @type {{ children: import('svelte').Snippet }} */
3+
let { children } = $props();
4+
5+
let visible = $state(false);
6+
</script>
7+
8+
<button onclick={() => visible = !visible}>toggle</button>
9+
10+
{#if visible}
11+
{@render children()}
12+
{/if}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { flushSync } from 'svelte';
2+
import { ok, test } from '../../test';
3+
4+
export default test({
5+
test({ assert, target, raf }) {
6+
const button = target.querySelector('button');
7+
ok(button);
8+
9+
flushSync(() => button.click());
10+
raf.tick(50);
11+
assert.htmlEqual(target.innerHTML, '<button>toggle</button><p style="opacity: 0.5;">hello</p>');
12+
13+
flushSync(() => button.click());
14+
raf.tick(75);
15+
assert.htmlEqual(
16+
target.innerHTML,
17+
'<button>toggle</button><p style="opacity: 0.25;">hello</p>'
18+
);
19+
}
20+
});

0 commit comments

Comments
 (0)