From 5e9b29c351294e84d8e652ac871fbe3e7d620351 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Jan 2025 15:57:26 -0500 Subject: [PATCH] chore: move context code into new module (#15132) --- packages/svelte/src/index-client.js | 15 +- .../svelte/src/internal/client/context.js | 214 +++++++++++++++++ .../svelte/src/internal/client/dev/legacy.js | 2 +- .../src/internal/client/dev/ownership.js | 2 +- .../src/internal/client/dom/blocks/await.js | 13 +- .../internal/client/dom/blocks/boundary.js | 3 +- .../src/internal/client/dom/blocks/html.js | 2 +- .../src/internal/client/dom/blocks/snippet.js | 2 +- .../client/dom/blocks/svelte-element.js | 3 +- .../internal/client/dom/legacy/lifecycle.js | 3 +- packages/svelte/src/internal/client/index.js | 9 +- packages/svelte/src/internal/client/proxy.js | 5 +- .../internal/client/reactivity/deriveds.js | 4 +- .../src/internal/client/reactivity/effects.js | 3 +- .../src/internal/client/reactivity/sources.js | 2 +- packages/svelte/src/internal/client/render.js | 3 +- .../svelte/src/internal/client/runtime.js | 220 +----------------- .../svelte/src/internal/client/validate.js | 2 +- packages/svelte/src/legacy/legacy-client.js | 10 +- packages/svelte/tests/signals/test.ts | 5 +- packages/svelte/types/index.d.ts | 56 ++--- 21 files changed, 288 insertions(+), 290 deletions(-) create mode 100644 packages/svelte/src/internal/client/context.js diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index 587d76623331..bd2b353395c6 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -1,12 +1,13 @@ /** @import { ComponentContext, ComponentContextLegacy } from '#client' */ /** @import { EventDispatcher } from './index.js' */ /** @import { NotFunction } from './internal/types.js' */ -import { component_context, flush_sync, untrack } from './internal/client/runtime.js'; +import { flush_sync, untrack } from './internal/client/runtime.js'; import { is_array } from './internal/shared/utils.js'; import { user_effect } from './internal/client/index.js'; import * as e from './internal/client/errors.js'; import { lifecycle_outside_component } from './internal/shared/errors.js'; import { legacy_mode_flag } from './internal/flags/index.js'; +import { component_context } from './internal/client/context.js'; /** * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. @@ -179,15 +180,7 @@ export function flushSync(fn) { flush_sync(fn); } +export { getContext, getAllContexts, hasContext, setContext } from './internal/client/context.js'; export { hydrate, mount, unmount } from './internal/client/render.js'; - -export { - getContext, - getAllContexts, - hasContext, - setContext, - tick, - untrack -} from './internal/client/runtime.js'; - +export { tick, untrack } from './internal/client/runtime.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js new file mode 100644 index 000000000000..1d7f0747cd72 --- /dev/null +++ b/packages/svelte/src/internal/client/context.js @@ -0,0 +1,214 @@ +/** @import { ComponentContext } from '#client' */ + +import { DEV } from 'esm-env'; +import { add_owner } from './dev/ownership.js'; +import { lifecycle_outside_component } from '../shared/errors.js'; +import { source } from './reactivity/sources.js'; +import { + active_effect, + active_reaction, + set_active_effect, + set_active_reaction +} from './runtime.js'; +import { effect } from './reactivity/effects.js'; +import { legacy_mode_flag } from '../flags/index.js'; + +/** @type {ComponentContext | null} */ +export let component_context = null; + +/** @param {ComponentContext | null} context */ +export function set_component_context(context) { + component_context = context; +} + +/** + * The current component function. Different from current component context: + * ```html + * + * + * + * + * ``` + * @type {ComponentContext['function']} + */ +export let dev_current_component_function = null; + +/** @param {ComponentContext['function']} fn */ +export function set_dev_current_component_function(fn) { + dev_current_component_function = fn; +} + +/** + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * + * @template T + * @param {any} key + * @returns {T} + */ +export function getContext(key) { + const context_map = get_or_init_context_map('getContext'); + const result = /** @type {T} */ (context_map.get(key)); + + if (DEV) { + const fn = /** @type {ComponentContext} */ (component_context).function; + if (fn) { + add_owner(result, fn, true); + } + } + + return result; +} + +/** + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component + * (including slotted content) with `getContext`. + * + * Like lifecycle functions, this must be called during component initialisation. + * + * @template T + * @param {any} key + * @param {T} context + * @returns {T} + */ +export function setContext(key, context) { + const context_map = get_or_init_context_map('setContext'); + context_map.set(key, context); + return context; +} + +/** + * Checks whether a given `key` has been set in the context of a parent component. + * Must be called during component initialisation. + * + * @param {any} key + * @returns {boolean} + */ +export function hasContext(key) { + const context_map = get_or_init_context_map('hasContext'); + return context_map.has(key); +} + +/** + * Retrieves the whole context map that belongs to the closest parent component. + * Must be called during component initialisation. Useful, for example, if you + * programmatically create a component and want to pass the existing context to it. + * + * @template {Map} [T=Map] + * @returns {T} + */ +export function getAllContexts() { + const context_map = get_or_init_context_map('getAllContexts'); + + if (DEV) { + const fn = component_context?.function; + if (fn) { + for (const value of context_map.values()) { + add_owner(value, fn, true); + } + } + } + + return /** @type {T} */ (context_map); +} + +/** + * @param {Record} props + * @param {any} runes + * @param {Function} [fn] + * @returns {void} + */ +export function push(props, runes = false, fn) { + component_context = { + p: component_context, + c: null, + e: null, + m: false, + s: props, + x: null, + l: null + }; + + if (legacy_mode_flag && !runes) { + component_context.l = { + s: null, + u: null, + r1: [], + r2: source(false) + }; + } + + if (DEV) { + // component function + component_context.function = fn; + dev_current_component_function = fn; + } +} + +/** + * @template {Record} T + * @param {T} [component] + * @returns {T} + */ +export function pop(component) { + const context_stack_item = component_context; + if (context_stack_item !== null) { + if (component !== undefined) { + context_stack_item.x = component; + } + const component_effects = context_stack_item.e; + if (component_effects !== null) { + var previous_effect = active_effect; + var previous_reaction = active_reaction; + context_stack_item.e = null; + try { + for (var i = 0; i < component_effects.length; i++) { + var component_effect = component_effects[i]; + set_active_effect(component_effect.effect); + set_active_reaction(component_effect.reaction); + effect(component_effect.fn); + } + } finally { + set_active_effect(previous_effect); + set_active_reaction(previous_reaction); + } + } + component_context = context_stack_item.p; + if (DEV) { + dev_current_component_function = context_stack_item.p?.function ?? null; + } + context_stack_item.m = true; + } + // Micro-optimization: Don't set .a above to the empty object + // so it can be garbage-collected when the return here is unused + return component || /** @type {T} */ ({}); +} + +/** + * @param {string} name + * @returns {Map} + */ +function get_or_init_context_map(name) { + if (component_context === null) { + lifecycle_outside_component(name); + } + + return (component_context.c ??= new Map(get_parent_context(component_context) || undefined)); +} + +/** + * @param {ComponentContext} component_context + * @returns {Map | null} + */ +function get_parent_context(component_context) { + let parent = component_context.p; + while (parent !== null) { + const context_map = parent.c; + if (context_map !== null) { + return context_map; + } + parent = parent.p; + } + return null; +} diff --git a/packages/svelte/src/internal/client/dev/legacy.js b/packages/svelte/src/internal/client/dev/legacy.js index a1d2fc88239e..138213c5517b 100644 --- a/packages/svelte/src/internal/client/dev/legacy.js +++ b/packages/svelte/src/internal/client/dev/legacy.js @@ -1,5 +1,5 @@ import * as e from '../errors.js'; -import { component_context } from '../runtime.js'; +import { component_context } from '../context.js'; import { FILENAME } from '../../../constants.js'; import { get_component } from './ownership.js'; diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index d113d9ae9018..70cfbb47f3b6 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -3,7 +3,7 @@ import { STATE_SYMBOL_METADATA } from '../constants.js'; import { render_effect, user_pre_effect } from '../reactivity/effects.js'; -import { dev_current_component_function } from '../runtime.js'; +import { dev_current_component_function } from '../context.js'; import { get_prototype_of } from '../../shared/utils.js'; import * as w from '../warnings.js'; import { FILENAME } from '../../../constants.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 62b2e4dd0cda..633a6e5bab87 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -3,18 +3,15 @@ import { DEV } from 'esm-env'; import { is_promise } from '../../../shared/utils.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; import { internal_set, mutable_source, source } from '../../reactivity/sources.js'; +import { flush_sync, is_runes, set_active_effect, set_active_reaction } from '../../runtime.js'; +import { hydrate_next, hydrate_node, hydrating } from '../hydration.js'; +import { queue_micro_task } from '../task.js'; +import { UNINITIALIZED } from '../../../../constants.js'; import { component_context, - flush_sync, - is_runes, - set_active_effect, - set_active_reaction, set_component_context, set_dev_current_component_function -} from '../../runtime.js'; -import { hydrate_next, hydrate_node, hydrating } from '../hydration.js'; -import { queue_micro_task } from '../task.js'; -import { UNINITIALIZED } from '../../../../constants.js'; +} from '../../context.js'; const PENDING = 0; const THEN = 1; diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 7f4f000dceae..c1ca7a960034 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,15 +1,14 @@ /** @import { Effect, TemplateNode, } from '#client' */ import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '../../constants.js'; +import { component_context, set_component_context } from '../../context.js'; import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import { active_effect, active_reaction, - component_context, handle_error, set_active_effect, set_active_reaction, - set_component_context, reset_is_throwing_error } from '../../runtime.js'; import { diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index 04ab0aee87f5..b3fc5a9c725d 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -7,7 +7,7 @@ import { assign_nodes } from '../template.js'; import * as w from '../../warnings.js'; import { hash, sanitize_location } from '../../../../utils.js'; import { DEV } from 'esm-env'; -import { dev_current_component_function } from '../../runtime.js'; +import { dev_current_component_function } from '../../context.js'; import { get_first_child, get_next_sibling } from '../operations.js'; /** diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index cec57f83b7ba..b916a02ce55f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -6,7 +6,7 @@ import { branch, block, destroy_effect, teardown } from '../../reactivity/effect import { dev_current_component_function, set_dev_current_component_function -} from '../../runtime.js'; +} from '../../context.js'; import { hydrate_next, hydrate_node, hydrating } from '../hydration.js'; import { create_fragment_from_html } from '../reconciler.js'; import { assign_nodes } from '../template.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index 35d2f223aed5..18641300e537 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -17,7 +17,8 @@ import { } from '../../reactivity/effects.js'; import { set_should_intro } from '../../render.js'; import { current_each_item, set_current_each_item } from './each.js'; -import { component_context, active_effect } from '../../runtime.js'; +import { active_effect } from '../../runtime.js'; +import { component_context } from '../../context.js'; import { DEV } from 'esm-env'; import { EFFECT_TRANSPARENT } from '../../constants.js'; import { assign_nodes } from '../template.js'; diff --git a/packages/svelte/src/internal/client/dom/legacy/lifecycle.js b/packages/svelte/src/internal/client/dom/legacy/lifecycle.js index 5ffbacc670a3..a564712f046c 100644 --- a/packages/svelte/src/internal/client/dom/legacy/lifecycle.js +++ b/packages/svelte/src/internal/client/dom/legacy/lifecycle.js @@ -1,8 +1,9 @@ /** @import { ComponentContextLegacy } from '#client' */ import { run, run_all } from '../../../shared/utils.js'; +import { component_context } from '../../context.js'; import { derived } from '../../reactivity/deriveds.js'; import { user_pre_effect, user_effect } from '../../reactivity/effects.js'; -import { component_context, deep_read_state, get, untrack } from '../../runtime.js'; +import { deep_read_state, get, untrack } from '../../runtime.js'; /** * Legacy-mode only: Call `onMount` callbacks and set up `beforeUpdate`/`afterUpdate` effects diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 2bf58c51f75d..877f3c59ee55 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -1,4 +1,5 @@ export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js'; +export { push, pop } from './context.js'; export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js'; export { cleanup_styles } from './dev/css.js'; export { add_locations } from './dev/elements.js'; @@ -141,14 +142,8 @@ export { update, update_pre, exclude_from_object, - pop, - push, deep_read, - deep_read_state, - getAllContexts, - getContext, - setContext, - hasContext + deep_read_state } from './runtime.js'; export { validate_binding, validate_each_keys } from './validate.js'; export { raf } from './timing.js'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 6cbd6394df3a..4c262880f1cd 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -1,6 +1,7 @@ -/** @import { ProxyMetadata, ProxyStateObject, Source } from '#client' */ +/** @import { ProxyMetadata, Source } from '#client' */ import { DEV } from 'esm-env'; -import { get, component_context, active_effect } from './runtime.js'; +import { get, active_effect } from './runtime.js'; +import { component_context } from './context.js'; import { array_prototype, get_descriptor, diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index da5a75eb6edb..e86963408ce7 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -17,8 +17,7 @@ import { skip_reaction, update_reaction, increment_write_version, - set_active_effect, - component_context + set_active_effect } from '../runtime.js'; import { equals, safe_equals } from './equality.js'; import * as e from '../errors.js'; @@ -26,6 +25,7 @@ import { destroy_effect } from './effects.js'; import { inspect_effects, set_inspect_effects } from './sources.js'; import { get_stack } from '../dev/tracing.js'; import { tracing_mode_flag } from '../../flags/index.js'; +import { component_context } from '../context.js'; /** * @template V diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 1faf9a47a080..8a7cffecd623 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -1,10 +1,8 @@ /** @import { ComponentContext, ComponentContextLegacy, Derived, Effect, TemplateNode, TransitionManager } from '#client' */ import { check_dirtiness, - component_context, active_effect, active_reaction, - dev_current_component_function, update_effect, get, is_destroying_effect, @@ -45,6 +43,7 @@ import { DEV } from 'esm-env'; import { define_property } from '../../shared/utils.js'; import { get_next_sibling } from '../dom/operations.js'; import { derived, destroy_derived } from './deriveds.js'; +import { component_context, dev_current_component_function } from '../context.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index c2448c9ee5fe..b3e66a333625 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -1,7 +1,6 @@ /** @import { Derived, Effect, Reaction, Source, Value } from '#client' */ import { DEV } from 'esm-env'; import { - component_context, active_reaction, active_effect, untracked_writes, @@ -35,6 +34,7 @@ import { import * as e from '../errors.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; import { get_stack } from '../dev/tracing.js'; +import { component_context } from '../context.js'; export let inspect_effects = new Set(); diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 767b23013169..bc74047f6497 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -9,7 +9,8 @@ import { init_operations } from './dom/operations.js'; import { HYDRATION_END, HYDRATION_ERROR, HYDRATION_START } from '../../constants.js'; -import { push, pop, component_context, active_effect } from './runtime.js'; +import { active_effect } from './runtime.js'; +import { push, pop, component_context } from './context.js'; import { component_root, branch } from './reactivity/effects.js'; import { hydrate_next, diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index a002665b51fa..859925186f59 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -5,7 +5,6 @@ import { destroy_block_effect_children, destroy_effect_children, destroy_effect_deriveds, - effect, execute_effect_teardown, unlink_effect } from './reactivity/effects.js'; @@ -28,14 +27,18 @@ import { BOUNDARY_EFFECT } from './constants.js'; import { flush_tasks } from './dom/task.js'; -import { add_owner } from './dev/ownership.js'; -import { internal_set, set, source } from './reactivity/sources.js'; +import { internal_set, set } from './reactivity/sources.js'; import { destroy_derived, execute_derived, update_derived } from './reactivity/deriveds.js'; import * as e from './errors.js'; -import { lifecycle_outside_component } from '../shared/errors.js'; import { FILENAME } from '../../constants.js'; import { legacy_mode_flag, tracing_mode_flag } from '../flags/index.js'; import { tracing_expressions, get_stack } from './dev/tracing.js'; +import { + component_context, + dev_current_component_function, + set_component_context, + set_dev_current_component_function +} from './context.js'; const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; @@ -150,32 +153,6 @@ export function set_captured_signals(value) { captured_signals = value; } -// Handling runtime component context -/** @type {ComponentContext | null} */ -export let component_context = null; - -/** @param {ComponentContext | null} context */ -export function set_component_context(context) { - component_context = context; -} - -/** - * The current component function. Different from current component context: - * ```html - * - * - * - * - * ``` - * @type {ComponentContext['function']} - */ -export let dev_current_component_function = null; - -/** @param {ComponentContext['function']} fn */ -export function set_dev_current_component_function(fn) { - dev_current_component_function = fn; -} - export function increment_write_version() { return ++write_version; } @@ -434,7 +411,7 @@ export function update_reaction(reaction) { active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0; derived_sources = null; - component_context = reaction.ctx; + set_component_context(reaction.ctx); untracking = false; read_version++; @@ -498,7 +475,7 @@ export function update_reaction(reaction) { active_reaction = previous_reaction; skip_reaction = previous_skip_reaction; derived_sources = prev_derived_sources; - component_context = previous_component_context; + set_component_context(previous_component_context); untracking = previous_untracking; } } @@ -578,7 +555,7 @@ export function update_effect(effect) { if (DEV) { var previous_component_fn = dev_current_component_function; - dev_current_component_function = effect.component_function; + set_dev_current_component_function(effect.component_function); } try { @@ -620,7 +597,7 @@ export function update_effect(effect) { active_effect = previous_effect; if (DEV) { - dev_current_component_function = previous_component_fn; + set_dev_current_component_function(previous_component_fn); } } } @@ -1114,109 +1091,6 @@ export function set_signal_status(signal, status) { signal.f = (signal.f & STATUS_MASK) | status; } -/** - * Retrieves the context that belongs to the closest parent component with the specified `key`. - * Must be called during component initialisation. - * - * @template T - * @param {any} key - * @returns {T} - */ -export function getContext(key) { - const context_map = get_or_init_context_map('getContext'); - const result = /** @type {T} */ (context_map.get(key)); - - if (DEV) { - const fn = /** @type {ComponentContext} */ (component_context).function; - if (fn) { - add_owner(result, fn, true); - } - } - - return result; -} - -/** - * Associates an arbitrary `context` object with the current component and the specified `key` - * and returns that object. The context is then available to children of the component - * (including slotted content) with `getContext`. - * - * Like lifecycle functions, this must be called during component initialisation. - * - * @template T - * @param {any} key - * @param {T} context - * @returns {T} - */ -export function setContext(key, context) { - const context_map = get_or_init_context_map('setContext'); - context_map.set(key, context); - return context; -} - -/** - * Checks whether a given `key` has been set in the context of a parent component. - * Must be called during component initialisation. - * - * @param {any} key - * @returns {boolean} - */ -export function hasContext(key) { - const context_map = get_or_init_context_map('hasContext'); - return context_map.has(key); -} - -/** - * Retrieves the whole context map that belongs to the closest parent component. - * Must be called during component initialisation. Useful, for example, if you - * programmatically create a component and want to pass the existing context to it. - * - * @template {Map} [T=Map] - * @returns {T} - */ -export function getAllContexts() { - const context_map = get_or_init_context_map('getAllContexts'); - - if (DEV) { - const fn = component_context?.function; - if (fn) { - for (const value of context_map.values()) { - add_owner(value, fn, true); - } - } - } - - return /** @type {T} */ (context_map); -} - -/** - * @param {string} name - * @returns {Map} - */ -function get_or_init_context_map(name) { - if (component_context === null) { - lifecycle_outside_component(name); - } - - return (component_context.c ??= new Map(get_parent_context(component_context) || undefined)); -} - -/** - * @param {ComponentContext} component_context - * @returns {Map | null} - */ -function get_parent_context(component_context) { - let parent = component_context.p; - while (parent !== null) { - const context_map = parent.c; - if (context_map !== null) { - return context_map; - } - parent = parent.p; - } - return null; -} - /** * @template {number | bigint} T * @param {Value} signal @@ -1264,78 +1138,6 @@ export function exclude_from_object(obj, keys) { return result; } -/** - * @param {Record} props - * @param {any} runes - * @param {Function} [fn] - * @returns {void} - */ -export function push(props, runes = false, fn) { - component_context = { - p: component_context, - c: null, - e: null, - m: false, - s: props, - x: null, - l: null - }; - - if (legacy_mode_flag && !runes) { - component_context.l = { - s: null, - u: null, - r1: [], - r2: source(false) - }; - } - - if (DEV) { - // component function - component_context.function = fn; - dev_current_component_function = fn; - } -} - -/** - * @template {Record} T - * @param {T} [component] - * @returns {T} - */ -export function pop(component) { - const context_stack_item = component_context; - if (context_stack_item !== null) { - if (component !== undefined) { - context_stack_item.x = component; - } - const component_effects = context_stack_item.e; - if (component_effects !== null) { - var previous_effect = active_effect; - var previous_reaction = active_reaction; - context_stack_item.e = null; - try { - for (var i = 0; i < component_effects.length; i++) { - var component_effect = component_effects[i]; - set_active_effect(component_effect.effect); - set_active_reaction(component_effect.reaction); - effect(component_effect.fn); - } - } finally { - set_active_effect(previous_effect); - set_active_reaction(previous_reaction); - } - } - component_context = context_stack_item.p; - if (DEV) { - dev_current_component_function = context_stack_item.p?.function ?? null; - } - context_stack_item.m = true; - } - // Micro-optimization: Don't set .a above to the empty object - // so it can be garbage-collected when the return here is unused - return component || /** @type {T} */ ({}); -} - /** * Possibly traverse an object and read all its properties so that they're all reactive in case this is `$state`. * Does only check first level of an object for performance reasons (heuristic should be good for 99% of all cases). diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 24e280edf88b..ec3d80544787 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -1,4 +1,4 @@ -import { dev_current_component_function } from './runtime.js'; +import { dev_current_component_function } from './context.js'; import { is_array } from '../shared/utils.js'; import * as e from './errors.js'; import { FILENAME } from '../../constants.js'; diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index 3715617f4c5a..3a05bc04963f 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -3,19 +3,13 @@ import { DIRTY, LEGACY_PROPS, MAYBE_DIRTY } from '../internal/client/constants.j import { user_pre_effect } from '../internal/client/reactivity/effects.js'; import { mutable_source, set } from '../internal/client/reactivity/sources.js'; import { hydrate, mount, unmount } from '../internal/client/render.js'; -import { - active_effect, - component_context, - dev_current_component_function, - flush_sync, - get, - set_signal_status -} from '../internal/client/runtime.js'; +import { active_effect, flush_sync, get, set_signal_status } from '../internal/client/runtime.js'; import { lifecycle_outside_component } from '../internal/shared/errors.js'; import { define_property, is_array } from '../internal/shared/utils.js'; import * as w from '../internal/client/warnings.js'; import { DEV } from 'esm-env'; import { FILENAME } from '../constants.js'; +import { component_context, dev_current_component_function } from '../internal/client/context.js'; /** * Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component. diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 5f0b93e1360d..aa1dbf31328c 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1,6 +1,7 @@ import { describe, assert, it } from 'vitest'; import { flushSync } from '../../src/index-client'; import * as $ from '../../src/internal/client/runtime'; +import { push, pop } from '../../src/internal/client/context'; import { effect, effect_root, @@ -22,13 +23,13 @@ import { SvelteSet } from '../../src/reactivity/set'; function run_test(runes: boolean, fn: (runes: boolean) => () => void) { return () => { // Create a component context to test runes vs legacy mode - $.push({}, runes); + push({}, runes); // Create a render context so that effect validations etc don't fail let execute: any; const destroy = effect_root(() => { execute = fn(runes); }); - $.pop(); + pop(); execute(); destroy(); }; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index d00b2b01ed18..eb3e93e4b5df 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -421,6 +421,34 @@ declare module 'svelte' { }): Snippet; /** Anything except a function */ type NotFunction = T extends Function ? never : T; + /** + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * + * */ + export function getContext(key: any): T; + /** + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component + * (including slotted content) with `getContext`. + * + * Like lifecycle functions, this must be called during component initialisation. + * + * */ + export function setContext(key: any, context: T): T; + /** + * Checks whether a given `key` has been set in the context of a parent component. + * Must be called during component initialisation. + * + * */ + export function hasContext(key: any): boolean; + /** + * Retrieves the whole context map that belongs to the closest parent component. + * Must be called during component initialisation. Useful, for example, if you + * programmatically create a component and want to pass the existing context to it. + * + * */ + export function getAllContexts = Map>(): T; /** * Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component. * Transitions will play during the initial render unless the `intro` option is set to `false`. @@ -484,34 +512,6 @@ declare module 'svelte' { * ``` * */ export function untrack(fn: () => T): T; - /** - * Retrieves the context that belongs to the closest parent component with the specified `key`. - * Must be called during component initialisation. - * - * */ - export function getContext(key: any): T; - /** - * Associates an arbitrary `context` object with the current component and the specified `key` - * and returns that object. The context is then available to children of the component - * (including slotted content) with `getContext`. - * - * Like lifecycle functions, this must be called during component initialisation. - * - * */ - export function setContext(key: any, context: T): T; - /** - * Checks whether a given `key` has been set in the context of a parent component. - * Must be called during component initialisation. - * - * */ - export function hasContext(key: any): boolean; - /** - * Retrieves the whole context map that belongs to the closest parent component. - * Must be called during component initialisation. Useful, for example, if you - * programmatically create a component and want to pass the existing context to it. - * - * */ - export function getAllContexts = Map>(): T; type Getters = { [K in keyof T]: () => T[K]; };