dependencies.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from __future__ import annotations
  2. import hashlib
  3. from dataclasses import dataclass
  4. from pathlib import Path
  5. from typing import TYPE_CHECKING, Dict, List, Set, Tuple
  6. import vbuild
  7. from . import __version__
  8. from .helpers import KWONLY_SLOTS
  9. if TYPE_CHECKING:
  10. from .element import Element
  11. @dataclass(**KWONLY_SLOTS)
  12. class Component:
  13. key: str
  14. name: str
  15. path: Path
  16. @property
  17. def tag(self) -> str:
  18. return f'nicegui-{self.name}'
  19. @dataclass(**KWONLY_SLOTS)
  20. class VueComponent(Component):
  21. html: str
  22. script: str
  23. style: str
  24. @dataclass(**KWONLY_SLOTS)
  25. class JsComponent(Component):
  26. pass
  27. @dataclass(**KWONLY_SLOTS)
  28. class Library:
  29. key: str
  30. name: str
  31. path: Path
  32. expose: bool
  33. vue_components: Dict[str, VueComponent] = {}
  34. js_components: Dict[str, JsComponent] = {}
  35. libraries: Dict[str, Library] = {}
  36. def register_vue_component(path: Path) -> Component:
  37. """Register a .vue or .js Vue component.
  38. Single-file components (.vue) are built right away
  39. to delegate this "long" process to the bootstrap phase
  40. and to avoid building the component on every single request.
  41. """
  42. key = compute_key(path)
  43. name = get_name(path)
  44. if path.suffix == '.vue':
  45. if key in vue_components and vue_components[key].path == path:
  46. return vue_components[key]
  47. assert key not in vue_components, f'Duplicate VUE component {key}'
  48. v = vbuild.VBuild(name, path.read_text())
  49. vue_components[key] = VueComponent(key=key, name=name, path=path, html=v.html, script=v.script, style=v.style)
  50. return vue_components[key]
  51. if path.suffix == '.js':
  52. if key in js_components and js_components[key].path == path:
  53. return js_components[key]
  54. assert key not in js_components, f'Duplicate JS component {key}'
  55. js_components[key] = JsComponent(key=key, name=name, path=path)
  56. return js_components[key]
  57. raise ValueError(f'Unsupported component type "{path.suffix}"')
  58. def register_library(path: Path, *, expose: bool = False) -> Library:
  59. """Register a *.js library."""
  60. key = compute_key(path)
  61. name = get_name(path)
  62. if path.suffix in {'.js', '.mjs'}:
  63. if key in libraries and libraries[key].path == path:
  64. return libraries[key]
  65. assert key not in libraries, f'Duplicate js library {key}'
  66. libraries[key] = Library(key=key, name=name, path=path, expose=expose)
  67. return libraries[key]
  68. raise ValueError(f'Unsupported library type "{path.suffix}"')
  69. def compute_key(path: Path) -> str:
  70. """Compute a key for a given path using a hash function.
  71. If the path is relative to the NiceGUI base directory, the key is computed from the relative path.
  72. """
  73. nicegui_base = Path(__file__).parent
  74. if path.is_relative_to(nicegui_base):
  75. path = path.relative_to(nicegui_base)
  76. return f'{hashlib.sha256(str(path.parent).encode()).hexdigest()}/{path.name}'
  77. def get_name(path: Path) -> str:
  78. return path.name.split('.', 1)[0]
  79. def generate_resources(prefix: str, elements: List[Element]) -> Tuple[List[str],
  80. List[str],
  81. List[str],
  82. Dict[str, str],
  83. List[str]]:
  84. done_libraries: Set[str] = set()
  85. done_components: Set[str] = set()
  86. vue_scripts: List[str] = []
  87. vue_html: List[str] = []
  88. vue_styles: List[str] = []
  89. js_imports: List[str] = []
  90. imports: Dict[str, str] = {}
  91. # build the importmap structure for exposed libraries
  92. for key, library in libraries.items():
  93. if key not in done_libraries and library.expose:
  94. imports[library.name] = f'{prefix}/_nicegui/{__version__}/libraries/{key}'
  95. done_libraries.add(key)
  96. # build the none-optimized component (i.e. the Vue component)
  97. for key, component in vue_components.items():
  98. if key not in done_components:
  99. vue_html.append(component.html)
  100. vue_scripts.append(component.script.replace(f"Vue.component('{component.name}',",
  101. f"app.component('{component.tag}',", 1))
  102. vue_styles.append(component.style)
  103. done_components.add(key)
  104. # build the resources associated with the elements
  105. for element in elements:
  106. for library in element.libraries:
  107. if library.key not in done_libraries:
  108. if not library.expose:
  109. url = f'{prefix}/_nicegui/{__version__}/libraries/{library.key}'
  110. js_imports.append(f'import "{url}";')
  111. done_libraries.add(library.key)
  112. if element.component:
  113. component = element.component
  114. if component.key not in done_components and component.path.suffix.lower() == '.js':
  115. url = f'{prefix}/_nicegui/{__version__}/components/{component.key}'
  116. js_imports.append(f'import {{ default as {component.name} }} from "{url}";')
  117. js_imports.append(f'app.component("{component.tag}", {component.name});')
  118. done_components.add(component.key)
  119. return vue_html, vue_styles, vue_scripts, imports, js_imports