Markup AST Transformations
Relevant source files
The following files were used as context for generating this wiki page:
- modules/svelte-effect-runtime/src/markup/transform/index.ts
- modules/svelte-effect-runtime/src/markup/transform/apply.ts
- modules/svelte-effect-runtime/src/markup/transform/scan.ts
- modules/svelte-effect-runtime/src/markup/transform/classify.ts
- modules/svelte-effect-runtime/src/markup/transform/types.ts
- modules/svelte-effect-runtime/src/compiler/source-scan.ts
Markup AST Transformations in the Svelte Effect Runtime (SER) are responsible for lowering {yield* expr} brace expressions within Svelte templates into generated dispatcher events. This process allows developers to use effectful generator syntax directly in Svelte markup, which is then transformed into valid Svelte code that interacts with the Effect runtime.
The transformation strategy follows a multi-phase approach: character-based scanning for initial identification, sanitization to allow standard Svelte parsing, AST-based classification to determine the context of each expression, and final code emission with source mapping.
Sources: modules/svelte-effect-runtime/src/markup/transform/index.ts:34-43, modules/svelte-effect-runtime/src/markup/transform/index.ts:53-56
Transformation Pipeline
The transformation of Svelte markup follows a structured flow to ensure that yield* expressions are correctly identified and lowered according to their structural context (e.g., whether they appear in an #each block, an event handler, or a plain expression).
The diagram above illustrates the high-level data flow from raw Svelte content to the final transformed output.
Sources: modules/svelte-effect-runtime/src/markup/transform/index.ts:53-100
1. Sanitization and Scanning
The process begins with sanitize_markup, which uses a lexical scanner to find all brace expressions containing yield*. These expressions are replaced with unique placeholder identifiers (e.g., __SER___markup_placeholder_0) so that the code can be safely parsed by the Svelte compiler without syntax errors.
Key features of this phase:
- Identifies expressions in tags like
#each,#await, and@render. - Handles
@constdeclarations and event callback handlers. - Preserves the surrounding Svelte syntax while isolating the effectful expression.
Sources: modules/svelte-effect-runtime/src/markup/transform/scan.ts:46-65, modules/svelte-effect-runtime/src/markup/transform/scan.ts:167-190
2. AST Classification
Once the markup is sanitized, it is parsed into a Svelte AST. The classify_candidates function walks this AST to match the injected placeholders back to their functional context. This is critical because the lowering logic for a yield* expression inside an event handler differs from one inside a reactive #each loop.
| Tag Kind | Description |
|---|---|
plain | Standard brace expressions or logic block tests (e.g., {#if yield* ...}). |
each | Expressions specifically inside the iterable part of an #each block. |
await | Expressions used as the promise source in an #await block. |
event | Expressions used in event attributes (e.g., onclick). |
render | Expressions within @render tags. |
render_argument | Specific arguments passed into a render snippet. |
Sources: modules/svelte-effect-runtime/src/markup/transform/classify.ts:25-39, modules/svelte-effect-runtime/src/markup/transform/types.ts:109-117
3. Emission and Injection
After classification, emit_replacements generates the actual runtime code for each candidate. This includes:
- Replacements: Overwriting the placeholders in the markup with runtime-compatible identifiers or calls.
- Helper Injection: Injecting necessary imports and constant declarations into the component's
<script>block.
If the component lacks a <script> block, one is prepended. If an instance script exists, the helpers are appended to it.
Sources: modules/svelte-effect-runtime/src/markup/transform/index.ts:81-93, modules/svelte-effect-runtime/src/markup/transform/apply.ts:46-77
Source Mapping and Relocation
Because the transformation changes offsets and introduces new code, SER generates MarkupRelocation data and source maps. Relocations preserve "hoverable" source spans, ensuring that editor features like "Go to Definition" or "Hover" still point to the original developer's code even when interacting with the transformed output.
The relationship between transform results and the metadata required to map generated code back to its original source.
Sources: modules/svelte-effect-runtime/src/markup/transform/types.ts:14-55, modules/svelte-effect-runtime/src/markup/transform/apply.ts:121-163
Key Implementation Details
Lexical Fact Discovery
The scan_svelte_effect_source function provides shared lexical facts about the Svelte document, including script regions, style tags, and markup brace expressions. This scanner is used to avoid entering "excluded ranges" (like the content of a <style> tag) when looking for yield* candidates.
Sources: modules/svelte-effect-runtime/src/compiler/source-scan.ts:153-176
Helper Bindings
The transformation allocates collision-free identifiers for the runtime helpers using a MarkupNameAllocator. This ensures that injected code like the dispatcher or yieldable wrapper does not conflict with variables already defined by the user in the component's script.
| Helper Identifier | Default Name | Purpose |
|---|---|---|
dispatcher | __SER___dispatcher | The runtime event dispatcher. |
codes | __SER___codes | Enum-like identifiers for markup events. |
yieldable | __SER___yieldable | Wrapper for converting values to Effects. |
Sources: modules/svelte-effect-runtime/src/markup/transform/apply.ts:104-118, modules/svelte-effect-runtime/src/markup/transform/types.ts:205-218
Conclusion
Markup AST Transformations provide the bridge between Effect's generator-based control flow and Svelte's reactive template syntax. By sanitizing markup, classifying expressions through AST traversal, and carefully managing source offsets via relocations, the system allows for a seamless development experience where effectful logic can be embedded directly within UI declarations.