compiler.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. """Compiler for the pynecone apps."""
  2. from __future__ import annotations
  3. import json
  4. from functools import wraps
  5. from typing import TYPE_CHECKING, Callable, List, Set, Tuple, Type
  6. from pynecone import constants
  7. from pynecone.compiler import templates, utils
  8. from pynecone.components.component import Component, CustomComponent, ImportDict
  9. from pynecone.state import State
  10. from pynecone.style import Style
  11. # Imports to be included in every Pynecone app.
  12. DEFAULT_IMPORTS: ImportDict = {
  13. "react": {"useEffect", "useRef", "useState"},
  14. "next/router": {"useRouter"},
  15. f"/{constants.STATE_PATH}": {"connect", "updateState", "E"},
  16. "": {"focus-visible/dist/focus-visible"},
  17. "@chakra-ui/react": {"useColorMode"},
  18. }
  19. def _compile_document_root(root: Component) -> str:
  20. """Compile the document root.
  21. Args:
  22. root: The document root to compile.
  23. Returns:
  24. The compiled document root.
  25. """
  26. return templates.DOCUMENT_ROOT(
  27. imports=utils.compile_imports(root.get_imports()),
  28. document=root.render(),
  29. )
  30. def _compile_theme(theme: dict) -> str:
  31. """Compile the theme.
  32. Args:
  33. theme: The theme to compile.
  34. Returns:
  35. The compiled theme.
  36. """
  37. return templates.THEME(theme=json.dumps(theme))
  38. def _compile_page(component: Component, state: Type[State]) -> str:
  39. """Compile the component given the app state.
  40. Args:
  41. component: The component to compile.
  42. state: The app state.
  43. Returns:
  44. The compiled component.
  45. """
  46. # Merge the default imports with the app-specific imports.
  47. imports = utils.merge_imports(DEFAULT_IMPORTS, component.get_imports())
  48. # Compile the code to render the component.
  49. return templates.PAGE(
  50. imports=utils.compile_imports(imports),
  51. custom_code=templates.join(component.get_custom_code()),
  52. constants=utils.compile_constants(),
  53. state=utils.compile_state(state),
  54. events=utils.compile_events(state),
  55. effects=utils.compile_effects(state),
  56. render=component.render(),
  57. )
  58. def _compile_components(components: Set[CustomComponent]) -> str:
  59. """Compile the components.
  60. Args:
  61. components: The components to compile.
  62. Returns:
  63. The compiled components.
  64. """
  65. imports = {
  66. "react": {"memo"},
  67. f"/{constants.STATE_PATH}": {"E"},
  68. }
  69. component_defs = []
  70. # Compile each component.
  71. for component in components:
  72. component_def, component_imports = utils.compile_custom_component(component)
  73. component_defs.append(component_def)
  74. imports = utils.merge_imports(imports, component_imports)
  75. # Compile the components page.
  76. return templates.COMPONENTS(
  77. imports=utils.compile_imports(imports),
  78. components=templates.join(component_defs),
  79. )
  80. def write_output(fn: Callable[..., Tuple[str, str]]):
  81. """Write the output of the function to a file.
  82. Args:
  83. fn: The function to decorate.
  84. Returns:
  85. The decorated function.
  86. """
  87. @wraps(fn)
  88. def wrapper(*args, write: bool = True) -> Tuple[str, str]:
  89. """Write the output of the function to a file.
  90. Args:
  91. *args: The arguments to pass to the function.
  92. write: Whether to write the output to a file.
  93. Returns:
  94. The path and code of the output.
  95. """
  96. path, code = fn(*args)
  97. if write:
  98. utils.write_page(path, code)
  99. return path, code
  100. return wrapper
  101. @write_output
  102. def compile_document_root(stylesheets: List[str]) -> Tuple[str, str]:
  103. """Compile the document root.
  104. Args:
  105. stylesheets: The stylesheets to include in the document root.
  106. Returns:
  107. The path and code of the compiled document root.
  108. """
  109. # Get the path for the output file.
  110. output_path = utils.get_page_path(constants.DOCUMENT_ROOT)
  111. # Create the document root.
  112. document_root = utils.create_document_root(stylesheets)
  113. # Compile the document root.
  114. code = _compile_document_root(document_root)
  115. return output_path, code
  116. @write_output
  117. def compile_theme(style: Style) -> Tuple[str, str]:
  118. """Compile the theme.
  119. Args:
  120. style: The style to compile.
  121. Returns:
  122. The path and code of the compiled theme.
  123. """
  124. output_path = utils.get_theme_path()
  125. # Create the theme.
  126. theme = utils.create_theme(style)
  127. # Compile the theme.
  128. code = _compile_theme(theme)
  129. return output_path, code
  130. @write_output
  131. def compile_page(
  132. path: str, component: Component, state: Type[State]
  133. ) -> Tuple[str, str]:
  134. """Compile a single page.
  135. Args:
  136. path: The path to compile the page to.
  137. component: The component to compile.
  138. state: The app state.
  139. Returns:
  140. The path and code of the compiled page.
  141. """
  142. # Get the path for the output file.
  143. output_path = utils.get_page_path(path)
  144. # Add the style to the component.
  145. code = _compile_page(component, state)
  146. return output_path, code
  147. @write_output
  148. def compile_components(components: Set[CustomComponent]):
  149. """Compile the custom components.
  150. Args:
  151. components: The custom components to compile.
  152. Returns:
  153. The path and code of the compiled components.
  154. """
  155. # Get the path for the output file.
  156. output_path = utils.get_components_path()
  157. # Compile the components.
  158. code = _compile_components(components)
  159. return output_path, code
  160. def purge_web_pages_dir():
  161. """Empty out .web directory."""
  162. template_files = ["_app.js", "404.js"]
  163. utils.empty_dir(constants.WEB_PAGES_DIR, keep_files=template_files)