fix: ensure internal effects for elements are sequenced correctly

pull/14033/head
Dominic Gannaway 2 years ago
parent 08bc37a374
commit 21c031acdb

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure internal effects for elements are sequenced correctly

@ -4,21 +4,22 @@ export const RENDER_EFFECT = 1 << 3;
export const BLOCK_EFFECT = 1 << 4;
export const BRANCH_EFFECT = 1 << 5;
export const ROOT_EFFECT = 1 << 6;
export const UNOWNED = 1 << 7;
export const DISCONNECTED = 1 << 8;
export const CLEAN = 1 << 9;
export const DIRTY = 1 << 10;
export const MAYBE_DIRTY = 1 << 11;
export const INERT = 1 << 12;
export const DESTROYED = 1 << 13;
export const EFFECT_RAN = 1 << 14;
export const ELEMENT_EFFECT = 1 << 7;
export const UNOWNED = 1 << 8;
export const DISCONNECTED = 1 << 9;
export const CLEAN = 1 << 10;
export const DIRTY = 1 << 11;
export const MAYBE_DIRTY = 1 << 12;
export const INERT = 1 << 13;
export const DESTROYED = 1 << 14;
export const EFFECT_RAN = 1 << 15;
/** 'Transparent' effects do not create a transition boundary */
export const EFFECT_TRANSPARENT = 1 << 15;
export const EFFECT_TRANSPARENT = 1 << 16;
/** Svelte 4 legacy mode props need to be handled with deriveds and be recognized elsewhere, hence the dedicated flag */
export const LEGACY_DERIVED_PROP = 1 << 16;
export const INSPECT_EFFECT = 1 << 17;
export const HEAD_EFFECT = 1 << 18;
export const EFFECT_HAS_DERIVED = 1 << 19;
export const LEGACY_DERIVED_PROP = 1 << 17;
export const INSPECT_EFFECT = 1 << 18;
export const HEAD_EFFECT = 1 << 19;
export const EFFECT_HAS_DERIVED = 1 << 20;
export const STATE_SYMBOL = Symbol('$state');
export const STATE_SYMBOL_METADATA = Symbol('$state metadata');

