feat: support embedding Reflex apps into host pages via mount_target#6462
feat: support embedding Reflex apps into host pages via mount_target#6462FarhanAliRaza wants to merge 6 commits intoreflex-dev:mainfrom
Conversation
Add and config options for embedding a Reflex app into an arbitrary DOM node on a host page. When is set, the entry client mounts a memory data router into the target element via instead of hydrating the document, the build emits a stable shim re-exporting the hashed Vite chunk, and a route manifest is generated at compile time so the embedded app owns its own URL space. now sources from the data router's location so the backend sees the in-widget URL rather than the host page's.
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThis PR adds
Confidence Score: 4/5The core embedding mechanism is well-reasoned and the happy-path test coverage is solid, but there are error-handling gaps and a caching quirk that could obscure problems in production. All four findings are non-blocking quality concerns: Promise.all lacks error handling so embed mount failures are invisible to users, a misconfigured selector silently triggers document hydration causing cryptic React Router errors on real host pages, @functools.cache contradicts the per-compile-refresh docstring and would serve stale content after a mid-session package upgrade, and the module-level locationRef is null until first render so early events still read the host window.location in embed mode. entry.client.js (error handling and selector fallback behavior) and frontend_skeleton.py (@functools.cache on the template reader) deserve a second look before merge. Important Files Changed
Reviews (1): Last reviewed commit: "feat: support embedding Reflex apps into..." | Re-trigger Greptile |
Split the embed entry into a separate template and only swap it in when mount_target is set, so non-embed builds produce the exact same entry.client.js as before this feature landed.
…e mount Log a clear error when MOUNT_TARGET points at a missing element or the dynamic embed imports reject, instead of silently no-op'ing. Pre-seed locationRef to / in embed mode so events dispatched before the first effect commit don't briefly fall back to the host page's URL. Drop the template read cache so toggling mount_target between compiles picks up the right entry without a re-init.
masenf
left a comment
There was a problem hiding this comment.
My main feedback here is to implement this primarily as a Plugin instead of wiring it through the whole framework. The plugin can take in the mount_target and embed_origin as parameters instead of adding more to the global config.
The only real downside of this approach is the current interpret_plugin_env parser for environment var specified plugins does not expose a way to provide arguments to a plugin class.
|
My thought was plugin too, but most of the will still remain outside of plugin i think. So I implemented this way.. I ll update it. |
Embedding is now opt-in via `rx.plugins.EmbedPlugin(mount_target=...)` in `rx.Config.plugins` rather than top-level `mount_target` / `embed_origin` fields on `BaseConfig`. The plugin owns the embed entry and route-manifest save tasks (no more conditional dispatch in `compile_app`) and gains an optional `dev_preview` mode that injects a Vite middleware serving a minimal host wrapper at `/`, so the embed can be exercised without a separately served host page. `mount_target` / `embed_origin` still fall back to `REFLEX_MOUNT_TARGET` / `REFLEX_EMBED_ORIGIN` so `REFLEX_PLUGINS=...EmbedPlugin` works without constructor args. Compiler and build code now query the active plugin via `get_embed_plugin()` instead of reading config attributes.
masenf
left a comment
There was a problem hiding this comment.
i see some of what you were saying before about how the changes are still somewhat spread out even with the plugin.
at least the changes in reflex/compiler/compiler.py seem like they could be moved into the embed plugin module, since they are constructing the embed manifest and only used when embedding is enabled.
the problematic areas are where we're calling get_embed_plugin(); this seems like a role reversal. ideally the rest of the code doesn't need to know anything about the internals of the EmbedPlugin for it to work. what it tells me is that maybe we need more hooks into the compilation process to allow plugins like this to be expressed succinctly.
update_env_json- this could return dictionaries that get merged together and becomeenv.json.- maybe
_emit_stable_entry_bootloadercould be moved to a post_compile step, which should run before the production build step. - the compile_vite_config is slightly more annoying, so we can leave that for now. but I'm wanting to bring in something like @riebecj's https://github.com/riebecj/reflex-vite-config-plugin at some point. So it would be cool if we could bring in something like that which exposed its own hooks, like
modify_vite_configwhich would allow plugins to make structural modifications to the vite config before it gets written out.
| ) | ||
|
|
||
|
|
||
| Plugin = EmbedPlugin |
There was a problem hiding this comment.
This is a useless assignment. Nowhere in any of your code are you importing plugins.embed.Plugin. Which makes this import statement unnecessary. Just from .base import Plugin and subclass as class EmbedPlugin(Plugin): so we're not creating needlessly confusing module attributes.
Add and config options for embedding a Reflex
app into an arbitrary DOM node on a host page. When is set, the entry client mounts a memory data router into the target element via
instead of hydrating the document, the build emits a stable
shim re-exporting the hashed Vite chunk, and a route
manifest is generated at compile time so the embedded app owns its own URL space. now sources from the data router's
location so the backend sees the in-widget URL rather than the host page's.
Usage
rxconfig.py
usage in the HTML file.
All Submissions:
Type of change
Please delete options that are not relevant.
New Feature Submission:
Changes To Core Features: