compiler.py 5.5 KB

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