Effectful Event Handlers
Relevant source files
The following files were used as context for generating this wiki page:
- skills/svelte-effect-runtime/references/syntax.md
- modules/svelte-effect-runtime/src/markup/transform/emit.ts
- modules/svelte-effect-runtime/src/diagnostics.ts
- modules/svelte-effect-runtime/src/markup/transform/scan.ts
- modules/svelte-effect-runtime/src/markup/transform/effect-callbacks.ts
- modules/svelte-effect-runtime/src/compiler/source-scan.ts
Effectful event handlers in Svelte Effect Runtime (SER) allow Svelte components to execute Effect programs directly in response to DOM events. By using a specific yield* syntax within event attributes, the runtime can manage the lifecycle, cancellation, and execution of these effects through a generated dispatcher.
The system relies on a compile-time transform that scans Svelte markup for specific patterns, validates them against SER rules, and rewrites them into reactive dispatcher calls. This ensures that effects triggered by UI interactions are correctly integrated into the Svelte 5 reactive system.
Sources: skills/svelte-effect-runtime/references/syntax.md:44-50, modules/svelte-effect-runtime/src/markup/transform/emit.ts:133-145
Syntax and Usage
Effectful event handlers must use a deliberate "yield-first" syntax. This involves prefixing the expression inside an event attribute (like onclick or oninput) with yield*. SER supports both Svelte 5 attributes and legacy on:click directives.
| Form | Example |
|---|---|
| Svelte 5 Attribute | <button onclick={yield* save(id)}>Save</button> |
| Input Event | <input oninput={yield* validate(event.currentTarget.value)} /> |
| Legacy Directive | <button on:click={yield* save(id)}>Legacy</button> |
Sources: skills/svelte-effect-runtime/references/syntax.md:46-51
Syntax Constraints and Validation
SER enforces strict rules regarding where yield* can appear in an event handler to ensure the transform can correctly lower the code:
- No Hidden Yields:
yield*must be at the event attribute boundary. Placing it inside a callback function (e.g.,onclick={() => yield* save()}) is invalid. - Direct Expression: The handler should be a direct expression that returns an Effect. SER automatically generates the
eventparameter for the resulting closure.
Sources: skills/svelte-effect-runtime/references/syntax.md:53-56, modules/svelte-effect-runtime/src/diagnostics.ts:110-120
Architecture and Transformation Flow
The transformation of an effectful event handler involves scanning the markup, classifying the attribute, and emitting a rewritten JavaScript closure.
The diagram shows the sequence from raw Svelte source to the final transformed output where effects are wrapped in dispatcher calls.
Sources: modules/svelte-effect-runtime/src/markup/transform/index.ts:54-95, modules/svelte-effect-runtime/src/markup/transform/scan.ts:46-60
The Transformation Process
- Scanning: The
scan_svelte_effect_sourcefunction identifies markup brace expressions and checks if the attribute name starts with "on". - Sanitization:
sanitize_markupidentifies expressions starting withyield*. It creates a placeholder for the expression to prevent the Svelte compiler from failing on the non-standard syntax. - Classification: The system determines if the candidate is in an "event" context based on the Svelte AST.
- Emission: The
make_event_handlerfunction generates a new closure. It wraps the original expression in adispatcher.emitcall using theMarkup.Runcode.
Sources: modules/svelte-effect-runtime/src/markup/transform/emit.ts:133-145, modules/svelte-effect-runtime/src/markup/transform/scan.ts:88-100, modules/svelte-effect-runtime/src/compiler/source-scan.ts:311-325
Diagnostics and Error Handling
The SER Vite plugin provides real-time diagnostics to catch common mistakes in effectful event handlers.
Common Warning Diagnostics
| Diagnostic Message | Cause |
|---|---|
Detected yield* hidden inside an event callback | Using onclick={() => yield* ...} instead of onclick={yield* ...}. |
Detected an event callback that returns an Effect but does not run it | Returning an Effect object from a callback without yield*. |
Detected an event attribute that looks like an Effect but is not written with yield* | Passing an Effect function directly to a handler without the yield* keyword. |
Sources: modules/svelte-effect-runtime/src/diagnostics.ts:148-212
Execution Logic
When a transformed event handler is triggered, it follows a specific sequence:
- The generated
(event) => { ... }wrapper is called by Svelte. - The wrapper calls
dispatcher.emit. - The
Markup.Runcommand is sent with a generator functionfunction* () { ... }. - The dispatcher executes the generator, allowing for effectful operations like
yield*.
Sources: modules/svelte-effect-runtime/src/markup/transform/emit.ts:144-145
Callback Normalization
SER includes a specialized transform for rewriting Effect shorthand inside handlers. The normalize_effect_callback_yields function looks for standard Effect combinators (like flatMap, tap, or match) and ensures that if they contain yield*, they are upgraded to generator-compatible versions.
This sequence illustrates how nested callbacks within an event handler are identified and wrapped to support effectful operations.
Sources: modules/svelte-effect-runtime/src/markup/transform/effect-callbacks.ts:49-70, modules/svelte-effect-runtime/src/markup/transform/effect-callbacks.ts:133-145
Effectful Combinators
The following members are specifically tracked for rewriting when they contain effectful code:
- Callback Members:
andThen,catchAll,flatMap,tap, etc. - Handler Members:
matchEffect,matchCauseEffect,tapBoth. - Property Names:
onFailure,onSuccess.
Sources: modules/svelte-effect-runtime/src/markup/transform/effect-callbacks.ts:7-22