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 { 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 { open, close } from './render.js';
import { define_property } from './utils.js'; import { define_property } from './utils.js';

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

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

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

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

@ -1,6 +1,5 @@
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { import {
effect_active,
get, get,
set, set,
update, update,
@ -11,6 +10,7 @@ import {
batch_inspect, batch_inspect,
current_component_context current_component_context
} from './runtime.js'; } from './runtime.js';
import { effect_active } from './reactivity/effects.js';
import { import {
array_prototype, array_prototype,
define_property, 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 remove
} from './reconciler.js'; } from './reconciler.js';
import { import {
render_effect,
destroy_signal, destroy_signal,
is_signal, is_signal,
push_destroy_fn, push_destroy_fn,
execute_effect, execute_effect,
untrack, untrack,
effect,
flush_sync, flush_sync,
current_block, current_block,
managed_effect,
push, push,
current_component_context, current_component_context,
pop, pop,
deep_read deep_read
} from './runtime.js'; } from './runtime.js';
import { render_effect, effect, managed_effect } from './reactivity/effects.js';
import { import {
current_hydration_fragment, current_hydration_fragment,
get_hydration_fragment, get_hydration_fragment,

@ -19,13 +19,14 @@ import {
} from '../../constants.js'; } from '../../constants.js';
import { STATE_SYMBOL, unstate } from './proxy.js'; import { STATE_SYMBOL, unstate } from './proxy.js';
import { EACH_BLOCK, IF_BLOCK } from './block.js'; import { EACH_BLOCK, IF_BLOCK } from './block.js';
import { pre_effect, user_effect } from './reactivity/effects.js';
export const SOURCE = 1; export const SOURCE = 1;
export const DERIVED = 1 << 1; export const DERIVED = 1 << 1;
export const EFFECT = 1 << 2; export const EFFECT = 1 << 2;
export const PRE_EFFECT = 1 << 3; export const PRE_EFFECT = 1 << 3;
export const RENDER_EFFECT = 1 << 4; export const RENDER_EFFECT = 1 << 4;
const MANAGED = 1 << 6; export const MANAGED = 1 << 6;
const UNOWNED = 1 << 7; const UNOWNED = 1 << 7;
export const CLEAN = 1 << 8; export const CLEAN = 1 << 8;
export const DIRTY = 1 << 9; export const DIRTY = 1 << 9;
@ -197,7 +198,7 @@ function create_source_signal(flags, value) {
* @param {import('./types.js').Block | null} block * @param {import('./types.js').Block | null} block
* @returns {import('./types.js').ComputationSignal<V> | import('./types.js').ComputationSignal<V> & import('./types.js').SourceSignalDebug} * @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) { if (DEV) {
return { return {
// block // block
@ -262,7 +263,7 @@ function create_computation_signal(flags, value, block) {
* @param {import('./types.js').ComputationSignal} ref_signal * @param {import('./types.js').ComputationSignal} ref_signal
* @returns {void} * @returns {void}
*/ */
function push_reference(target_signal, ref_signal) { export function push_reference(target_signal, ref_signal) {
const references = target_signal.r; const references = target_signal.r;
if (references === null) { if (references === null) {
target_signal.r = [ref_signal]; 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 * @template V
* @param {import('./types.js').ComputationSignal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal

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

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

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

Loading…
Cancel
Save