This document provides an overview of the signal-based reactivity system implemented in this project. This system allows for efficient and fine-grained updates in response to data changes. It is inspired by reactive programming paradigms and utilizes key concepts like signals, derived values, and effects.
The reactivity system revolves around three main concepts:
-
Signals (Sources): These are the fundamental building blocks of the system. A signal holds a value that can change over time. When a signal's value is updated, it notifies all the parts of the system that depend on it.
-
Derived Values (Deriveds): These are signals whose values are automatically computed based on the values of other signals. When the dependencies of a derived value change, its value is automatically recomputed.
-
Effects: These are functions that run in response to changes in signals. They are used to perform side effects, such as updating the DOM, logging data, or making API calls, whenever the signals they depend on change.
This reactivity system is defined by the following files:
This file contains the logic for creating and managing derived signals. Derived signals are read-only signals whose values are derived from other signals.
Key Functions:
derived(fn): Creates a new derived signal. The provided functionfnis executed to compute the derived value. This function automatically tracks any other signals accessed within it. When any of these dependency signals change, thefnwill be re-executed, and the derived signal's value will be updated.- It handles scenarios where derived signals are created within other reactions (deriveds or effects) to establish ownership and dependency trees.
- It uses flags to track the state of the derived signal (e.g.,
DIRTY,UNOWNED).
user_derived(fn): Similar toderived, but it also pushes the newly created derived signal onto a runtime stack. This is likely used for specific lifecycle management or initial value tracking, especially in the context of components.derived_safe_equal(fn): Creates a derived signal that uses a "safe equality" check instead of strict equality to determine if the derived value has changed. This is useful when comparing complex objects where reference equality might not be sufficient.destroy_derived_effects(derived): When a derived signal is no longer needed, this function cleans up any effects that were created inside the derived's computation function. This helps prevent memory leaks and ensures proper resource management.update_derived(derived): This internal function is responsible for re-evaluating the derived signal's computation function. It handles dependency tracking, checks if the new value is different from the old one, updates the derived signal's value and write version, and marks any dependent reactions as needing updates.- It also includes logic to detect and prevent infinite recursion in derived computations.
This file defines how effects are created and managed. Effects are used to perform side effects in response to signal changes.
Key Functions:
user_effect(fn): Creates a new effect. The provided functionfnis executed initially and whenever any of the signals it depends on change.- It includes validation to ensure effects are created within a valid reactive context (e.g., not orphaned or during teardown).
- It handles deferring the execution of effects within certain contexts (like component mounting).
user_pre_effect(fn): Creates an effect that is intended to run synchronously before other effects, often used for setup or tasks that need to happen immediately.inspect_effect(fn): Creates a special effect used for debugging and introspection of the reactivity system. These effects run immediately when their dependencies change.effect_root(fn): Creates a root effect. Any effects created within the provided functionfnare considered children of this root effect. When the root effect is destroyed, all its child effects are also destroyed, providing a mechanism for managing the lifecycle of related effects.component_root(fn): Similar toeffect_root, but specifically designed for managing effects within a component. It includes functionality for handling outro transitions before destroying child effects.effect(fn): A lower-level function for creating effects with specific flags and configurations.legacy_pre_effect(deps, fn)andlegacy_pre_effect_reset(): These functions seem to be related to a previous or legacy reactivity system and are likely kept for compatibility or specific use cases.render_effect(fn): Creates an effect that is specifically intended for rendering updates. These effects typically run synchronously.template_effect(fn, thunks, d = derived): Creates an effect that depends on a set of derived values (represented bythunks). This is likely used in the context of rendering dynamic content in templates.block(fn, flags = 0): Creates a block effect, which might have specific characteristics or optimizations related to rendering blocks of content.branch(fn, push = true): Creates a branch effect, likely used for conditional rendering logic.execute_effect_teardown(effect): If an effect has a teardown function (a function returned by the main effect function), this function executes it when the effect is being destroyed. This is used for cleanup tasks like removing event listeners.destroy_effect_children(signal, remove_dom = false): Recursively destroys all child effects of a given effect. It can optionally remove associated DOM nodes.destroy_block_effect_children(signal): Destroys child effects of a block effect, potentially excluding certain types of effects (like branch effects).destroy_effect(effect, remove_dom = true): Destroys a specific effect. This involves executing its teardown function, destroying its child effects, removing associated DOM nodes (if applicable), and unlinking it from the reactive graph.unlink_effect(effect): Detaches an effect from its parent in the effect tree.pause_effect(effect, callback)andresume_effect(effect): These functions are used to temporarily pause and resume the execution of an effect and its children, often used in conjunction with animations or transitions.
This file defines the fundamental source signals, which hold mutable values and trigger updates when their values change.
Key Functions:
source(v, stack): Creates a new source signal with an initial valuev. This is the most basic type of signal that directly holds a mutable value.- In development mode with tracing enabled, it can capture the creation stack for debugging purposes.
state(v, stack): Similar tosource, but it also pushes the newly created signal onto a runtime stack. This is likely the primary way to create mutable state within components or the application.mutable_source(initial_value, immutable = false): Creates a source signal that is intended to be mutated. Theimmutableflag can be used to control whether the signal uses strict equality checks for updates.- It also handles binding the signal to the component context in legacy mode for lifecycle callbacks.
mutate(source, value): Provides a way to update a source signal's value by applying a mutation to its current value, while ensuring that the update triggers reactivity.set(source, value, should_proxy = false): Updates the value of a source signal to the providedvalue.- It includes checks to prevent unsafe mutations of state within derived computations or block effects in runes mode.
- It uses an equality check to avoid triggering updates if the new value is the same as the old one (using the signal's
equalsmethod). - It increments a write version counter to track changes.
- It marks all dependent reactions (deriveds and effects) as dirty.
- It handles immediate updates for inspect effects.
internal_set(source, value): A lower-level function that performs the actual setting of the source signal's value and handles the update logic.update(source, d = 1)andupdate_pre(source, d = 1): Convenience functions for incrementing or decrementing the numerical value of a source signal.mark_reactions(signal, status): This internal function iterates through all the reactions (deriveds and effects) that depend on the givensignaland marks them with the specifiedstatus(e.g.,DIRTYorMAYBE_DIRTY), indicating that they need to be re-evaluated or re-run.
This file deals with how component properties (props) are handled within the reactivity system, allowing data to flow into components and be reactive.
Key Functions:
prop(props, key, flags, fallback): This is the core function for defining a component property.- It takes the component's props object, the key of the property, flags indicating its behavior (e.g.,
PROPS_IS_IMMUTABLE,PROPS_IS_BINDABLE,PROPS_IS_RUNES,PROPS_IS_UPDATED), and an optional fallback value. - It handles cases where the prop might be bound (using
bind:syntax), in which case it captures the store binding. - It provides a getter function to access the current value of the prop.
- If the prop is marked as
PROPS_IS_UPDATED, it returns a setter function that allows the component to update the prop's value, which will then propagate back to the parent component if it's bound. - It handles synchronization between the parent and child component's prop values, including logic for local temporary overrides.
- It also manages fallback values when the prop is not provided by the parent.
- It takes the component's props object, the key of the property, flags indicating its behavior (e.g.,
rest_props(props, exclude, name)andlegacy_rest_props(props, exclude): These functions handle the collection of "rest" props (props passed to a component that are not explicitly declared). They return a proxy that allows accessing these props. The legacy version has additional logic for handling updates and deletions.spread_props(...props): This function takes an array of prop objects (or functions that return prop objects) and creates a proxy that merges them into a single props object. This is used for the spread syntax in templates.update_prop(fn, d = 1)andupdate_pre_prop(fn, d = 1): These utility functions are similar to theupdatefunctions insources.jsbut are specifically designed for updating prop values.
Here are some basic examples to illustrate how to use this reactivity system:
Creating a Source Signal:
import { state } from './sources.js';
const count = state(0); // Creates a signal with an initial value of 0
console.log(count.v); // Access the current value (0)
Updating a Source Signal:
import { set } from './sources.js';
import { count } from './examples'; // Assuming the above example
set(count, 1); // Updates the value of the count signal to 1
console.log(count.v); // Output: 1
Creating a Derived Signal:
import { derived } from './deriveds.js';
import { count } from './examples'; // Assuming the above example
const doubleCount = derived(() => count.v * 2);
console.log(doubleCount.v); // Output: 2 (based on the current count value of 1)
Creating an Effect:
import { user_effect } from './effects.js';
import { count } from './examples'; // Assuming the above example
user_effect(() => {
console.log(`Count is: ${count.v}`); // This will log "Count is: 1" when count changes
});
// When count is updated:
import { set } from './sources.js';
set(count, 2); // This will trigger the effect, logging "Count is: 2"
Component Property:
// In a component's code:
import { prop } from './props.js';
export function MyComponent(props) {
const name = prop(props, 'name');
// Access the prop value:
console.log(name());
}
// When using the component:
<MyComponent name="Alice" />;
Key Features and Principles
* Fine-Grained Dependency Tracking: The system automatically tracks which signals are used within derived computations and effect functions. This ensures that updates are only triggered when the relevant dependencies change.
* Lazy Evaluation: Derived signals are typically evaluated lazily, meaning their values are only computed when they are actually needed by other parts of the system (like effects or when their value is explicitly accessed).
* Efficient Updates: When a source signal changes, only the derived signals and effects that directly depend on it are re-evaluated or re-run, leading to efficient and minimal updates.
* Reactive Context Management: The system uses a concept of an "active reaction" and "active effect" to manage the context in which derived signals and effects are created, ensuring proper dependency tracking and ownership.
* Lifecycle Management: Features like effect roots and component roots provide mechanisms for managing the lifecycle of effects and ensuring they are properly cleaned up when they are no longer needed.
This documentation provides a foundational understanding of the signal-based reactivity system. For more detailed information on specific functions or advanced use cases, please refer to the source code and any additional documentation.
Gemini 2.5 Pro
Svelte 5 Reactivity System: Developer Documentation
Introduction
This document explains the internal implementation and core concepts of Svelte 5's new reactivity system (commonly known as Runes), targeted towards Svelte core developers. Unlike the compiler-based reactivity of Svelte 4 and earlier, Svelte 5 employs a system based on more explicit and fine-grained reactivity primitives: Signals, Derived Signals, and Effects. This aims to improve runtime performance, code understandability, and enable more powerful features.
The provided codebase represents the core components of this new reactivity system.
Core Concepts
Svelte 5's reactivity is built upon three primary concepts:
source()function (internal) or thestate()rune (public API).derived()function in the code.effect()function oruser_effect()(corresponding to the$effectrune) in the code.These primitives form dependency relationships, constructing a reactive graph.
Signal (Source)
A Signal is the atomic state container forming the foundation of the reactive system.
source(initialValue)creates a basic Signal object. It includes properties like:v: The current value.equals: A function to compare value equality (defaults to===). Used to detect changes.reactions: A list of Derived Signals or Effects that depend on this Signal. They need to be notified when the value changes.wv(Write Version): The global write version when the Signal was last updated.rv(Read Version): The read version of the reaction that last read this Signal.f: Bit flags indicating the Signal's state and characteristics.state(initialValue): A higher-level API (rune) that internally callssource()and automatically registers it as a dependency of the current derived computation (if any) (push_derived_source).get(signal)function to retrieve the value.getregisters the Signal as a dependency of the currently active reaction (Derived or Effect), if one exists.setfunction is in the code example) This is done by updating the Signal'svproperty and updatingwvusingincrement_write_version(). This marks dependent reactions as dirty, scheduling them for recalculation or re-execution.Derived Signal
A Derived Signal is a computed value derived from other Signals.
derived(computeFn)creates a Derived Signal object.fn: The function to compute the value.deps: A list of Signals this Derived Signal depends on.effects: A list of Effects created within this Derived Signal (for nested reactivity).parent: The scope (Effect or another Derived Signal) where this Derived Signal was created.f: State flags. TheDERIVEDflag is always set.get()is called for the first time (or the first time after a dependency has changed). The result is cached in thevproperty and won't be recalculated unless a dependency changes.get()is called during the execution ofcomputeFn, the read Signal is automatically added todeps(logic withinupdate_reaction).CLEAN: The value is up-to-date, and dependencies haven't changed.DIRTY: The value is stale and needs recalculation the next timeget()is called. Set when a dependent Source is directly changed.MAYBE_DIRTY: A state indicating that one of the dependent Derived Signals might beMAYBE_DIRTYorDIRTY. Whether recalculation is actually needed is determined by recursively checking dependencies incheck_dirtiness. This is an optimization to avoid unnecessary recalculations.UNOWNEDFlag: Typically, a Derived Signal is "owned" by the Effect (or component) that created it.UNOWNEDindicates a Derived Signal that doesn't have a specific owner (Effect). This is relevant mainly when created outside an Effect scope or used at the top level. Unowned Derived Signals might be automatically cleaned up when no longer referenced (similar behavior to garbage collection).DISCONNECTEDFlag: Indicates that anUNOWNEDDerived Signal is temporarily not referenced by any reaction. It needs to reconnect its dependencies if referenced again (withincheck_dirtiness).Effect
Effects are responsible for executing side effects in response to Signal changes.
effect(fn): Creates a standard Effect. Its execution is scheduled asynchronously.user_effect(fn): Called when using the$effectrune within a Svelte component. Similar toeffect(), but execution timing might be adjusted based on the component's mount state (deferlogic).effect_root(fn): Creates the root of an independent reactivity tree. Often used to manage reactivity for the entire application or specific isolated UI sections. Executes synchronously for the first time.get()'d during the execution offnare registered as dependencies.DIRTYorMAYBE_DIRTYand added to an execution queue (schedule_effect).queueMicrotask) (flush_queued_root_effects). This allows batching multiple changes and skipping unnecessary intermediate state calculations or side effects.flush_queued_effectsprocesses the queued Effects, checks if they are dirty usingcheck_dirtiness, and then callsupdate_effectto re-run them.fncan optionally return a cleanup function. This function is called before the Effect re-runs or when the Effect is destroyed (execute_effect_teardown). Used for tasks like removing event listeners or clearing timers.create_effectand linked to its parent Effect.fnis executed (synchronously foreffect_root, asynchronously foreffect), and dependencies are collected.update_effect(after running the cleanup function, if any).destroy_effectis called, the cleanup function runs, the Effect removes itself from dependencies (remove_reactions), unlinks itself from the Effect tree (unlink_effect), and releases associated resources (like DOM nodes).EFFECT: A regular Effect.ROOT_EFFECT: The root of a reactivity tree. The starting point for scheduling.BLOCK_EFFECT,BRANCH_EFFECT: Special Effects generated by the Svelte compiler, primarily managing the lifecycle and DOM operations for block elements like{#if}and{#each}.BOUNDARY_EFFECT: An Effect defining a boundary for error handling. Catches errors from child Effects (handle_error,propagate_error).Reactivity Execution Model
get):get(signal)is called, it first checks if there's a current reaction (active_reaction) and if tracking is active (!untracking).depsornew_deps). Versions (rv,read_version) help efficiently check if it's already registered within the same reaction.check_dirtinessis called to ensure its value is current. If dirty,update_derivedrecalculates it.signal.v).set):sourcevalue directly)signal.vwith the new value.signal.wvusingincrement_write_version().signal.reactionsas dirty (set_signal_status(reaction, DIRTY)orMAYBE_DIRTY).schedule_effectto schedule their execution.flush_queued_root_effectsis called.process_effectsstarts from the scheduled root Effects and collects dirty Effects. During this,check_dirtinessforMAYBE_DIRTYDerived Signals is called, potentially triggering recalculations viaupdate_derived.flush_queued_effectsloops through the collected dirty Effects, performs a final check withcheck_dirtiness, and callsupdate_effectto re-run the Effect function (fn).update_reaction(Core logic shared by Derived and Effect):active_reaction), etc.fn. During this execution,getcalls collect new dependencies intonew_deps.deps) with new ones (new_deps), removes the reaction from dependencies that are no longer needed (remove_reactions), and adds the reaction to new dependencies.fnintoteardown.handle_erroris called. The error propagates up the parent Effect tree viapropagate_errorto the nearest ancestor Effect with theBOUNDARY_EFFECTflag. If not handled there, it's thrown globally.destroy_effectrecursively destroys an Effect and its descendants. This includes running cleanup functions, removing dependencies, unlinking from the tree, and removing DOM nodes (if applicable).Flags
The
fproperty on each Signal and Effect object is a set of bit flags used to efficiently manage the object's state and type. Key flags include:DERIVED,EFFECT,BLOCK_EFFECT,BRANCH_EFFECT,ROOT_EFFECT,HEAD_EFFECTCLEAN,DIRTY,MAYBE_DIRTYUNOWNED,DISCONNECTEDDESTROYED,INERT,EFFECT_RANBOUNDARY_EFFECT(error handling),EFFECT_HAS_DERIVED(optimization)These flags are efficiently set, unset, and checked using bitwise operations (
|,&,^) (e.g.,set_signal_status).Context and Ownership
active_reaction/active_effect: Global (module-scoped) variables tracking the currently executing reaction (Derived or Effect) or Effect. Essential for registering dependencies correctly whengetis called.parent,first,last,next,prev). This allows hierarchical management of scheduling (ROOT_EFFECT), error handling (BOUNDARY_EFFECT), and cleanup (destroy_effect_children).component_context: Holds the Svelte component instance associated with an Effect or Derived Signal. This links component-specific behavior (like lifecycle) with the reactivity system.UNOWNED: Derived Signals that don't belong to the Effect tree. Their lifecycle tends to be managed by a mechanism resembling reference counting.Optimizations
MAYBE_DIRTY: When a Signal changes, direct dependents becomeDIRTY, but further dependents becomeMAYBE_DIRTY. This delays expensive recalculations untilcheck_dirtinessconfirms if the value actually changed.wv,rv,read_version,write_version): Tracking Signal reads and writes with version numbers streamlinescheck_dirtinessand dependency updates.skip_reaction: A flag to temporarily skip dependency tracking and updates under specific circumstances (e.g., an Unowned Derived read outside an Effect scope).untracking: The internal flag/function corresponding to theuntrack()API. Prevents dependency registration forgetcalls within a specified function's execution.This document provides a high-level overview of the concepts and internal workings of the Svelte 5 reactivity system as inferred from the provided codebase. The actual system involves more details and edge cases, but this explanation should aid in understanding the core mechanics.