dynamic.py 5.4 KB

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