State Reactivity & Yieldables
Relevant source files
The following files were used as context for generating this wiki page:
- modules/svelte-effect-runtime/src/script-transform/runes.ts
- modules/svelte-effect-runtime/src/script-transform/lower.ts
- modules/svelte-effect-runtime/src/script-transform/index.ts
- modules/svelte-effect-runtime/src/markup/transform/scan.ts
- modules/svelte-effect-runtime/src/markup/transform/index.ts
- skills/svelte-effect-runtime/references/syntax.md
- modules/svelte-effect-runtime/src/diagnostics.ts
State Reactivity and Yieldables represent the core mechanism by which Svelte Effect Runtime (SER) integrates the Effect ecosystem's asynchronous, effectful generator functions with Svelte 5's fine-grained reactivity system. This system allows developers to use the yield* operator directly within Svelte scripts and markup, which the SER compiler then lowers into reactive Svelte $state bindings and tracked lifecycle management.
By using yield*, components can perform asynchronous operations—such as fetching data or executing remote commands—while maintaining the declarative nature of Svelte. The runtime ensures that these effects are automatically cancelled when components unmount or when their reactive dependencies change, preventing memory leaks and stale data updates.
Reactivity Integration with Runes
SER integrates with Svelte 5 runes by validating and transforming yield* expressions into code that interacts safely with Svelte's reactive state. The compiler specifically targets runes that support asynchronous execution or state initialization.
Supported and Restricted Runes
The transformation logic distinguishes between runes that can handle async/effectful work and those that must remain synchronous.
| Rune Category | Runes | Behavior with yield* |
|---|---|---|
| Async-Compatible | $derived, $state, $state.raw, $state.snapshot, $bindable | Allowed; yield* is lowered into reactive bindings. |
| Callback Restricted | $derived.by, $effect, $effect.pre, $effect.root | Forbidden; top-level yield* in these callbacks throws an AsyncEffectInSyncRuneError. |
Sources: modules/svelte-effect-runtime/src/script-transform/runes.ts:10-23, skills/svelte-effect-runtime/references/syntax.md:30-38
Validation Logic
The compiler performs a recursive AST walk to ensure yield* is not placed in invalid synchronous contexts.
Yieldable Transformation & Lowering
The "Yieldable" concept refers to the wrapping of Effect programs so they can be executed by the SER dispatcher. The lowering process converts high-level yield* syntax into generated runtime calls.
The Lowering Process
When the compiler encounters a yield* in a script or markup, it performs several operations:
- Identifier Allocation: Generates collision-free names for temporary state variables (e.g.,
__SER___temp_0). - State Declaration: Injects
let name = $state(undefined)at the top level of the script. - Wrapping: Wraps the effect operand in a
yieldablehelper (often aliased toToEffect). - Dispatcher Wiring: Wires the effect into the runtime dispatcher, which manages the execution fiber.
Sources: modules/svelte-effect-runtime/src/script-transform/index.ts:114-142, modules/svelte-effect-runtime/src/script-transform/lower.ts:251-253
Script Transformation Flow
The transformation of a <script effect> block follows a specific sequence to ensure all reactive bindings are correctly established.
Markup Reactivity
Yieldables in markup allow for direct asynchronous rendering within Svelte templates. The runtime handles the transition from "pending" to "resolved" state automatically.
Markup Contexts
The compiler supports yield* in several markup positions, each mapped to a TagKind:
- Plain: Standard interpolation
{yield* ...}. - Each/Await: Iteration and promise blocks
{#each yield* ...}. - Event: Direct event handlers
onclick={yield* ...}. - Declarations: Inside markup constants
{const x = yield* ...}.
Sources: modules/svelte-effect-runtime/src/markup/transform/types.ts:109-110, skills/svelte-effect-runtime/references/syntax.md:42-60
Sanitization and Classification
Because Svelte's parser does not natively understand yield* in markup, SER uses a two-step process:
- Sanitization: Replaces
yield*expressions with valid JS placeholders to allow Svelte to parse the AST. - Classification: Matches placeholders back to their AST context (e.g., determining if a placeholder is inside an
onclickattribute).
Sources: modules/svelte-effect-runtime/src/markup/transform/index.ts:54-75, modules/svelte-effect-runtime/src/markup/transform/scan.ts:40-75
Diagnostics and Safety
SER provides diagnostics to prevent common pitfalls where reactivity might be "hidden" or broken by incorrect yield* usage.
Common Warnings
- Hidden Event Yield: Warning when
yield*is inside a callback likeonclick={() => yield* save()}. SER requires the yield at the boundary:onclick={yield* save()}. - Explicit Runner: Warning when
Effect.runSyncor similar is used in markup, as this bypasses SER's reactive lifecycle and cancellation. - Bare Effect.gen: Warning when
Effect.genis passed as a value without being yielded.
Sources: modules/svelte-effect-runtime/src/diagnostics.ts:103-160, skills/svelte-effect-runtime/references/syntax.md:72-82
Relocation Mapping
To maintain editor features like "Go to Definition" or "Hover" on the original source code despite the heavy transformation, SER generates Relocations. These map the generated code ranges back to the original yield* operand offsets.
Sources: modules/svelte-effect-runtime/src/markup/transform/types.ts:63-71, modules/svelte-effect-runtime/src/script-transform/index.ts:187-215
Summary
State Reactivity and Yieldables enable a seamless developer experience where Effect-based logic behaves like native Svelte state. By automating the lowering of yield* into reactive $state and managing fibers via a centralized dispatcher, the system ensures that complex asynchronous flows remain predictable, cancelable, and fully integrated with the Svelte 5 component lifecycle.