Merge branch 'main' into fix-memory-leak-3

pull/11197/head
Dominic Gannaway 2 years ago
commit f3510c8748

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: remove memory leak from bind:this

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: make snippet effects transparent for transitions

@ -179,7 +179,9 @@
"large-clouds-carry",
"large-turkeys-deny",
"late-crabs-lay",
"late-grapes-judge",
"late-peaches-mate",
"lazy-knives-happen",
"lazy-masks-sit",
"lazy-months-knock",
"lazy-spiders-think",
@ -252,6 +254,7 @@
"polite-pumpkins-guess",
"polite-ravens-study",
"poor-eggs-enjoy",
"poor-hats-design",
"poor-seahorses-flash",
"popular-ligers-perform",
"popular-mangos-rest",
@ -410,6 +413,7 @@
"unlucky-steaks-warn",
"unlucky-trees-lick",
"violet-pigs-jam",
"weak-drinks-speak",
"weak-terms-destroy",
"wet-games-fly",
"wet-wombats-repeat",

@ -1,5 +1,17 @@
# svelte
## 5.0.0-next.106
### Patch Changes
- feat: use state proxy ancestry for ownership validation ([#11184](https://github.com/sveltejs/svelte/pull/11184))
- fix: make snippet effects transparent for transitions ([#11195](https://github.com/sveltejs/svelte/pull/11195))
- fix: return ast from `compile` (like Svelte 4 does) ([#11191](https://github.com/sveltejs/svelte/pull/11191))
- fix: ensure bind:this unmount behavior for members is conditional ([#11193](https://github.com/sveltejs/svelte/pull/11193))
## 5.0.0-next.105
### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.0.0-next.105",
"version": "5.0.0-next.106",
"type": "module",
"types": "./types/index.d.ts",
"engines": {

@ -565,7 +565,7 @@ function special(parser) {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{ type: 'VariableDeclarator', id, init }],
start: start + 1,
start: start + 2, // start at const, not at @const
end: parser.index - 1
}
});

@ -10,7 +10,9 @@ export const DIRTY = 1 << 9;
export const MAYBE_DIRTY = 1 << 10;
export const INERT = 1 << 11;
export const DESTROYED = 1 << 12;
export const IS_ELSEIF = 1 << 13;
export const EFFECT_RAN = 1 << 14;
export const EFFECT_RAN = 1 << 13;
/** 'Transparent' effects do not create a transition boundary */
export const EFFECT_TRANSPARENT = 1 << 14;
export const STATE_SYMBOL = Symbol('$state');

@ -1,4 +1,4 @@
import { IS_ELSEIF } from '../../constants.js';
import { EFFECT_TRANSPARENT } from '../../constants.js';
import { hydrate_nodes, hydrating, set_hydrating } from '../hydration.js';
import { remove } from '../reconciler.js';
import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js';
@ -79,6 +79,6 @@ export function if_block(
});
if (elseif) {
effect.f |= IS_ELSEIF;
effect.f |= EFFECT_TRANSPARENT;
}
}

@ -1,3 +1,4 @@
import { EFFECT_TRANSPARENT } from '../../constants.js';
import { branch, render_effect } from '../../reactivity/effects.js';
/**
@ -11,11 +12,13 @@ export function snippet(get_snippet, node, ...args) {
/** @type {SnippetFn | null | undefined} */
var snippet;
render_effect(() => {
var effect = render_effect(() => {
if (snippet === (snippet = get_snippet())) return;
if (snippet) {
branch(() => /** @type {SnippetFn} */ (snippet)(node, ...args));
}
});
effect.f |= EFFECT_TRANSPARENT;
}

@ -1,6 +1,7 @@
import { STATE_SYMBOL } from '../../../constants.js';
import { effect, render_effect } from '../../../reactivity/effects.js';
import { untrack } from '../../../runtime.js';
import { queue_task } from '../../task.js';
/**
* @param {any} bound_value
@ -47,7 +48,8 @@ export function bind_this(element_or_component, update, get_value, get_parts) {
});
return () => {
effect(() => {
// We cannot use effects in the teardown phase, we we use a microtask instead.
queue_task(() => {
if (parts && is_bound_this(get_value(...parts), element_or_component)) {
update(null, ...parts);
}

@ -7,7 +7,7 @@ import { should_intro } from '../../render.js';
import { is_function } from '../../utils.js';
import { current_each_item } from '../blocks/each.js';
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
import { EFFECT_RAN } from '../../constants.js';
import { BLOCK_EFFECT, EFFECT_RAN } from '../../constants.js';
/**
* @template T
@ -212,6 +212,11 @@ export function transition(flags, element, get_fn, get_params) {
if (is_intro && should_intro) {
var parent = /** @type {import('#client').Effect} */ (e.parent);
// e.g snippets are implemented as render effects — keep going until we find the parent block
while ((parent.f & BLOCK_EFFECT) === 0 && parent.parent) {
parent = parent.parent;
}
if (is_global || (parent.f & EFFECT_RAN) !== 0) {
effect(() => {
untrack(() => transition.in());

@ -1,12 +1,9 @@
import { run_all } from '../../shared/utils.js';
let is_task_queued = false;
let is_raf_queued = false;
/** @type {Array<() => void>} */
let current_queued_tasks = [];
/** @type {Array<() => void>} */
let current_raf_tasks = [];
function process_task() {
is_task_queued = false;
@ -15,11 +12,15 @@ function process_task() {
run_all(tasks);
}
function process_raf_task() {
is_raf_queued = false;
const tasks = current_raf_tasks.slice();
current_raf_tasks = [];
run_all(tasks);
/**
* @param {() => void} fn
*/
export function queue_task(fn) {
if (!is_task_queued) {
is_task_queued = true;
queueMicrotask(process_task);
}
current_queued_tasks.push(fn);
}
/**
@ -29,7 +30,4 @@ export function flush_tasks() {
if (is_task_queued) {
process_task();
}
if (is_raf_queued) {
process_raf_task();
}
}

@ -7,9 +7,11 @@ import {
destroy_effect_children,
execute_effect,
get,
is_destroying_effect,
is_flushing_effect,
remove_reactions,
schedule_effect,
set_is_destroying_effect,
set_is_flushing_effect,
set_signal_status,
untrack
@ -24,7 +26,7 @@ import {
EFFECT_RAN,
BLOCK_EFFECT,
ROOT_EFFECT,
IS_ELSEIF
EFFECT_TRANSPARENT
} from '../constants.js';
import { set } from './sources.js';
import { remove } from '../dom/reconciler.js';
@ -109,6 +111,12 @@ export function user_effect(fn) {
(DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '')
);
}
if (is_destroying_effect) {
throw new Error(
'ERR_SVELTE_EFFECT_IN_TEARDOWN' +
(DEV ? ': The Svelte $effect rune can not be used in the teardown phase of an effect.' : '')
);
}
// Non-nested `$effect(...)` in a component should be deferred
// until the component is mounted
@ -140,6 +148,14 @@ export function user_pre_effect(fn) {
: '')
);
}
if (is_destroying_effect) {
throw new Error(
'ERR_SVELTE_EFFECT_IN_TEARDOWN' +
(DEV
? ': The Svelte $effect.pre rune can not be used in the teardown phase of an effect.'
: '')
);
}
return render_effect(fn);
}
@ -228,6 +244,22 @@ export function branch(fn) {
return create_effect(RENDER_EFFECT | BRANCH_EFFECT, fn, true);
}
/**
* @param {import("#client").Effect} effect
*/
export function execute_effect_teardown(effect) {
var teardown = effect.teardown;
if (teardown !== null) {
const previously_destroying_effect = is_destroying_effect;
set_is_destroying_effect(true);
try {
teardown.call(null);
} finally {
set_is_destroying_effect(previously_destroying_effect);
}
}
}
/**
* @param {import('#client').Effect} effect
* @returns {void}
@ -249,7 +281,7 @@ export function destroy_effect(effect) {
}
}
effect.teardown?.call(null);
execute_effect_teardown(effect);
var parent = effect.parent;
@ -345,7 +377,7 @@ export function pause_children(effect, transitions, local) {
while (child !== null) {
var sibling = child.next;
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
var transparent = (child.f & EFFECT_TRANSPARENT) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
// TODO we don't need to call pause_children recursively with a linked list in place
// it's slightly more involved though as we have to account for `transparent` changing
// through the tree.
@ -381,7 +413,7 @@ function resume_children(effect, local) {
while (child !== null) {
var sibling = child.next;
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
var transparent = (child.f & EFFECT_TRANSPARENT) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
// TODO we don't need to call resume_children recursively with a linked list in place
// it's slightly more involved though as we have to account for `transparent` changing
// through the tree.

@ -8,7 +8,12 @@ import {
object_prototype
} from './utils.js';
import { snapshot } from './proxy.js';
import { destroy_effect, effect, user_pre_effect } from './reactivity/effects.js';
import {
destroy_effect,
effect,
execute_effect_teardown,
user_pre_effect
} from './reactivity/effects.js';
import {
EFFECT,
RENDER_EFFECT,
@ -37,12 +42,18 @@ let current_scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling
let is_micro_task_queued = false;
export let is_flushing_effect = false;
export let is_destroying_effect = false;
/** @param {boolean} value */
export function set_is_flushing_effect(value) {
is_flushing_effect = value;
}
/** @param {boolean} value */
export function set_is_destroying_effect(value) {
is_destroying_effect = value;
}
// Used for $inspect
export let is_batching_effect = false;
let is_inspecting_signal = false;
@ -406,7 +417,7 @@ export function execute_effect(effect) {
destroy_effect_children(effect);
}
effect.teardown?.call(null);
execute_effect_teardown(effect);
var teardown = execute_reaction_fn(effect);
effect.teardown = typeof teardown === 'function' ? teardown : null;
} finally {
@ -658,11 +669,11 @@ export function flush_sync(fn, flush_previous = true) {
var result = fn?.();
flush_tasks();
if (current_queued_root_effects.length > 0 || root_effects.length > 0) {
flush_sync();
}
flush_tasks();
flush_count = 0;
return result;

@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string}
*/
export const VERSION = '5.0.0-next.105';
export const VERSION = '5.0.0-next.106';
export const PUBLIC_VERSION = '5';

@ -0,0 +1,12 @@
<script>
/** @type {{ children: import('svelte').Snippet }} */
let { children } = $props();
let visible = $state(false);
</script>
<button onclick={() => visible = !visible}>toggle</button>
{#if visible}
{@render children()}
{/if}

@ -0,0 +1,20 @@
import { flushSync } from 'svelte';
import { ok, test } from '../../test';
export default test({
test({ assert, target, raf }) {
const button = target.querySelector('button');
ok(button);
flushSync(() => button.click());
raf.tick(50);
assert.htmlEqual(target.innerHTML, '<button>toggle</button><p style="opacity: 0.5;">hello</p>');
flushSync(() => button.click());
raf.tick(75);
assert.htmlEqual(
target.innerHTML,
'<button>toggle</button><p style="opacity: 0.25;">hello</p>'
);
}
});

@ -0,0 +1,9 @@
<script>
import { fade } from 'svelte/transition';
import { linear } from 'svelte/easing';
import Container from './Container.svelte';
</script>
<Container>
<p style="opacity: 1" transition:fade={{ duration: 100, easing: linear }}>hello</p>
</Container>
Loading…
Cancel
Save