dependencies.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import json
  2. import logging
  3. from pathlib import Path
  4. from typing import Any, Dict, List, Set, Tuple
  5. import vbuild
  6. from . import __version__
  7. from .element import Element
  8. vue_components: Dict[str, Any] = {}
  9. js_components: Dict[str, Any] = {}
  10. libraries: Dict[str, Any] = {}
  11. def register_vue_component(location: Path,
  12. base_path: Path = Path(__file__).parent / 'elements', *, expose: bool = False
  13. ) -> str:
  14. """Register a .vue or .js Vue component.
  15. :param location: the location to the library you want to register relative to the base_path. This is also used as the resource identifier and must therefore be url-safe.
  16. :param base_path: the base path where your libraries are located
  17. :return: the resource identifier library name to be used in element's `use_component`
  18. """
  19. if isinstance(location, str):
  20. logging.warning('register_vue_component: location is a string, did you mean to use register_library?')
  21. return
  22. suffix = location.suffix.lower()
  23. assert suffix in {'.vue', '.js', '.mjs'}, 'Only VUE and JS components are supported.'
  24. name = location.stem
  25. path = base_path / location
  26. if suffix == '.vue':
  27. assert name not in vue_components, f'Duplicate VUE component name {name}'
  28. # The component (in case of .vue) is built right away to:
  29. # 1. delegate this "long" process to the bootstrap phase
  30. # 2. avoid building the component on every single request
  31. vue_components[name] = vbuild.VBuild(name, path.read_text())
  32. elif suffix == '.js':
  33. assert name not in js_components, f'Duplicate JS component name {name}'
  34. js_components[str(location)] = {'name': name, 'path': path}
  35. return str(location)
  36. def register_library(location: Path,
  37. base_path: Path = Path(__file__).parent / 'elements' / 'lib', *, expose: bool = False
  38. ) -> str:
  39. """Register a new external library.
  40. :param location: the location to the library you want to register relative to the base_path. This is also used as the resource identifier and must therefore be url-safe.
  41. :param base_path: the base path where your libraries are located
  42. :param expose: if True, this will be exposed as an ESM module but NOT imported
  43. :return: the resource identifier library name to be used in element's `use_library`
  44. """
  45. if isinstance(location, str):
  46. return
  47. assert location.suffix == '.js' or location.suffix == '.mjs', 'Only JS dependencies are supported.'
  48. name = str(location)
  49. assert name not in libraries, f'Duplicate js library name {name}'
  50. libraries[name] = {'name': name, 'path': base_path / location, 'expose': expose}
  51. return name
  52. def generate_resources(prefix: str, elements: List[Element]) -> Tuple[str, str, str, str, str]:
  53. done_libraries: Set[str] = set()
  54. done_components: Set[str] = set()
  55. vue_scripts = ''
  56. vue_html = ''
  57. vue_styles = ''
  58. js_imports = ''
  59. import_maps = {'imports': {}}
  60. # Build the importmap structure for exposed libraries.
  61. for resource in libraries:
  62. if resource not in done_libraries and libraries[resource]['expose']:
  63. name = libraries[resource]['name']
  64. import_maps['imports'][name] = f'{prefix}/_nicegui/{__version__}/library/{resource}'
  65. done_libraries.add(resource)
  66. # Build the none optimized component (ie, the vue component).
  67. for resource in vue_components:
  68. if resource not in done_components:
  69. vue_html += f'{vue_components[resource].html}\n'
  70. vue_scripts += f'{vue_components[resource].script.replace("Vue.component", "app.component", 1)}\n'
  71. vue_styles += f'{vue_components[resource].style}\n'
  72. done_components.add(resource)
  73. # Build the resources associated with the elements.
  74. for element in elements:
  75. for resource in element.libraries:
  76. if resource in libraries and resource not in done_libraries:
  77. if not libraries[resource]['expose']:
  78. js_imports += f'import "{prefix}/_nicegui/{__version__}/library/{resource}";\n'
  79. done_libraries.add(resource)
  80. for resource in element.components:
  81. if resource in js_components and resource not in done_components:
  82. name = js_components[resource]['name']
  83. var = name.replace('-', '_')
  84. js_imports += f'import {{ default as {var} }} from "{prefix}/_nicegui/{__version__}/components/{resource}";\n'
  85. js_imports += f'app.component("{name}", {var});\n'
  86. done_components.add(resource)
  87. vue_styles = f'<style>{vue_styles}</style>'
  88. import_maps = f'<script type="importmap">{json.dumps(import_maps)}</script>'
  89. return vue_html, vue_styles, vue_scripts, import_maps, js_imports