Fix async child rendering in custom components - #13817
Conversation
🪼 branch checks and previews
Install Gradio from this PR pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/7bc0fe3782c1a60a21080516de08437b4a5deda8/gradio-6.26.0-py3-none-any.whlInstall Gradio Python Client from this PR pip install "gradio-client @ git+https://github.com/gradio-app/gradio@7bc0fe3782c1a60a21080516de08437b4a5deda8#subdirectory=client/python"Import Gradio JS Client from this PR via CDN import { Client } from "https://huggingface.co/buckets/gradio/npm-previews/resolve/7bc0fe3782c1a60a21080516de08437b4a5deda8/browser.js"; |
🦄 change detectedThis Pull Request includes changes to the following packages.
|
|
Verified this fix with paired public Spaces using byte-identical
Preview wheel used by the after Space: |
There was a problem hiding this comment.
🟡 Changes recommended
The bridge alters child DOM structure, and the regression test does not exercise separate Svelte runtimes.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Fixes delayed child rendering across host and custom-component Svelte runtimes.
Changes:
- Bridges host children into the custom runtime.
- Exports
createRawSnippetfrom generated runtimes. - Adds an asynchronous rendering regression test.
File summaries
| File | Description |
|---|---|
js/preview/src/svelte_runtime_entry.js |
Exports the snippet factory. |
js/core/src/MountCustomComponent.svelte |
Adds the cross-runtime child bridge. |
js/core/src/MountChildren.svelte |
Renders host-runtime children. |
js/core/src/AsyncChildren.test.svelte |
Provides an asynchronous test component. |
js/core/src/MountCustomComponent.test.ts |
Tests delayed child rendering. |
.changeset/tidy-symbols-act.md |
Records patch releases. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const runtime_children = | ||
| children && _runtime.createRawSnippet | ||
| ? _runtime.createRawSnippet(() => ({ | ||
| render: () => "<span></span>", |
There was a problem hiding this comment.
Addressed in ed50099. The foreign-runtime raw snippet now creates only a temporary hidden marker; after the host runtime mounts into it, target.replaceWith(...target.childNodes) removes that marker synchronously and leaves the real children as direct siblings at the render position. The regression test also asserts the button is a direct child of the async block.
| runtime: Promise.resolve({ | ||
| createRawSnippet: runtime_create_raw_snippet, | ||
| mount, | ||
| unmount |
There was a problem hiding this comment.
Addressed in ed50099. The fixture now loads its component and Svelte exports in an isolated iframe module graph, so it has independent runtime singleton state from the host test. I also validated the inverse: bypassing the bridge makes this test fail with the original Cannot read properties of null (reading nodes) stack; restoring the bridge passes.
…m-components-blocked-when-using-dynamic-chi
#13818 before/after verification
Both Spaces run the same app and byte-identical custom-component wheel. Click Stream updates and compare the visible mount counter:
|
Description
Fixes two custom-component regressions caused by Gradio and a custom component using separate Svelte runtimes:
childrensnippet was invoked from the custom runtime without an active host effect.$effect, causing the whole custom component to unmount and mount again on every prop update.This change recreates the outer child snippet with the custom component's runtime and mounts a small host-runtime wrapper inside it. It also calls the foreign runtime's mount inside the host runtime's
untrack, exportscreateRawSnippetfrom newly built custom component runtime bundles, and adds isolated-runtime regression coverage for both behaviors.Closes: #13131
Closes: #13818
Reproduction
For #13131, rendering children after an async boundary failed:
{#await promise then value} <div> {value} {@render children?.()} </div> {/await}Before this change, the delayed block throws
Cannot read properties of null (reading 'nodes')and never renders its child. After this change, both the resolved value and child render.For #13818, a streaming update to a custom component caused its Svelte component to unmount and mount again for every yielded value. After this change, the component remains mounted while its value updates.
Testing
CI=1 pnpm exec vitest run js/core/src/MountCustomComponent.test.ts --config .config/vitest.config.ts --reporter=verbosepnpm --filter @gradio/preview buildpnpm --filter @self/spa buildmainand this branchvalue-4without any additional mount/unmount logsbash scripts/format_frontend.shformatted the changes successfully. Its repository-widesvelte-checkphase currently reports unrelated existing errors in PrismJS imports, preview Rollup types, and ColorPicker.AI Disclosure
🎯 PRs Should Target Issues
This PR targets and closes #13131 and #13818. I checked both issues for overlapping open PRs before adding their fixes and found none.
Testing and Formatting Your Code
The frontend formatting and verification details are listed above. No backend code was changed.