123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- """Components that are dynamically generated on the backend."""
- from typing import TYPE_CHECKING, Union
- from reflex import constants
- from reflex.utils import imports
- from reflex.utils.exceptions import DynamicComponentMissingLibraryError
- 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.
- Args:
- lib: The library to get the CDN URL for.
- Returns:
- The CDN URL for the library.
- """
- return f"https://cdn.jsdelivr.net/npm/{lib}" + "/+esm"
- bundled_libraries = {
- "react",
- "@radix-ui/themes",
- "@emotion/react",
- "next/link",
- f"$/{constants.Dirs.UTILS}/context",
- f"$/{constants.Dirs.UTILS}/state",
- f"$/{constants.Dirs.UTILS}/components",
- }
- def bundle_library(component: Union["Component", str]):
- """Bundle a library with the component.
- Args:
- component: The component to bundle the library with.
- Raises:
- DynamicComponentMissingLibraryError: Raised when a dynamic component is missing a library.
- """
- if isinstance(component, str):
- bundled_libraries.add(component)
- return
- if component.library is None:
- raise DynamicComponentMissingLibraryError(
- "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.
- from reflex.components.component import Component
- @serializer
- def make_component(component: Component) -> str:
- """Generate the code for a dynamic component.
- Args:
- component: The component to generate code for.
- Returns:
- The generated code
- """
- # Causes a circular import, so we import here.
- from reflex.compiler import compiler, templates, utils
- from reflex.components.base.bare import Bare
- component = Bare.create(Var.create(component))
- rendered_components = {}
- # Include dynamic imports in the shared component.
- if dynamic_imports := component._get_all_dynamic_imports():
- rendered_components.update(dict.fromkeys(dynamic_imports))
- # Include custom code in the shared component.
- rendered_components.update(
- dict.fromkeys(component._get_all_custom_code()),
- )
- rendered_components[
- templates.STATEFUL_COMPONENT.render(
- tag_name="MySSRComponent",
- memo_trigger_hooks=[],
- component=component,
- )
- ] = None
- libs_in_window = bundled_libraries
- component_imports = component._get_all_imports()
- compiler._apply_common_imports(component_imports)
- imports = {}
- for lib, names in component_imports.items():
- formatted_lib_name = format_library_name(lib)
- if (
- not lib.startswith((".", "/", "$/"))
- and not lib.startswith("http")
- and formatted_lib_name not in libs_in_window
- ):
- imports[get_cdn_url(lib)] = names
- else:
- imports[lib] = names
- module_code_lines = templates.STATEFUL_COMPONENTS.render(
- imports=utils.compile_imports(imports),
- memoized_code="\n".join(rendered_components),
- ).splitlines()[1:]
- # Rewrite imports from `/` to destructure from window
- for ix, line in enumerate(module_code_lines[:]):
- if line.startswith("import "):
- if 'from "$/' in line or 'from "/' in line:
- module_code_lines[ix] = (
- line.replace("import ", "const ", 1)
- .replace(" as ", ": ")
- .replace(" from ", " = window['__reflex'][", 1)
- + "]"
- )
- else:
- for lib in libs_in_window:
- if f'from "{lib}"' in line:
- module_code_lines[ix] = (
- line.replace("import ", "const ", 1)
- .replace(
- f' from "{lib}"', f" = window.__reflex['{lib}']", 1
- )
- .replace(" as ", ": ")
- )
- if line.startswith("export function"):
- module_code_lines[ix] = line.replace(
- "export function", "export default function", 1
- )
- line_stripped = line.strip()
- if line_stripped.startswith("{") and line_stripped.endswith("}"):
- module_code_lines[ix] = line_stripped[1:-1]
- module_code_lines.insert(0, "const React = window.__reflex.react;")
- function_line = next(
- index
- for index, line in enumerate(module_code_lines)
- if line.startswith("export default function")
- )
- module_code_lines = [
- line
- for _, line in sorted(
- enumerate(module_code_lines),
- key=lambda x: (
- not (x[1].startswith("import ") and x[0] < function_line),
- x[0],
- ),
- )
- ]
- return "\n".join(
- [
- "//__reflex_evaluate",
- *module_code_lines,
- ]
- )
- @transform
- def evaluate_component(js_string: Var[str]) -> Var[Component]:
- """Evaluate a component.
- Args:
- js_string: The JavaScript string to evaluate.
- Returns:
- The evaluated JavaScript string.
- """
- unique_var_name = get_unique_variable_name()
- return js_string._replace(
- _js_expr=unique_var_name,
- _var_type=Component,
- merge_var_data=VarData.merge(
- VarData(
- imports={
- f"$/{constants.Dirs.STATE_PATH}": [
- imports.ImportVar(tag="evalReactComponent"),
- ],
- "react": [
- imports.ImportVar(tag="useState"),
- imports.ImportVar(tag="useEffect"),
- ],
- },
- hooks={
- f"const [{unique_var_name}, set_{unique_var_name}] = useState(null);": None,
- "useEffect(() => {"
- "let isMounted = true;"
- f"evalReactComponent({js_string!s})"
- ".then((component) => {"
- "if (isMounted) {"
- f"set_{unique_var_name}(component);"
- "}"
- "});"
- "return () => {"
- "isMounted = false;"
- "};"
- "}"
- f", [{js_string!s}]);": None,
- },
- ),
- ),
- )
|