Merge branch 'main' into sequencing

pull/10939/head
Dominic Gannaway 2 years ago
commit e1da295509

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: use implicit return for each block keys

@ -371,7 +371,7 @@ export const javascript_visitors_runes = {
const func = context.visit(node.expression.arguments[0]); const func = context.visit(node.expression.arguments[0]);
return { return {
...node, ...node,
expression: b.call('$.pre_effect', /** @type {import('estree').Expression} */ (func)) expression: b.call('$.user_pre_effect', /** @type {import('estree').Expression} */ (func))
}; };
} }
} }

@ -231,7 +231,7 @@ function setup_select_synchronization(value_binding, context) {
context.state.init.push( context.state.init.push(
b.stmt( b.stmt(
b.call( b.call(
'$.invalidate_effect', '$.pre_effect',
b.thunk( b.thunk(
b.block([ b.block([
b.stmt( b.stmt(
@ -747,7 +747,7 @@ function serialize_inline_component(node, component_name, context) {
binding_initializers.push( binding_initializers.push(
b.stmt( b.stmt(
b.call( b.call(
b.id('$.pre_effect'), b.id('$.user_pre_effect'),
b.thunk(b.call(b.id('$.add_owner'), expression, b.id(component_name))) b.thunk(b.call(b.id('$.add_owner'), expression, b.id(component_name)))
) )
) )
@ -2329,11 +2329,13 @@ export const template_visitors = {
const key_function = node.key const key_function = node.key
? b.arrow( ? b.arrow(
[node.context.type === 'Identifier' ? node.context : b.id('$$item'), index], [node.context.type === 'Identifier' ? node.context : b.id('$$item'), index],
b.block( declarations.length > 0
? b.block(
declarations.concat( declarations.concat(
b.return(/** @type {import('estree').Expression} */ (context.visit(node.key))) b.return(/** @type {import('estree').Expression} */ (context.visit(node.key)))
) )
) )
: /** @type {import('estree').Expression} */ (context.visit(node.key))
) )
: b.literal(null); : b.literal(null);
@ -2359,16 +2361,16 @@ export const template_visitors = {
args.push( args.push(
context.state.node, context.state.node,
each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection),
b.literal(each_type), b.literal(each_type),
each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection),
key_function, key_function,
b.arrow([b.id('$$anchor'), item, index], b.block(declarations.concat(children))) b.arrow([b.id('$$anchor'), item, index], b.block(declarations.concat(children)))
); );
} else { } else {
args.push( args.push(
context.state.node, context.state.node,
each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection),
b.literal(each_type), b.literal(each_type),
each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection),
b.arrow([b.id('$$anchor'), item, index], b.block(declarations.concat(children))) b.arrow([b.id('$$anchor'), item, index], b.block(declarations.concat(children)))
); );
} }

@ -46,15 +46,15 @@ export function set_current_each_item(item) {
/** /**
* @template V * @template V
* @param {Element | Comment} anchor The next sibling node, or the parent node if this is a 'controlled' block * @param {Element | Comment} anchor The next sibling node, or the parent node if this is a 'controlled' block
* @param {() => V[]} get_collection
* @param {number} flags * @param {number} flags
* @param {() => V[]} get_collection
* @param {null | ((item: V) => string)} get_key * @param {null | ((item: V) => string)} get_key
* @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn * @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn
* @param {null | ((anchor: Node | null) => void)} fallback_fn * @param {null | ((anchor: Node | null) => void)} fallback_fn
* @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn
* @returns {void} * @returns {void}
*/ */
function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, reconcile_fn) { function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, reconcile_fn) {
/** @type {import('#client').EachState} */ /** @type {import('#client').EachState} */
var state = { flags, items: [] }; var state = { flags, items: [] };
@ -193,28 +193,28 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re
/** /**
* @template V * @template V
* @param {Element | Comment} anchor * @param {Element | Comment} anchor
* @param {() => V[]} get_collection
* @param {number} flags * @param {number} flags
* @param {() => V[]} get_collection
* @param {null | ((item: V) => string)} get_key * @param {null | ((item: V) => string)} get_key
* @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn * @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn
* @param {null | ((anchor: Node | null) => void)} [fallback_fn] * @param {null | ((anchor: Node | null) => void)} [fallback_fn]
* @returns {void} * @returns {void}
*/ */
export function each_keyed(anchor, get_collection, flags, get_key, render_fn, fallback_fn = null) { export function each_keyed(anchor, flags, get_collection, get_key, render_fn, fallback_fn = null) {
each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, reconcile_tracked_array); each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, reconcile_tracked_array);
} }
/** /**
* @template V * @template V
* @param {Element | Comment} anchor * @param {Element | Comment} anchor
* @param {() => V[]} get_collection
* @param {number} flags * @param {number} flags
* @param {() => V[]} get_collection
* @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn * @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn
* @param {null | ((anchor: Node | null) => void)} [fallback_fn] * @param {null | ((anchor: Node | null) => void)} [fallback_fn]
* @returns {void} * @returns {void}
*/ */
export function each_indexed(anchor, get_collection, flags, render_fn, fallback_fn = null) { export function each_indexed(anchor, flags, get_collection, render_fn, fallback_fn = null) {
each(anchor, get_collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array); each(anchor, flags, get_collection, null, render_fn, fallback_fn, reconcile_indexed_array);
} }
/** /**

@ -1,5 +1,5 @@
import { run } from '../../../common.js'; import { run } from '../../../common.js';
import { pre_effect, user_effect } from '../../reactivity/effects.js'; import { user_pre_effect, user_effect } from '../../reactivity/effects.js';
import { import {
current_component_context, current_component_context,
deep_read_state, deep_read_state,
@ -19,7 +19,7 @@ export function init() {
// beforeUpdate // beforeUpdate
if (callbacks.b.length) { if (callbacks.b.length) {
pre_effect(() => { user_pre_effect(() => {
observe_all(context); observe_all(context);
callbacks.b.forEach(run); callbacks.b.forEach(run);
// beforeUpdate might change state that affects rendering, ensure the render effects following from it // beforeUpdate might change state that affects rendering, ensure the render effects following from it

@ -7,8 +7,10 @@ import {
destroy_children, destroy_children,
execute_effect, execute_effect,
get, get,
is_flushing_effect,
remove_reactions, remove_reactions,
schedule_effect, schedule_effect,
set_is_flushing_effect,
set_signal_status, set_signal_status,
untrack untrack
} from '../runtime.js'; } from '../runtime.js';
@ -20,7 +22,8 @@ import {
PRE_EFFECT, PRE_EFFECT,
DESTROYED, DESTROYED,
INERT, INERT,
IS_ELSEIF IS_ELSEIF,
EFFECT_RAN
} from '../constants.js'; } from '../constants.js';
import { set } from './sources.js'; import { set } from './sources.js';
import { noop } from '../../common.js'; import { noop } from '../../common.js';
@ -35,7 +38,7 @@ import { remove } from '../dom/reconciler.js';
*/ */
function create_effect(type, fn, sync, init = true) { function create_effect(type, fn, sync, init = true) {
/** @type {import('#client').Effect} */ /** @type {import('#client').Effect} */
const signal = { const effect = {
parent: current_effect, parent: current_effect,
dom: null, dom: null,
deps: null, deps: null,
@ -51,22 +54,34 @@ function create_effect(type, fn, sync, init = true) {
}; };
if (current_effect !== null) { if (current_effect !== null) {
signal.l = current_effect.l + 1; effect.l = current_effect.l + 1;
} }
if (current_reaction !== null) { if (current_reaction !== null) {
if (current_reaction.effects === null) { if (current_reaction.effects === null) {
current_reaction.effects = [signal]; current_reaction.effects = [effect];
} else { } else {
current_reaction.effects.push(signal); current_reaction.effects.push(effect);
} }
} }
if (init) { if (init) {
schedule_effect(signal, sync); if (sync) {
const 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);
}
} }
return signal; return effect;
} }
/** /**
@ -108,6 +123,24 @@ export function user_effect(fn) {
return effect; return effect;
} }
/**
* Internal representation of `$effect.pre(...)`
* @param {() => void | (() => void)} fn
* @returns {import('#client').Effect}
*/
export function user_pre_effect(fn) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV
? ': The Svelte $effect.pre rune can only be used during component initialisation.'
: '')
);
}
return pre_effect(fn);
}
/** /**
* Internal representation of `$effect.root(...)` * Internal representation of `$effect.root(...)`
* @param {() => void | (() => void)} fn * @param {() => void | (() => void)} fn
@ -128,24 +161,6 @@ export function effect(fn) {
return create_effect(EFFECT, fn, false); return create_effect(EFFECT, fn, false);
} }
/**
* Internal representation of `$effect.pre(...)`
* @param {() => void | (() => void)} fn
* @returns {import('#client').Effect}
*/
export function pre_effect(fn) {
if (current_effect === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_EFFECT' +
(DEV
? ': The Svelte $effect.pre rune can only be used during component initialisation.'
: '')
);
}
return create_effect(PRE_EFFECT, fn, true);
}
/** /**
* Internal representation of `$: ..` * Internal representation of `$: ..`
* @param {() => any} deps * @param {() => any} deps
@ -157,9 +172,7 @@ export function legacy_pre_effect(deps, fn) {
current_component_context current_component_context
); );
const token = {}; const token = {};
return create_effect( return pre_effect(() => {
PRE_EFFECT,
() => {
deps(); deps();
if (component_context.l1.includes(token)) { if (component_context.l1.includes(token)) {
return; return;
@ -167,9 +180,7 @@ export function legacy_pre_effect(deps, fn) {
component_context.l1.push(token); component_context.l1.push(token);
set(component_context.l2, true); set(component_context.l2, true);
return untrack(fn); return untrack(fn);
}, });
true
);
} }
export function legacy_pre_effect_reset() { export function legacy_pre_effect_reset() {
@ -186,13 +197,10 @@ export function legacy_pre_effect_reset() {
} }
/** /**
* 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)} fn * @param {() => void | (() => void)} fn
* @returns {import('#client').Effect} * @returns {import('#client').Effect}
*/ */
export function invalidate_effect(fn) { export function pre_effect(fn) {
return create_effect(PRE_EFFECT, fn, true); return create_effect(PRE_EFFECT, fn, true);
} }

@ -135,7 +135,7 @@ export function set(signal, value) {
) { ) {
if (current_dependencies !== null && current_dependencies.includes(signal)) { if (current_dependencies !== null && current_dependencies.includes(signal)) {
set_signal_status(current_effect, DIRTY); set_signal_status(current_effect, DIRTY);
schedule_effect(current_effect, false); schedule_effect(current_effect);
} else { } else {
if (current_untracked_writes === null) { if (current_untracked_writes === null) {
set_current_untracked_writes([signal]); set_current_untracked_writes([signal]);

@ -8,7 +8,7 @@ import {
object_prototype object_prototype
} from './utils.js'; } from './utils.js';
import { unstate } from './proxy.js'; import { unstate } from './proxy.js';
import { destroy_effect, pre_effect } from './reactivity/effects.js'; import { destroy_effect, user_pre_effect } from './reactivity/effects.js';
import { import {
EFFECT, EFFECT,
PRE_EFFECT, PRE_EFFECT,
@ -36,7 +36,13 @@ const FLUSH_SYNC = 1;
let current_scheduler_mode = FLUSH_MICROTASK; let current_scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling // Used for handling scheduling
let is_micro_task_queued = false; let is_micro_task_queued = false;
let is_flushing_effect = false; export let is_flushing_effect = false;
/** @param {boolean} value */
export function set_is_flushing_effect(value) {
is_flushing_effect = value;
}
// Used for $inspect // Used for $inspect
export let is_batching_effect = false; export let is_batching_effect = false;
let is_inspecting_signal = false; let is_inspecting_signal = false;
@ -210,9 +216,6 @@ export function check_dirtiness(reaction) {
* @returns {V} * @returns {V}
*/ */
export function execute_reaction_fn(signal) { export function execute_reaction_fn(signal) {
const fn = signal.fn;
const flags = signal.f;
const previous_dependencies = current_dependencies; const previous_dependencies = current_dependencies;
const previous_dependencies_index = current_dependencies_index; const previous_dependencies_index = current_dependencies_index;
const previous_untracked_writes = current_untracked_writes; const previous_untracked_writes = current_untracked_writes;
@ -224,11 +227,11 @@ export function execute_reaction_fn(signal) {
current_dependencies_index = 0; current_dependencies_index = 0;
current_untracked_writes = null; current_untracked_writes = null;
current_reaction = signal; current_reaction = signal;
current_skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0; current_skip_reaction = !is_flushing_effect && (signal.f & UNOWNED) !== 0;
current_untracking = false; current_untracking = false;
try { try {
let res = fn(); let res = signal.fn();
let dependencies = /** @type {import('./types.js').Value<unknown>[]} **/ (signal.deps); let dependencies = /** @type {import('./types.js').Value<unknown>[]} **/ (signal.deps);
if (current_dependencies !== null) { if (current_dependencies !== null) {
let i; let i;
@ -373,33 +376,35 @@ export function destroy_children(signal) {
} }
/** /**
* @param {import('./types.js').Effect} signal * @param {import('./types.js').Effect} effect
* @returns {void} * @returns {void}
*/ */
export function execute_effect(signal) { export function execute_effect(effect) {
if ((signal.f & DESTROYED) !== 0) { if ((effect.f & DESTROYED) !== 0) {
return; return;
} }
const previous_effect = current_effect; set_signal_status(effect, CLEAN);
const previous_component_context = current_component_context;
var component_context = effect.ctx;
const component_context = signal.ctx; var previous_effect = current_effect;
var previous_component_context = current_component_context;
current_effect = signal; current_effect = effect;
current_component_context = component_context; current_component_context = component_context;
try { try {
destroy_children(signal); destroy_children(effect);
signal.teardown?.(); effect.teardown?.();
const teardown = execute_reaction_fn(signal); var teardown = execute_reaction_fn(effect);
signal.teardown = typeof teardown === 'function' ? teardown : null; effect.teardown = typeof teardown === 'function' ? teardown : null;
} finally { } finally {
current_effect = previous_effect; current_effect = previous_effect;
current_component_context = previous_component_context; current_component_context = previous_component_context;
} }
if ((signal.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) { if ((effect.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) {
flush_local_pre_effects(component_context); flush_local_pre_effects(component_context);
} }
} }
@ -436,7 +441,6 @@ function flush_queued_effects(effects) {
if ((signal.f & (DESTROYED | INERT)) === 0) { if ((signal.f & (DESTROYED | INERT)) === 0) {
if (check_dirtiness(signal)) { if (check_dirtiness(signal)) {
set_signal_status(signal, CLEAN);
execute_effect(signal); execute_effect(signal);
} }
} }
@ -466,27 +470,18 @@ function process_microtask() {
/** /**
* @param {import('./types.js').Effect} signal * @param {import('./types.js').Effect} signal
* @param {boolean} sync
* @returns {void} * @returns {void}
*/ */
export function schedule_effect(signal, sync) { export function schedule_effect(signal) {
const flags = signal.f; const flags = signal.f;
if (sync) {
const previously_flushing_effect = is_flushing_effect;
try {
is_flushing_effect = true;
execute_effect(signal);
set_signal_status(signal, CLEAN);
} finally {
is_flushing_effect = previously_flushing_effect;
}
} else {
if (current_scheduler_mode === FLUSH_MICROTASK) { if (current_scheduler_mode === FLUSH_MICROTASK) {
if (!is_micro_task_queued) { if (!is_micro_task_queued) {
is_micro_task_queued = true; is_micro_task_queued = true;
queueMicrotask(process_microtask); queueMicrotask(process_microtask);
} }
} }
if ((flags & EFFECT) !== 0) { if ((flags & EFFECT) !== 0) {
current_queued_effects.push(signal); current_queued_effects.push(signal);
// Prevent any nested user effects from potentially triggering // Prevent any nested user effects from potentially triggering
@ -544,9 +539,6 @@ export function schedule_effect(signal, sync) {
} }
} }
signal.f |= EFFECT_RAN;
}
/** /**
* @returns {void} * @returns {void}
*/ */
@ -696,7 +688,7 @@ export function get(signal) {
current_untracked_writes.includes(signal) current_untracked_writes.includes(signal)
) { ) {
set_signal_status(current_effect, DIRTY); set_signal_status(current_effect, DIRTY);
schedule_effect(current_effect, false); schedule_effect(current_effect);
} }
} }
@ -772,7 +764,7 @@ export function mark_subtree_inert(signal, inert) {
if (is_already_inert !== inert) { if (is_already_inert !== inert) {
signal.f ^= INERT; signal.f ^= INERT;
if (!inert && (flags & CLEAN) === 0) { if (!inert && (flags & CLEAN) === 0) {
schedule_effect(signal, false); schedule_effect(signal);
} }
} }
@ -819,7 +811,7 @@ export function mark_reactions(signal, to_status, force_schedule) {
force_schedule force_schedule
); );
} else { } else {
schedule_effect(/** @type {import('#client').Effect} */ (reaction), false); schedule_effect(/** @type {import('#client').Effect} */ (reaction));
} }
} }
} }
@ -1075,7 +1067,7 @@ export function pop(component) {
if (effects !== null) { if (effects !== null) {
context_stack_item.e = null; context_stack_item.e = null;
for (let i = 0; i < effects.length; i++) { for (let i = 0; i < effects.length; i++) {
schedule_effect(effects[i], false); schedule_effect(effects[i]);
} }
} }
current_component_context = context_stack_item.p; current_component_context = context_stack_item.p;
@ -1201,7 +1193,7 @@ let warned_inspect_changed = false;
export function inspect(get_value, inspect = console.log) { export function inspect(get_value, inspect = console.log) {
let initial = true; let initial = true;
pre_effect(() => { user_pre_effect(() => {
const fn = () => { const fn = () => {
const value = untrack(() => get_value().map((v) => deep_unstate(v))); const value = untrack(() => get_value().map((v) => deep_unstate(v)));
if (value.length === 2 && typeof value[1] === 'function' && !warned_inspect_changed) { if (value.length === 2 && typeof value[1] === 'function' && !warned_inspect_changed) {

@ -10,7 +10,7 @@ export default function Each_string_template($$anchor, $$props) {
var fragment = $.comment($$anchor); var fragment = $.comment($$anchor);
var node = $.first_child(fragment); var node = $.first_child(fragment);
$.each_indexed(node, () => ['foo', 'bar', 'baz'], 1, ($$anchor, thing, $$index) => { $.each_indexed(node, 1, () => ['foo', 'bar', 'baz'], ($$anchor, thing, $$index) => {
var text = $.space_frag($$anchor); var text = $.space_frag($$anchor);
$.render_effect(() => $.set_text(text, `${$.stringify($.unwrap(thing))}, `)); $.render_effect(() => $.set_text(text, `${$.stringify($.unwrap(thing))}, `));

Loading…
Cancel
Save