dynamic.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. """Components that are dynamically generated on the backend."""
  2. from reflex import constants
  3. from reflex.utils import imports
  4. from reflex.utils.format import format_library_name
  5. from reflex.utils.serializers import serializer
  6. from reflex.vars import Var, get_unique_variable_name
  7. from reflex.vars.base import VarData, transform
  8. def get_cdn_url(lib: str) -> str:
  9. """Get the CDN URL for a library.
  10. Args:
  11. lib: The library to get the CDN URL for.
  12. Returns:
  13. The CDN URL for the library.
  14. """
  15. return f"https://cdn.jsdelivr.net/npm/{lib}" + "/+esm"
  16. def load_dynamic_serializer():
  17. """Load the serializer for dynamic components."""
  18. # Causes a circular import, so we import here.
  19. from reflex.components.component import Component
  20. @serializer
  21. def make_component(component: Component) -> str:
  22. """Generate the code for a dynamic component.
  23. Args:
  24. component: The component to generate code for.
  25. Returns:
  26. The generated code
  27. """
  28. # Causes a circular import, so we import here.
  29. from reflex.compiler import templates, utils
  30. rendered_components = {}
  31. # Include dynamic imports in the shared component.
  32. if dynamic_imports := component._get_all_dynamic_imports():
  33. rendered_components.update(
  34. {dynamic_import: None for dynamic_import in dynamic_imports}
  35. )
  36. # Include custom code in the shared component.
  37. rendered_components.update(
  38. {code: None for code in component._get_all_custom_code()},
  39. )
  40. rendered_components[
  41. templates.STATEFUL_COMPONENT.render(
  42. tag_name="MySSRComponent",
  43. memo_trigger_hooks=[],
  44. component=component,
  45. )
  46. ] = None
  47. libs_in_window = [
  48. "react",
  49. "@radix-ui/themes",
  50. ]
  51. imports = {}
  52. for lib, names in component._get_all_imports().items():
  53. formatted_lib_name = format_library_name(lib)
  54. if (
  55. not lib.startswith((".", "/"))
  56. and not lib.startswith("http")
  57. and all(
  58. formatted_lib_name != lib_in_window
  59. for lib_in_window in libs_in_window
  60. )
  61. ):
  62. imports[get_cdn_url(lib)] = names
  63. else:
  64. imports[lib] = names
  65. module_code_lines = templates.STATEFUL_COMPONENTS.render(
  66. imports=utils.compile_imports(imports),
  67. memoized_code="\n".join(rendered_components),
  68. ).splitlines()[1:]
  69. # Rewrite imports from `/` to destructure from window
  70. for ix, line in enumerate(module_code_lines[:]):
  71. if line.startswith("import "):
  72. if 'from "/' in line:
  73. module_code_lines[ix] = (
  74. line.replace("import ", "const ", 1).replace(
  75. " from ", " = window['__reflex'][", 1
  76. )
  77. + "]"
  78. )
  79. else:
  80. for lib in libs_in_window:
  81. if f'from "{lib}"' in line:
  82. module_code_lines[ix] = (
  83. line.replace("import ", "const ", 1)
  84. .replace(
  85. f' from "{lib}"', f" = window.__reflex['{lib}']", 1
  86. )
  87. .replace(" as ", ": ")
  88. )
  89. if line.startswith("export function"):
  90. module_code_lines[ix] = line.replace(
  91. "export function", "export default function", 1
  92. )
  93. module_code_lines.insert(0, "const React = window.__reflex.react;")
  94. return "//__reflex_evaluate\n" + "\n".join(module_code_lines)
  95. @transform
  96. def evaluate_component(js_string: Var[str]) -> Var[Component]:
  97. """Evaluate a component.
  98. Args:
  99. js_string: The JavaScript string to evaluate.
  100. Returns:
  101. The evaluated JavaScript string.
  102. """
  103. unique_var_name = get_unique_variable_name()
  104. return js_string._replace(
  105. _js_expr=unique_var_name,
  106. _var_type=Component,
  107. merge_var_data=VarData.merge(
  108. VarData(
  109. imports={
  110. f"/{constants.Dirs.STATE_PATH}": [
  111. imports.ImportVar(tag="evalReactComponent"),
  112. ],
  113. "react": [
  114. imports.ImportVar(tag="useState"),
  115. imports.ImportVar(tag="useEffect"),
  116. ],
  117. },
  118. hooks={
  119. f"const [{unique_var_name}, set_{unique_var_name}] = useState(null);": None,
  120. "useEffect(() => {"
  121. "let isMounted = true;"
  122. f"evalReactComponent({str(js_string)})"
  123. ".then((component) => {"
  124. "if (isMounted) {"
  125. f"set_{unique_var_name}(component);"
  126. "}"
  127. "});"
  128. "return () => {"
  129. "isMounted = false;"
  130. "};"
  131. "}"
  132. f", [{str(js_string)}]);": None,
  133. },
  134. ),
  135. ),
  136. )