compiler.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. """Compiler for the reflex apps."""
  2. from __future__ import annotations
  3. import os
  4. from pathlib import Path
  5. from typing import Optional, Type
  6. from reflex import constants
  7. from reflex.compiler import templates, utils
  8. from reflex.components.component import Component, ComponentStyle, CustomComponent
  9. from reflex.config import get_config
  10. from reflex.state import State
  11. from reflex.utils import imports
  12. from reflex.vars import ImportVar
  13. # Imports to be included in every Reflex app.
  14. DEFAULT_IMPORTS: imports.ImportDict = {
  15. "react": [
  16. ImportVar(tag="Fragment"),
  17. ImportVar(tag="useEffect"),
  18. ImportVar(tag="useRef"),
  19. ImportVar(tag="useState"),
  20. ImportVar(tag="useContext"),
  21. ],
  22. "next/router": [ImportVar(tag="useRouter")],
  23. f"/{constants.Dirs.STATE_PATH}": [
  24. ImportVar(tag="uploadFiles"),
  25. ImportVar(tag="Event"),
  26. ImportVar(tag="isTrue"),
  27. ImportVar(tag="spreadArraysOrObjects"),
  28. ImportVar(tag="preventDefault"),
  29. ImportVar(tag="refs"),
  30. ImportVar(tag="getRefValue"),
  31. ImportVar(tag="getRefValues"),
  32. ImportVar(tag="getAllLocalStorageItems"),
  33. ImportVar(tag="useEventLoop"),
  34. ],
  35. "/utils/context.js": [
  36. ImportVar(tag="EventLoopContext"),
  37. ImportVar(tag="initialEvents"),
  38. ImportVar(tag="StateContext"),
  39. ImportVar(tag="ColorModeContext"),
  40. ],
  41. "/utils/helpers/range.js": [
  42. ImportVar(tag="range", is_default=True),
  43. ],
  44. "": [ImportVar(tag="focus-visible/dist/focus-visible", install=False)],
  45. }
  46. def _compile_document_root(root: Component) -> str:
  47. """Compile the document root.
  48. Args:
  49. root: The document root to compile.
  50. Returns:
  51. The compiled document root.
  52. """
  53. return templates.DOCUMENT_ROOT.render(
  54. imports=utils.compile_imports(root.get_imports()),
  55. document=root.render(),
  56. )
  57. def _compile_app(app_root: Component) -> str:
  58. """Compile the app template component.
  59. Args:
  60. app_root: The app root to compile.
  61. Returns:
  62. The compiled app.
  63. """
  64. return templates.APP_ROOT.render(
  65. imports=utils.compile_imports(app_root.get_imports()),
  66. custom_codes=app_root.get_custom_code(),
  67. hooks=app_root.get_hooks(),
  68. render=app_root.render(),
  69. )
  70. def _compile_theme(theme: dict) -> str:
  71. """Compile the theme.
  72. Args:
  73. theme: The theme to compile.
  74. Returns:
  75. The compiled theme.
  76. """
  77. return templates.THEME.render(theme=theme)
  78. def _compile_contexts(state: Optional[Type[State]]) -> str:
  79. """Compile the initial state and contexts.
  80. Args:
  81. state: The app state.
  82. Returns:
  83. The compiled context file.
  84. """
  85. is_dev_mode = os.environ.get("REFLEX_ENV_MODE", "dev") == "dev"
  86. return (
  87. templates.CONTEXT.render(
  88. initial_state=utils.compile_state(state),
  89. state_name=state.get_name(),
  90. client_storage=utils.compile_client_storage(state),
  91. is_dev_mode=is_dev_mode,
  92. )
  93. if state
  94. else templates.CONTEXT.render(is_dev_mode=is_dev_mode)
  95. )
  96. def _compile_page(
  97. component: Component,
  98. state: Type[State],
  99. ) -> str:
  100. """Compile the component given the app state.
  101. Args:
  102. component: The component to compile.
  103. state: The app state.
  104. Returns:
  105. The compiled component.
  106. """
  107. # Merge the default imports with the app-specific imports.
  108. imports = utils.merge_imports(DEFAULT_IMPORTS, component.get_imports())
  109. imports = {k: list(set(v)) for k, v in imports.items()}
  110. utils.validate_imports(imports)
  111. imports = utils.compile_imports(imports)
  112. # Compile the code to render the component.
  113. kwargs = {"state_name": state.get_name()} if state else {}
  114. return templates.PAGE.render(
  115. imports=imports,
  116. dynamic_imports=component.get_dynamic_imports(),
  117. custom_codes=component.get_custom_code(),
  118. hooks=component.get_hooks(),
  119. render=component.render(),
  120. **kwargs,
  121. )
  122. def compile_root_stylesheet(stylesheets: list[str]) -> tuple[str, str]:
  123. """Compile the root stylesheet.
  124. Args:
  125. stylesheets: The stylesheets to include in the root stylesheet.
  126. Returns:
  127. The path and code of the compiled root stylesheet.
  128. """
  129. output_path = utils.get_root_stylesheet_path()
  130. code = _compile_root_stylesheet(stylesheets)
  131. return output_path, code
  132. def _compile_root_stylesheet(stylesheets: list[str]) -> str:
  133. """Compile the root stylesheet.
  134. Args:
  135. stylesheets: The stylesheets to include in the root stylesheet.
  136. Returns:
  137. The compiled root stylesheet.
  138. Raises:
  139. FileNotFoundError: If a specified stylesheet in assets directory does not exist.
  140. """
  141. # Add tailwind css if enabled.
  142. sheets = (
  143. [constants.Tailwind.ROOT_STYLE_PATH]
  144. if get_config().tailwind is not None
  145. else []
  146. )
  147. for stylesheet in stylesheets:
  148. if not utils.is_valid_url(stylesheet):
  149. # check if stylesheet provided exists.
  150. stylesheet_full_path = (
  151. Path.cwd() / constants.Dirs.APP_ASSETS / stylesheet.strip("/")
  152. )
  153. if not os.path.exists(stylesheet_full_path):
  154. raise FileNotFoundError(
  155. f"The stylesheet file {stylesheet_full_path} does not exist."
  156. )
  157. stylesheet = f"@/{stylesheet.strip('/')}"
  158. sheets.append(stylesheet) if stylesheet not in sheets else None
  159. return templates.STYLE.render(stylesheets=sheets)
  160. def _compile_component(component: Component) -> str:
  161. """Compile a single component.
  162. Args:
  163. component: The component to compile.
  164. Returns:
  165. The compiled component.
  166. """
  167. return templates.COMPONENT.render(component=component)
  168. def _compile_components(components: set[CustomComponent]) -> str:
  169. """Compile the components.
  170. Args:
  171. components: The components to compile.
  172. Returns:
  173. The compiled components.
  174. """
  175. imports = {
  176. "react": [ImportVar(tag="memo")],
  177. f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="E"), ImportVar(tag="isTrue")],
  178. }
  179. component_renders = []
  180. # Compile each component.
  181. for component in components:
  182. component_render, component_imports = utils.compile_custom_component(component)
  183. component_renders.append(component_render)
  184. imports = utils.merge_imports(imports, component_imports)
  185. # Compile the components page.
  186. return templates.COMPONENTS.render(
  187. imports=utils.compile_imports(imports),
  188. components=component_renders,
  189. )
  190. def _compile_tailwind(
  191. config: dict,
  192. ) -> str:
  193. """Compile the Tailwind config.
  194. Args:
  195. config: The Tailwind config.
  196. Returns:
  197. The compiled Tailwind config.
  198. """
  199. return templates.TAILWIND_CONFIG.render(
  200. **config,
  201. )
  202. def compile_document_root(head_components: list[Component]) -> tuple[str, str]:
  203. """Compile the document root.
  204. Args:
  205. head_components: The components to include in the head.
  206. Returns:
  207. The path and code of the compiled document root.
  208. """
  209. # Get the path for the output file.
  210. output_path = utils.get_page_path(constants.PageNames.DOCUMENT_ROOT)
  211. # Create the document root.
  212. document_root = utils.create_document_root(head_components)
  213. # Compile the document root.
  214. code = _compile_document_root(document_root)
  215. return output_path, code
  216. def compile_app(app_root: Component) -> tuple[str, str]:
  217. """Compile the app root.
  218. Args:
  219. app_root: The app root component to compile.
  220. Returns:
  221. The path and code of the compiled app wrapper.
  222. """
  223. # Get the path for the output file.
  224. output_path = utils.get_page_path(constants.PageNames.APP_ROOT)
  225. # Compile the document root.
  226. code = _compile_app(app_root)
  227. return output_path, code
  228. def compile_theme(style: ComponentStyle) -> tuple[str, str]:
  229. """Compile the theme.
  230. Args:
  231. style: The style to compile.
  232. Returns:
  233. The path and code of the compiled theme.
  234. """
  235. output_path = utils.get_theme_path()
  236. # Create the theme.
  237. theme = utils.create_theme(style)
  238. # Compile the theme.
  239. code = _compile_theme(theme)
  240. return output_path, code
  241. def compile_contexts(state: Optional[Type[State]]) -> tuple[str, str]:
  242. """Compile the initial state / context.
  243. Args:
  244. state: The app state.
  245. Returns:
  246. The path and code of the compiled context.
  247. """
  248. # Get the path for the output file.
  249. output_path = utils.get_context_path()
  250. return output_path, _compile_contexts(state)
  251. def compile_page(
  252. path: str, component: Component, state: Type[State]
  253. ) -> tuple[str, str]:
  254. """Compile a single page.
  255. Args:
  256. path: The path to compile the page to.
  257. component: The component to compile.
  258. state: The app state.
  259. Returns:
  260. The path and code of the compiled page.
  261. """
  262. # Get the path for the output file.
  263. output_path = utils.get_page_path(path)
  264. # Add the style to the component.
  265. code = _compile_page(component, state)
  266. return output_path, code
  267. def compile_components(components: set[CustomComponent]):
  268. """Compile the custom components.
  269. Args:
  270. components: The custom components to compile.
  271. Returns:
  272. The path and code of the compiled components.
  273. """
  274. # Get the path for the output file.
  275. output_path = utils.get_components_path()
  276. # Compile the components.
  277. code = _compile_components(components)
  278. return output_path, code
  279. def compile_tailwind(
  280. config: dict,
  281. ):
  282. """Compile the Tailwind config.
  283. Args:
  284. config: The Tailwind config.
  285. Returns:
  286. The compiled Tailwind config.
  287. """
  288. # Get the path for the output file.
  289. output_path = constants.Tailwind.CONFIG
  290. # Compile the config.
  291. code = _compile_tailwind(config)
  292. return output_path, code
  293. def purge_web_pages_dir():
  294. """Empty out .web directory."""
  295. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])