Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/7096.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Persist bundled-library metadata for backend-only workers so state hydration can serialize values that reference libraries included in the frontend build.
1 change: 1 addition & 0 deletions packages/reflex-base/news/7096.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Persist the bundled-library registry used by backend-only workers when serializing state hydration data.
2 changes: 2 additions & 0 deletions packages/reflex-base/src/reflex_base/constants/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class Dirs(SimpleNamespace):
APP_COMPONENTS = "app_components"
# The name of the env json file.
ENV_JSON = "env.json"
# The name of the compiled bundled-library registry.
BUNDLED_LIBRARIES = "bundled_libraries.json"
# The name of the reflex json file.
REFLEX_JSON = "reflex.json"
# The name of the postcss config file.
Expand Down
9 changes: 6 additions & 3 deletions reflex/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,7 @@ def compile_app(
logger.debug(f"BE Evaluating stateful page: {route}")
app._compile_page(route, save_page=False)
if app._state is not None:
utils._restore_bundled_libraries()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a backend-only worker discovers a library while evaluating a page, this restore replaces that registration with the older frontend snapshot before initial-state serialization. Restore the snapshot before evaluating pages, or merge it with registrations discovered during evaluation, so page-level bundle_library() calls remain available.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/compiler/compiler.py, line 1244:

<comment>When a backend-only worker discovers a library while evaluating a page, this restore replaces that registration with the older frontend snapshot before initial-state serialization. Restore the snapshot before evaluating pages, or merge it with registrations discovered during evaluation, so page-level `bundle_library()` calls remain available.</comment>

<file context>
@@ -1241,6 +1241,7 @@ def compile_app(
                 logger.debug(f"BE Evaluating stateful page: {route}")
                 app._compile_page(route, save_page=False)
         if app._state is not None:
+            utils._restore_bundled_libraries()
             utils._compile_initial_state(app._state)
         app._add_optional_endpoints()
</file context>

utils._compile_initial_state(app._state)
app._add_optional_endpoints()
return False
Expand All @@ -1261,6 +1262,7 @@ def compile_app(

app._write_stateful_pages_marker()
if app._state is not None:
utils._restore_bundled_libraries()
utils._compile_initial_state(app._state)
app._add_optional_endpoints()
return False
Expand Down Expand Up @@ -1463,13 +1465,14 @@ def add_save_task(
compile_results.append(result)
progress.advance(task)

compile_results.append(
compile_results.extend([
compile_contexts(
app._state,
radix_themes_plugin.get_theme(),
component_imports=all_imports,
)
)
),
utils._compile_bundled_libraries(),
])
progress.advance(task)

compile_results.append(compile_app_root(app_root, hydrate_fallback_export))
Expand Down
26 changes: 26 additions & 0 deletions reflex/compiler/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,32 @@ def serialize_initial_value(value: Any) -> Any:
)


def _compile_bundled_libraries() -> tuple[str, str]:
"""Return the bundled-library registry as a frontend build artifact.

Returns:
The output path and serialized registry.
"""
bundled_libraries = RegistrationContext.ensure_context().bundled_libraries
return constants.Dirs.BUNDLED_LIBRARIES, format.json_dumps(bundled_libraries)


def _restore_bundled_libraries() -> None:
"""Restore the registry emitted by the most recent frontend compile."""
path = get_web_dir() / constants.Dirs.BUNDLED_LIBRARIES
try:
bundled_libraries = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When bundled_libraries.json contains invalid UTF-8, read_text() raises UnicodeDecodeError before JSON parsing, so backend-only compilation crashes instead of falling back. Catch UnicodeDecodeError here (or the broader ValueError) with the existing malformed-file exceptions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/compiler/utils.py, line 291:

<comment>When `bundled_libraries.json` contains invalid UTF-8, `read_text()` raises `UnicodeDecodeError` before JSON parsing, so backend-only compilation crashes instead of falling back. Catch `UnicodeDecodeError` here (or the broader `ValueError`) with the existing malformed-file exceptions.</comment>

<file context>
@@ -273,6 +273,32 @@ def serialize_initial_value(value: Any) -> Any:
+    path = get_web_dir() / constants.Dirs.BUNDLED_LIBRARIES
+    try:
+        bundled_libraries = json.loads(path.read_text(encoding="utf-8"))
+    except (OSError, json.JSONDecodeError):
+        return
+    if not isinstance(bundled_libraries, list) or not all(
</file context>
Suggested change
except (OSError, json.JSONDecodeError):
except (OSError, UnicodeDecodeError, json.JSONDecodeError):

return
if not isinstance(bundled_libraries, list) or not all(
isinstance(library, str) for library in bundled_libraries
):
return
RegistrationContext.ensure_context().bundled_libraries[:] = list(
dict.fromkeys(bundled_libraries)
)


def _compile_client_storage_field(
field: Field,
) -> (
Expand Down
24 changes: 24 additions & 0 deletions tests/units/compiler/test_compiler_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import asyncio

import pytest
from reflex_base.registry import RegistrationContext
from reflex_components_core.base.fragment import Fragment
from reflex_components_core.base.script import Script
from reflex_components_core.el.elements.metadata import Link

from reflex.compiler import utils
from reflex.compiler.utils import compile_state, create_document_root
from reflex.compiler.utils import write_file as compiler_write_file
from reflex.constants.state import FIELD_MARKER
Expand All @@ -20,6 +22,28 @@ def test_write_file_reexport() -> None:
assert compiler_write_file is write_file


def test_bundled_libraries_artifact_round_trip(
tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Backend-only workers can restore the registry from the frontend build."""
monkeypatch.setattr(utils, "get_web_dir", lambda: tmp_path)
with RegistrationContext() as context:
context.bundled_libraries.append("@radix-ui/themes")
output_path, output = utils._compile_bundled_libraries()
(tmp_path / output_path).write_text(output, encoding="utf-8")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Compiler Wiring Is Untested

This test manually writes the registry artifact and directly calls both helpers. It would still pass if compile_app stopped emitting the artifact or restoring it before initial-state serialization, leaving the orchestration behind this fix without regression coverage.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

context.bundled_libraries[:] = ["react"]

utils._restore_bundled_libraries()

assert context.bundled_libraries == [
"react",
"@emotion/react",
"$/utils/context",
"$/utils/state",
"@radix-ui/themes",
]


def test_document_preloads_the_global_stylesheet():
"""Render-blocking CSS should be discoverable alongside early resource hints."""
head = create_document_root().children[0]
Expand Down
Loading