move effect code

pull/10582/head
Rich Harris 2 years ago
parent 187400b409
commit a8333d54de

@ -1,5 +1,6 @@
import { createClassComponent } from '../../legacy/legacy-client.js';
import { render_effect, destroy_signal } from './runtime.js';
import { destroy_signal } from './runtime.js';
import { render_effect } from './reactivity/effects.js';
import { open, close } from './render.js';
import { define_property } from './utils.js';

@ -8,9 +8,9 @@ import {
destroy_signal,
execute_effect,
flushSync,
push_destroy_fn,
render_effect
push_destroy_fn
} from '../../runtime.js';
import { render_effect } from '../../reactivity/effects.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').AwaitBlock} */

@ -23,10 +23,10 @@ import {
execute_effect,
mutable_source,
push_destroy_fn,
render_effect,
set_signal_value,
source
} from '../../runtime.js';
import { render_effect } from '../../reactivity/effects.js';
import { trigger_transitions } from '../../transitions.js';
import { is_array } from '../../utils.js';

@ -6,13 +6,8 @@ import {
set_current_hydration_fragment
} from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
current_block,
destroy_signal,
execute_effect,
push_destroy_fn,
render_effect
} from '../../runtime.js';
import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js';
import { render_effect } from '../../reactivity/effects.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').IfBlock} */

@ -7,9 +7,9 @@ import {
destroy_signal,
execute_effect,
push_destroy_fn,
render_effect,
safe_not_equal
} from '../../runtime.js';
import { render_effect } from '../../reactivity/effects.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').KeyBlock} */