@ -1,5 +1,5 @@
/** @import { ActionPayload } from '#client' */
import { effect, render_effect } from '../../reactivity/effects.js';
import { element_effect, render_effect } from '../../reactivity/effects.js';
import { safe_not_equal } from '../../reactivity/equality.js';
import { deep_read_state, untrack } from '../../runtime.js';
@ -11,7 +11,7 @@ import { deep_read_state, untrack } from '../../runtime.js';
* @returns {void}
*/
export function action(dom, action, get_value) {
effect(() => {
element_effect(() => {
var payload = untrack(() => action(dom, get_value?.()) || {});
if (get_value && payload?.update) {

@ -1,4 +1,3 @@
import { hydrating } from '../../hydration.js';
import { render_effect, effect, teardown } from '../../../reactivity/effects.js';
import { listen } from './shared.js';

@ -1,4 +1,4 @@
import { effect } from '../../../reactivity/effects.js';
import { element_effect } from '../../../reactivity/effects.js';
import { listen_to_event_and_reset_event } from './shared.js';
import { untrack } from '../../../runtime.js';
import { is } from '../../../proxy.js';
@ -40,7 +40,7 @@ export function select_option(select, value, mounting) {
*/
export function init_select(select, get_value) {
let mounting = true;
effect(() => {
element_effect(() => {
if (get_value) {
select_option(select, untrack(get_value), mounting);
}
@ -96,7 +96,7 @@ export function bind_select_value(select, get, set = get) {
});
// Needs to be an effect, not a render_effect, so that in case of each loops the logic runs after the each block has updated
effect(() => {
element_effect(() => {
var value = get();
select_option(select, value, mounting);

@ -1,4 +1,4 @@
import { effect, teardown } from '../../../reactivity/effects.js';
import { element_effect, teardown } from '../../../reactivity/effects.js';
import { untrack } from '../../../runtime.js';
/**
@ -100,7 +100,7 @@ export function bind_resize_observer(element, type, set) {
export function bind_element_size(element, type, set) {
var unsub = resize_observer_border_box.observe(element, () => set(element[type]));
effect(() => {
element_effect(() => {
// The update could contain reads which should be ignored
untrack(() => set(element[type]));
return unsub;

@ -1,5 +1,5 @@
import { STATE_SYMBOL } from '../../../constants.js';
import { effect, render_effect } from '../../../reactivity/effects.js';
import { element_effect, render_effect } from '../../../reactivity/effects.js';
import { untrack } from '../../../runtime.js';
import { queue_micro_task } from '../../task.js';
@ -23,7 +23,7 @@ function is_bound_this(bound_value, element_or_component) {
* @returns {void}
*/
export function bind_this(element_or_component = {}, update, get_value, get_parts) {
effect(() => {
element_effect(() => {
/** @type {unknown[]} */
var old_parts;

@ -35,7 +35,8 @@ import {
INSPECT_EFFECT,
HEAD_EFFECT,
MAYBE_DIRTY,
EFFECT_HAS_DERIVED
EFFECT_HAS_DERIVED,
ELEMENT_EFFECT
} from '../constants.js';
import { set } from './sources.js';
import * as e from '../errors.js';
@ -257,6 +258,14 @@ export function effect(fn) {
return create_effect(EFFECT, fn, false);
}
/**
* @param {() => void | (() => void)} fn
* @returns {Effect}
*/
export function element_effect(fn) {
return create_effect(EFFECT | ELEMENT_EFFECT, fn, false);
}
/**
* Internal representation of `$: ..`
* @param {() => any} deps

@ -24,7 +24,8 @@ import {
BLOCK_EFFECT,
ROOT_EFFECT,
LEGACY_DERIVED_PROP,
DISCONNECTED
DISCONNECTED,
ELEMENT_EFFECT
} from './constants.js';
import { flush_tasks } from './dom/task.js';
import { add_owner } from './dev/ownership.js';
@ -611,6 +612,7 @@ function process_effects(effect, collected_effects) {
var flags = current_effect.f;
var is_branch = (flags & BRANCH_EFFECT) !== 0;
var is_skippable_branch = is_branch && (flags & CLEAN) !== 0;
var sibling;
if (!is_skippable_branch && (flags & INERT) === 0) {
if ((flags & RENDER_EFFECT) !== 0) {
@ -623,6 +625,18 @@ function process_effects(effect, collected_effects) {
var child = current_effect.first;
if (child !== null) {
// Before traversing children, ensure that any sibling element effects
// are collected before any effects in children to keep the template
// effect ordering consistent
sibling = current_effect.next;
while (sibling !== null) {
if ((sibling.f & ELEMENT_EFFECT) !== 0) {
effects.push(sibling);
}
sibling = sibling.next;
}
current_effect = child;
continue;
}
@ -631,7 +645,7 @@ function process_effects(effect, collected_effects) {
}
}
var sibling = current_effect.next;
sibling = current_effect.next;
if (sibling === null) {
let parent = current_effect.parent;

@ -14,6 +14,6 @@ export default test({
};
},
test({ assert }) {
assert.deepEqual(result, ['each_action', 'import_action']); // ideally this would be reversed, but it doesn't matter a whole lot
assert.deepEqual(result, ['import_action', 'each_action']);
}
});

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
async test({ assert, logs }) {
assert.deepEqual(logs, [0, 1, 2, 3, 4, 5]);
}
});

@ -0,0 +1,19 @@
<script>
function create_action(){
let index = 0;
return ()=>{
console.log(index++);
}
}
const content = create_action();
</script>
<div use:content></div>
{#each {length: 5} as _}
<div>
<div use:content></div>
</div>
{/each}
Loading…
Cancel
Save