dynamic.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. imports = {}
  47. for lib, names in component._get_all_imports().items():
  48. if (
  49. not lib.startswith((".", "/"))
  50. and not lib.startswith("http")
  51. and lib != "react"
  52. ):
  53. imports[get_cdn_url(lib)] = names
  54. else:
  55. imports[lib] = names
  56. module_code_lines = templates.STATEFUL_COMPONENTS.render(
  57. imports=utils.compile_imports(imports),
  58. memoized_code="\n".join(rendered_components),
  59. ).splitlines()[1:]
  60. # Rewrite imports from `/` to destructure from window
  61. for ix, line in enumerate(module_code_lines[:]):
  62. if line.startswith("import "):
  63. if 'from "/' in line:
  64. module_code_lines[ix] = (
  65. line.replace("import ", "const ", 1).replace(
  66. " from ", " = window['__reflex'][", 1
  67. )
  68. + "]"
  69. )
  70. elif 'from "react"' in line:
  71. module_code_lines[ix] = line.replace(
  72. "import ", "const ", 1
  73. ).replace(' from "react"', " = window.__reflex.react", 1)
  74. if line.startswith("export function"):
  75. module_code_lines[ix] = line.replace(
  76. "export function", "export default function", 1
  77. )
  78. module_code_lines.insert(0, "const React = window.__reflex.react;")
  79. return "//__reflex_evaluate\n" + "\n".join(module_code_lines)
  80. @transform
  81. def evaluate_component(js_string: Var[str]) -> Var[Component]:
  82. """Evaluate a component.
  83. Args:
  84. js_string: The JavaScript string to evaluate.
  85. Returns:
  86. The evaluated JavaScript string.
  87. """
  88. unique_var_name = get_unique_variable_name()
  89. return js_string._replace(
  90. _js_expr=unique_var_name,
  91. _var_type=Component,
  92. merge_var_data=VarData.merge(
  93. VarData(
  94. imports={
  95. f"/{constants.Dirs.STATE_PATH}": [
  96. imports.ImportVar(tag="evalReactComponent"),
  97. ],
  98. "react": [
  99. imports.ImportVar(tag="useState"),
  100. imports.ImportVar(tag="useEffect"),
  101. ],
  102. },
  103. hooks={
  104. f"const [{unique_var_name}, set_{unique_var_name}] = useState(null);": None,
  105. "useEffect(() => {"
  106. "let isMounted = true;"
  107. f"evalReactComponent({str(js_string)})"
  108. ".then((component) => {"
  109. "if (isMounted) {"
  110. f"set_{unique_var_name}(component);"
  111. "}"
  112. "});"
  113. "return () => {"
  114. "isMounted = false;"
  115. "};"
  116. "}"
  117. f", [{str(js_string)}]);": None,
  118. },
  119. ),
  120. ),
  121. )