fix: use push_renderer_if_inactive in compiled components#18058
fix: use push_renderer_if_inactive in compiled components#18058tomyan wants to merge 1 commit intosveltejs:svelte-custom-rendererfrom
Conversation
When render() already pushed a renderer, the compiled component should not override it with its own imported renderer. This ensures that effects created during mount capture the renderer provided by the render() caller, not the statically imported one. Without this fix, custom renderers that configure their renderer at mount() time (e.g., with a RenderContext for incremental rendering) have their configuration lost because the compiled component pushes a different renderer instance. Added push_renderer_if_inactive() which only pushes if no renderer is currently active. Compiled components now use this instead of push_renderer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
Can you explain a bit more what you are trying to do? Why are you creating a separate renderer? |
|
Thanks @paoloricciuti! I'm prototyping a terminal renderer using the custom renderer API - Svelte components rendered to a cell grid with ANSI escape sequences. It's been really great to work with. I may be doing something the API doesn't intend — I'm creating the renderer at mount time with per-instance context (a render queue that tracks which nodes changed for incremental updates). The static import is a bare instance without this context. The issue is that effects capture the static import, so reactive updates bypass the mount-time renderer. Is the expectation that there's only ever one renderer instance (the static import), and any state should be attached to that? I could make that work with a mutable global, but it would prevent mounting multiple independent component trees. If per-mount context is a reasonable pattern, |
codeCraft-Ritik
left a comment
There was a problem hiding this comment.
Closure Capture Timing: The core issue is that template_effect (and other runes) capture the "current" renderer at the moment of initialization. If the compiled component pushes its own renderer onto the stack after the custom one, the effect is permanently tethered to the wrong instance.
Hi @paoloricciuti — I'm excited about this PR. I've been trying out the custom renderer API for a terminal renderer and ran into an issue with reactive updates.
One problem I ran into is that when
render()is called with a renderer that has been configured with context (e.g. a render queue for incremental updates), the compiled component'spush_renderer($renderer)call overrides it with the statically imported default renderer. Effects created during the component capture the imported renderer, so when they re-run on$statechanges,renderer.setTextis called on the wrong instance — one without the mount-time configuration.In practice this means that if you create a renderer with
createRenderer()for the static import (as required by the compiler), and then create a second configured instance in yourmount()function to pass torenderer.render(), the configured instance is only used during initial tree construction. Any subsequent reactive updates ($statechanges triggeringtemplate_effectre-runs) go through the unconfigured import instead. This effectively makes reactive state changes invisible to the renderer's infrastructure — text updates, attribute changes, and DOM mutations from effects all bypass whatever context therender()caller set up.The sequence:
render()callspush_renderer(mountRenderer)— renderer with context ✓push_renderer($renderer)— statically imported, no context ✗template_effectcapturesr: renderer— gets the import, not the mount renderer$statechange, effect re-run callsset_text→renderer.setTexton wrong instanceTo fix this I propose
push_renderer_if_inactive()which only pushes if no renderer is currently active. The compiled component uses this instead ofpush_renderer, so ifrender()already pushed a renderer, the component respects it.