diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index a1597bfed..43cac2ba8 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -4,7 +4,6 @@ from __future__ import annotations import os from datetime import datetime -from importlib.util import find_spec from pathlib import Path from typing import Dict, Iterable, Optional, Type, Union @@ -41,6 +40,20 @@ def _compile_document_root(root: Component) -> str: ) +def _normalize_library_name(lib: str) -> str: + """Normalize the library name. + + Args: + lib: The library name to normalize. + + Returns: + The normalized library name. + """ + if lib == "react": + return "React" + return lib.replace("@", "").replace("/", "_").replace("-", "_") + + def _compile_app(app_root: Component) -> str: """Compile the app template component. @@ -50,25 +63,20 @@ def _compile_app(app_root: Component) -> str: Returns: The compiled app. """ - chakra_available = find_spec("reflex_chakra") is not None + from reflex.components.dynamic import bundled_libraries + + window_libraries = [ + (_normalize_library_name(name), name) for name in bundled_libraries + ] + [ + ("utils_context", f"/{constants.Dirs.UTILS}/context.js"), + ("utils_state", f"/{constants.Dirs.UTILS}/state.js"), + ] return templates.APP_ROOT.render( imports=utils.compile_imports(app_root._get_all_imports()), custom_codes=app_root._get_all_custom_code(), hooks={**app_root._get_all_hooks_internal(), **app_root._get_all_hooks()}, - window_libraries=[ - ("React", "react"), - ("utils_context", f"/{constants.Dirs.UTILS}/context.js"), - ("utils_state", f"/{constants.Dirs.UTILS}/state.js"), - ("radix", "@radix-ui/themes"), - ] - + ( - [ - ("chakra", "@chakra-ui/react"), - ] - if chakra_available - else [] - ), + window_libraries=window_libraries, render=app_root.render(), ) diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index fdf046669..2e336027b 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -1,14 +1,18 @@ """Components that are dynamically generated on the backend.""" -from importlib.util import find_spec +from typing import TYPE_CHECKING from reflex import constants from reflex.utils import imports +from reflex.utils.exceptions import DynamicComponentMissingLibrary from reflex.utils.format import format_library_name from reflex.utils.serializers import serializer from reflex.vars import Var, get_unique_variable_name from reflex.vars.base import VarData, transform +if TYPE_CHECKING: + from reflex.components.component import Component + def get_cdn_url(lib: str) -> str: """Get the CDN URL for a library. @@ -22,6 +26,23 @@ def get_cdn_url(lib: str) -> str: return f"https://cdn.jsdelivr.net/npm/{lib}" + "/+esm" +bundled_libraries = {"react", "@radix-ui/themes"} + + +def bundle_library(component: "Component"): + """Bundle a library with the component. + + Args: + component: The component to bundle the library with. + + Raises: + DynamicComponentMissingLibrary: Raised when a dynamic component is missing a library. + """ + if component.library is None: + raise DynamicComponentMissingLibrary("Component must have a library to bundle.") + bundled_libraries.add(format_library_name(component.library)) + + def load_dynamic_serializer(): """Load the serializer for dynamic components.""" # Causes a circular import, so we import here. @@ -60,12 +81,7 @@ def load_dynamic_serializer(): ) ] = None - chakra_available = find_spec("reflex_chakra") is not None - - libs_in_window = [ - "react", - "@radix-ui/themes", - ] + (["@chakra-ui/react"] if chakra_available else []) + libs_in_window = bundled_libraries imports = {} for lib, names in component._get_all_imports().items(): diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index 7c3532861..330763f22 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -115,3 +115,7 @@ class PrimitiveUnserializableToJSON(ReflexError, ValueError): class InvalidLifespanTaskType(ReflexError, TypeError): """Raised when an invalid task type is registered as a lifespan task.""" + + +class DynamicComponentMissingLibrary(ReflexError, ValueError): + """Raised when a dynamic component is missing a library."""