@ -1,6 +1,5 @@
import { DEV } from 'esm-env';
import {
effect_active,
get,
set,
update,
@ -11,6 +10,7 @@ import {
batch_inspect,
current_component_context
} from './runtime.js';
import { effect_active } from './reactivity/effects.js';
import {
array_prototype,
define_property,

@ -0,0 +1,180 @@
import { DEV } from 'esm-env';
import {
DIRTY,
EFFECT,
MANAGED,
PRE_EFFECT,
RENDER_EFFECT,
create_computation_signal,
current_block,
current_component_context,
current_effect,
destroy_signal,
flush_local_render_effects,
push_reference,
schedule_effect
} from '../runtime.js';
/**
* @param {import('../types.js').EffectType} type
* @param {(() => void | (() => void)) | ((b: import('../types.js').Block) => void | (() => void))} init
* @param {boolean} sync
* @param {null | import('../types.js').Block} block
* @param {boolean} schedule
* @returns {import('../types.js').EffectSignal}
*/
function internal_create_effect(type, init, sync, block, schedule) {
const signal = create_computation_signal(type | DIRTY, null, block);
signal.i = init;
signal.x = current_component_context;
if (current_effect !== null) {
signal.l = current_effect.l + 1;
if ((type & MANAGED) === 0) {
push_reference(current_effect, signal);
}
}
if (schedule) {
schedule_effect(signal, sync);
}
return signal;
}
/**
* @returns {boolean}
*/
export function effect_active() {
return current_effect ? (current_effect.f & MANAGED) === 0 : false;
}
/**
* @param {() => void | (() => void)} init
* @returns {import('../types.js').EffectSignal}
*/
export function user_effect(init) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '')
);
}
const apply_component_effect_heuristics =
current_effect.f & RENDER_EFFECT &&
current_component_context !== null &&
!current_component_context.m;
const effect = internal_create_effect(
EFFECT,
init,
false,
current_block,
!apply_component_effect_heuristics
);
if (apply_component_effect_heuristics) {
const context = /** @type {import('../types.js').ComponentContext} */ (
current_component_context
);
(context.e ??= []).push(effect);
}
return effect;
}
/**
* @param {() => void | (() => void)} init
* @returns {() => void}
*/
export function user_root_effect(init) {
const effect = managed_render_effect(init);
return () => {
destroy_signal(effect);
};
}
/**
* @param {() => void | (() => void)} init
* @returns {import('../types.js').EffectSignal}
*/
export function effect(init) {
return internal_create_effect(EFFECT, init, false, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @returns {import('../types.js').EffectSignal}
*/
export function managed_effect(init) {
return internal_create_effect(EFFECT | MANAGED, init, false, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @param {boolean} sync
* @returns {import('../types.js').EffectSignal}
*/
export function managed_pre_effect(init, sync) {
return internal_create_effect(PRE_EFFECT | MANAGED, init, sync, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @returns {import('../types.js').EffectSignal}
*/
export function pre_effect(init) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV
? ': The Svelte $effect.pre rune can only be used during component initialisation.'
: '')
);
}
const sync = current_effect !== null && (current_effect.f & RENDER_EFFECT) !== 0;
return internal_create_effect(
PRE_EFFECT,
() => {
const val = init();
flush_local_render_effects();
return val;
},
sync,
current_block,
true
);
}
/**
* This effect is used to ensure binding are kept in sync. We use a pre effect to ensure we run before the
* bindings which are in later effects. However, we don't use a pre_effect directly as we don't want to flush anything.
*
* @param {() => void | (() => void)} init
* @returns {import('../types.js').EffectSignal}
*/
export function invalidate_effect(init) {
return internal_create_effect(PRE_EFFECT, init, true, current_block, true);
}
/**
* @template {import('../types.js').Block} B
* @param {(block: B) => void | (() => void)} init
* @param {any} block
* @param {any} managed
* @param {any} sync
* @returns {import('../types.js').EffectSignal}
*/
export function render_effect(init, block = current_block, managed = false, sync = true) {
let flags = RENDER_EFFECT;
if (managed) {
flags |= MANAGED;
}
return internal_create_effect(flags, /** @type {any} */ (init), sync, block, true);
}
/**
* @template {import('../types.js').Block} B
* @param {(block: B) => void | (() => void)} init
* @param {any} block
* @param {any} sync
* @returns {import('../types.js').EffectSignal}
*/
export function managed_render_effect(init, block = current_block, sync = true) {
const flags = RENDER_EFFECT | MANAGED;
return internal_create_effect(flags, /** @type {any} */ (init), sync, block, true);
}

@ -31,21 +31,19 @@ import {
remove
} from './reconciler.js';
import {
render_effect,
destroy_signal,
is_signal,
push_destroy_fn,
execute_effect,
untrack,
effect,
flush_sync,
current_block,
managed_effect,
push,
current_component_context,
pop,
deep_read
} from './runtime.js';
import { render_effect, effect, managed_effect } from './reactivity/effects.js';
import {
current_hydration_fragment,
get_hydration_fragment,

@ -19,13 +19,14 @@ import {
} from '../../constants.js';
import { STATE_SYMBOL, unstate } from './proxy.js';
import { EACH_BLOCK, IF_BLOCK } from './block.js';
import { pre_effect, user_effect } from './reactivity/effects.js';
export const SOURCE = 1;
export const DERIVED = 1 << 1;
export const EFFECT = 1 << 2;
export const PRE_EFFECT = 1 << 3;
export const RENDER_EFFECT = 1 << 4;
const MANAGED = 1 << 6;
export const MANAGED = 1 << 6;
const UNOWNED = 1 << 7;
export const CLEAN = 1 << 8;
export const DIRTY = 1 << 9;
@ -197,7 +198,7 @@ function create_source_signal(flags, value) {
* @param {import('./types.js').Block | null} block
* @returns {import('./types.js').ComputationSignal<V> | import('./types.js').ComputationSignal<V> & import('./types.js').SourceSignalDebug}
*/
function create_computation_signal(flags, value, block) {
export function create_computation_signal(flags, value, block) {
if (DEV) {
return {
// block
@ -262,7 +263,7 @@ function create_computation_signal(flags, value, block) {
* @param {import('./types.js').ComputationSignal} ref_signal
* @returns {void}
*/
function push_reference(target_signal, ref_signal) {
export function push_reference(target_signal, ref_signal) {
const references = target_signal.r;
if (references === null) {
target_signal.r = [ref_signal];
@ -1374,170 +1375,6 @@ export function untrack(fn) {
}
}
/**
* @param {import('./types.js').EffectType} type
* @param {(() => void | (() => void)) | ((b: import('./types.js').Block) => void | (() => void))} init
* @param {boolean} sync
* @param {null | import('./types.js').Block} block
* @param {boolean} schedule
* @returns {import('./types.js').EffectSignal}
*/
function internal_create_effect(type, init, sync, block, schedule) {
const signal = create_computation_signal(type | DIRTY, null, block);
signal.i = init;
signal.x = current_component_context;
if (current_effect !== null) {
signal.l = current_effect.l + 1;
if ((type & MANAGED) === 0) {
push_reference(current_effect, signal);
}
}
if (schedule) {
schedule_effect(signal, sync);
}
return signal;
}
/**
* @returns {boolean}
*/
export function effect_active() {
return current_effect ? (current_effect.f & MANAGED) === 0 : false;
}
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function user_effect(init) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '')
);
}
const apply_component_effect_heuristics =
current_effect.f & RENDER_EFFECT &&
current_component_context !== null &&
!current_component_context.m;
const effect = internal_create_effect(
EFFECT,
init,
false,
current_block,
!apply_component_effect_heuristics
);
if (apply_component_effect_heuristics) {
const context = /** @type {import('./types.js').ComponentContext} */ (
current_component_context
);
(context.e ??= []).push(effect);
}
return effect;
}
/**
* @param {() => void | (() => void)} init
* @returns {() => void}
*/
export function user_root_effect(init) {
const effect = managed_render_effect(init);
return () => {
destroy_signal(effect);
};
}
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function effect(init) {
return internal_create_effect(EFFECT, init, false, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function managed_effect(init) {
return internal_create_effect(EFFECT | MANAGED, init, false, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @param {boolean} sync
* @returns {import('./types.js').EffectSignal}
*/
export function managed_pre_effect(init, sync) {
return internal_create_effect(PRE_EFFECT | MANAGED, init, sync, current_block, true);
}
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function pre_effect(init) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV
? ': The Svelte $effect.pre rune can only be used during component initialisation.'
: '')
);
}
const sync = current_effect !== null && (current_effect.f & RENDER_EFFECT) !== 0;
return internal_create_effect(
PRE_EFFECT,
() => {
const val = init();
flush_local_render_effects();
return val;
},
sync,
current_block,
true
);
}
/**
* This effect is used to ensure binding are kept in sync. We use a pre effect to ensure we run before the
* bindings which are in later effects. However, we don't use a pre_effect directly as we don't want to flush anything.
*
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
export function invalidate_effect(init) {
return internal_create_effect(PRE_EFFECT, init, true, current_block, true);
}
/**
* @template {import('./types.js').Block} B
* @param {(block: B) => void | (() => void)} init
* @param {any} block
* @param {any} managed
* @param {any} sync
* @returns {import('./types.js').EffectSignal}
*/
export function render_effect(init, block = current_block, managed = false, sync = true) {
let flags = RENDER_EFFECT;
if (managed) {
flags |= MANAGED;
}
return internal_create_effect(flags, /** @type {any} */ (init), sync, block, true);
}
/**
* @template {import('./types.js').Block} B
* @param {(block: B) => void | (() => void)} init
* @param {any} block
* @param {any} sync
* @returns {import('./types.js').EffectSignal}
*/
export function managed_render_effect(init, block = current_block, sync = true) {
const flags = RENDER_EFFECT | MANAGED;
return internal_create_effect(flags, /** @type {any} */ (init), sync, block, true);
}
/**
* @template V
* @param {import('./types.js').ComputationSignal<V>} signal

@ -11,14 +11,12 @@ import {
} from './block.js';
import { destroy_each_item_block, get_first_element } from './dom/blocks/each.js';
import { append_child, empty } from './operations.js';
import { effect, managed_effect, managed_pre_effect } from './reactivity/effects.js';
import {
current_block,
current_effect,
destroy_signal,
effect,
execute_effect,
managed_effect,
managed_pre_effect,
mark_subtree_inert,
schedule_raf_task,
untrack

@ -9,10 +9,6 @@ export {
derived,
derived_safe_equal,
prop,
user_effect,
render_effect,
pre_effect,
invalidate_effect,
flushSync,
bubble_event,
safe_equal,
@ -33,8 +29,6 @@ export {
pop,
push,
reactive_import,
effect_active,
user_root_effect,
inspect,
unwrap,
freeze,
@ -46,6 +40,7 @@ export { await_block as await } from './client/dom/blocks/await.js';
export { if_block as if } from './client/dom/blocks/if.js';
export { key_block as key } from './client/dom/blocks/key.js';
export * from './client/dom/blocks/each.js';
export * from './client/reactivity/effects.js';
export * from './client/render.js';
export * from './client/validate.js';
export { raf } from './client/timing.js';

@ -1,10 +1,10 @@
import {
current_component_context,
get_or_init_context_map,
untrack,
user_effect
untrack
} from '../internal/client/runtime.js';
import { is_array } from '../internal/client/utils.js';
import { user_effect } from '../internal/index.js';
/**
* The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.

@ -1,5 +1,6 @@
import { describe, assert, it } from 'vitest';
import * as $ from '../../src/internal/client/runtime';
import { effect, render_effect, user_effect } from '../../src/internal/client/reactivity/effects';
import type { ComputationSignal } from '../../src/internal/client/types';
/**
@ -13,7 +14,7 @@ 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 signal = render_effect(
() => {
execute = fn(runes);
},
@ -38,7 +39,7 @@ describe('signals', () => {
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
$.effect(() => {
effect(() => {
log.push(`${$.get(count)}:${$.get(double)}`);
});
@ -56,10 +57,10 @@ describe('signals', () => {
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
$.effect(() => {
effect(() => {
log.push(`A:${$.get(count)}:${$.get(double)}`);
});
$.effect(() => {
effect(() => {
log.push(`B:${$.get(double)}`);
});
@ -77,10 +78,10 @@ describe('signals', () => {
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
$.effect(() => {
effect(() => {
log.push(`A:${$.get(double)}`);
});
$.effect(() => {
effect(() => {
log.push(`B:${$.get(count)}:${$.get(double)}`);
});
@ -98,7 +99,7 @@ describe('signals', () => {
let count = $.source(0);
let double = $.derived(() => $.get(count) * 2);
$.effect(() => {
effect(() => {
log.push($.get(double));
});
@ -117,7 +118,7 @@ describe('signals', () => {
let double = $.derived(() => $.get(count) * 2);
let quadruple = $.derived(() => $.get(double) * 2);
$.effect(() => {
effect(() => {
log.push($.get(quadruple));
});
@ -143,13 +144,13 @@ describe('signals', () => {
const E = $.derived(() => hard($.get(C) + $.get(A) + $.get(D)[0]!, 'E'));
const F = $.derived(() => hard($.get(D)[0]! && $.get(B), 'F'));
const G = $.derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0]! + $.get(F));
$.effect(() => {
effect(() => {
res.push(hard($.get(G), 'H'));
});
$.effect(() => {
effect(() => {
res.push($.get(G));
});
$.effect(() => {
effect(() => {
res.push(hard($.get(F), 'J'));
});
@ -182,7 +183,7 @@ describe('signals', () => {
return $.get(x);
};
const derivedCount = $.derived(() => read().count);
$.user_effect(() => {
user_effect(() => {
log.push($.get(derivedCount));
});
@ -244,7 +245,7 @@ describe('signals', () => {
test('effect with derived using unowned derived every time', () => {
const log: Array<number | string> = [];
const effect = $.user_effect(() => {
const effect = user_effect(() => {
log.push($.get(calc));
});
@ -270,11 +271,11 @@ describe('signals', () => {
test('two effects with an unowned derived that has no depedencies', () => {
const log: Array<Array<any>> = [];
$.render_effect(() => {
render_effect(() => {
log.push($.get(no_deps));
});
$.render_effect(() => {
render_effect(() => {
log.push($.get(no_deps));
});
@ -292,11 +293,11 @@ describe('signals', () => {
test('two effects with an unowned derived that has some depedencies', () => {
const log: Array<Array<any>> = [];
$.render_effect(() => {
render_effect(() => {
log.push($.get(some_deps));
});
$.render_effect(() => {
render_effect(() => {
log.push($.get(some_deps));
});
@ -310,7 +311,7 @@ describe('signals', () => {
if (!runes) return () => {};
const value = $.source({ count: 0 });
$.user_effect(() => {
user_effect(() => {
$.set(value, { count: 0 });
$.get(value);
});

Loading…
Cancel
Save