mirror of https://github.com/sveltejs/svelte
chore: move context code into new module (#15132)
parent
b2c8224a73
commit
5e9b29c351
@ -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
|
||||||
|
* <!-- App.svelte -->
|
||||||
|
* <Foo>
|
||||||
|
* <Bar /> <!-- context == Foo.svelte, function == App.svelte -->
|
||||||
|
* </Foo>
|
||||||
|
* ```
|
||||||
|
* @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<any, any>} [T=Map<any, any>]
|
||||||
|
* @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<string, unknown>} 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<string, any>} 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<unknown, unknown>}
|
||||||
|
*/
|
||||||
|
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<unknown, unknown> | 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;
|
||||||
|
}
|
Loading…
Reference in new issue