Skip to content

more effect ordering stuff #10958

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function is_bound_this(bound_value, element_or_component) {
* @returns {void}
*/
export function bind_this(element_or_component, update, get_value, get_parts) {
render_effect(() => {
effect(() => {
/** @type {unknown[]} */
var old_parts;

Expand Down
41 changes: 25 additions & 16 deletions packages/svelte/src/internal/client/reactivity/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ import { remove } from '../dom/reconciler.js';
* @param {import('./types.js').EffectType} type
* @param {(() => void | (() => void))} fn
* @param {boolean} sync
* @param {boolean} init
* @returns {import('#client').Effect}
*/
function create_effect(type, fn, sync, init = true) {
function create_effect(type, fn, sync) {
var is_root = (type & ROOT_EFFECT) !== 0;
/** @type {import('#client').Effect} */
var effect = {
Expand All @@ -61,20 +60,18 @@ function create_effect(type, fn, sync, init = true) {
}
}

if (init) {
if (sync) {
var previously_flushing_effect = is_flushing_effect;
if (sync) {
var previously_flushing_effect = is_flushing_effect;

try {
set_is_flushing_effect(true);
execute_effect(effect);
effect.f |= EFFECT_RAN;
} finally {
set_is_flushing_effect(previously_flushing_effect);
}
} else {
schedule_effect(effect);
try {
set_is_flushing_effect(true);
execute_effect(effect);
effect.f |= EFFECT_RAN;
} finally {
set_is_flushing_effect(previously_flushing_effect);
}
} else {
schedule_effect(effect);
}

return effect;
Expand All @@ -91,7 +88,6 @@ export function effect_active() {
/**
* Internal representation of `$effect(...)`
* @param {() => void | (() => void)} fn
* @returns {import('#client').Effect}
*/
export function user_effect(fn) {
if (current_effect === null) {
Expand All @@ -101,7 +97,20 @@ export function user_effect(fn) {
);
}

return create_effect(EFFECT, fn, false, true);
// Non-nested `$effect(...)` in a component should be deferred
// until the component is mounted
const defer =
current_effect.f & RENDER_EFFECT &&
// TODO do we actually need this? removing them changes nothing
current_component_context !== null &&
!current_component_context.m;

if (defer) {
const context = /** @type {import('#client').ComponentContext} */ (current_component_context);
(context.e ??= []).push(fn);
} else {
effect(fn);
}
}

/**
Expand Down
11 changes: 10 additions & 1 deletion packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
object_prototype
} from './utils.js';
import { unstate } from './proxy.js';
import { destroy_effect, user_pre_effect } from './reactivity/effects.js';
import { destroy_effect, effect, user_pre_effect } from './reactivity/effects.js';
import {
EFFECT,
RENDER_EFFECT,
Expand Down Expand Up @@ -1094,6 +1094,8 @@ export function push(props, runes = false, fn) {
x: null,
// context
c: null,
// effects
e: null,
// mounted
m: false,
// parent
Expand Down Expand Up @@ -1129,6 +1131,13 @@ export function pop(component) {
if (component !== undefined) {
context_stack_item.x = component;
}
const effects = context_stack_item.e;
if (effects !== null) {
context_stack_item.e = null;
for (let i = 0; i < effects.length; i++) {
effect(effects[i]);
}
}
current_component_context = context_stack_item.p;
context_stack_item.m = true;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/src/internal/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export type ComponentContext = {
s: Record<string, unknown>;
/** exports (and props, if `accessors: true`) */
x: Record<string, any> | null;
/** deferred effects */
e: null | Array<() => void | (() => void)>;
/** mounted */
m: boolean;
/** parent */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { test } from '../../test';
import { destroyed, reset } from './destroyed.js';

export default test({
test({ assert, component }) {
// for hydration, ssr may have pushed to `destroyed`
before_test() {
reset();
},

test({ assert, component }) {
component.visible = false;
assert.deepEqual(destroyed, ['A', 'B', 'C']);

reset();
},

test_ssr({ assert }) {
Expand Down
14 changes: 8 additions & 6 deletions packages/svelte/tests/signals/test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { describe, assert, it } from 'vitest';
import * as $ from '../../src/internal/client/runtime';
import {
destroy_effect,
effect,
effect_root,
render_effect,
user_effect
} from '../../src/internal/client/reactivity/effects';
Expand All @@ -22,12 +22,12 @@ function run_test(runes: boolean, fn: (runes: boolean) => () => void) {
$.push({}, runes);
// Create a render context so that effect validations etc don't fail
let execute: any;
const signal = render_effect(() => {
const destroy = effect_root(() => {
execute = fn(runes);
});
$.pop();
execute();
destroy_effect(signal);
destroy();
};
}

Expand Down Expand Up @@ -248,8 +248,10 @@ describe('signals', () => {
test('effect with derived using unowned derived every time', () => {
const log: Array<number | string> = [];

const effect = user_effect(() => {
log.push($.get(calc));
const destroy = effect_root(() => {
user_effect(() => {
log.push($.get(calc));
});
});

return () => {
Expand All @@ -261,7 +263,7 @@ describe('signals', () => {
// Ensure we're not leaking consumers
assert.deepEqual(count.reactions?.length, 1);
assert.deepEqual(log, [0, 2, 'limit', 0]);
destroy_effect(effect);
destroy();
// Ensure we're not leaking consumers
assert.deepEqual(count.reactions, null);
};
Expand Down