You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
5.4 KiB
151 lines
5.4 KiB
from __future__ import annotations |
|
|
|
from dataclasses import dataclass |
|
from pathlib import Path |
|
from typing import TYPE_CHECKING, Dict, Iterable, List, Set, Tuple |
|
|
|
import vbuild |
|
|
|
from .dataclasses import KWONLY_SLOTS |
|
from .helpers import hash_file_path |
|
from .version import __version__ |
|
|
|
if TYPE_CHECKING: |
|
from .element import Element |
|
|
|
|
|
@dataclass(**KWONLY_SLOTS) |
|
class Component: |
|
key: str |
|
name: str |
|
path: Path |
|
|
|
@property |
|
def tag(self) -> str: |
|
return f'nicegui-{self.name}' |
|
|
|
|
|
@dataclass(**KWONLY_SLOTS) |
|
class VueComponent(Component): |
|
html: str |
|
script: str |
|
style: str |
|
|
|
|
|
@dataclass(**KWONLY_SLOTS) |
|
class JsComponent(Component): |
|
pass |
|
|
|
|
|
@dataclass(**KWONLY_SLOTS) |
|
class Library: |
|
key: str |
|
name: str |
|
path: Path |
|
expose: bool |
|
|
|
|
|
vue_components: Dict[str, VueComponent] = {} |
|
js_components: Dict[str, JsComponent] = {} |
|
libraries: Dict[str, Library] = {} |
|
|
|
|
|
def register_vue_component(path: Path) -> Component: |
|
"""Register a .vue or .js Vue component. |
|
|
|
Single-file components (.vue) are built right away |
|
to delegate this "long" process to the bootstrap phase |
|
and to avoid building the component on every single request. |
|
""" |
|
key = compute_key(path) |
|
name = get_name(path) |
|
if path.suffix == '.vue': |
|
if key in vue_components and vue_components[key].path == path: |
|
return vue_components[key] |
|
assert key not in vue_components, f'Duplicate VUE component {key}' |
|
v = vbuild.VBuild(name, path.read_text()) |
|
vue_components[key] = VueComponent(key=key, name=name, path=path, html=v.html, script=v.script, style=v.style) |
|
return vue_components[key] |
|
if path.suffix == '.js': |
|
if key in js_components and js_components[key].path == path: |
|
return js_components[key] |
|
assert key not in js_components, f'Duplicate JS component {key}' |
|
js_components[key] = JsComponent(key=key, name=name, path=path) |
|
return js_components[key] |
|
raise ValueError(f'Unsupported component type "{path.suffix}"') |
|
|
|
|
|
def register_library(path: Path, *, expose: bool = False) -> Library: |
|
"""Register a *.js library.""" |
|
key = compute_key(path) |
|
name = get_name(path) |
|
if path.suffix in {'.js', '.mjs'}: |
|
if key in libraries and libraries[key].path == path: |
|
return libraries[key] |
|
assert key not in libraries, f'Duplicate js library {key}' |
|
libraries[key] = Library(key=key, name=name, path=path, expose=expose) |
|
return libraries[key] |
|
raise ValueError(f'Unsupported library type "{path.suffix}"') |
|
|
|
|
|
def compute_key(path: Path) -> str: |
|
"""Compute a key for a given path using a hash function. |
|
|
|
If the path is relative to the NiceGUI base directory, the key is computed from the relative path. |
|
""" |
|
nicegui_base = Path(__file__).parent |
|
try: |
|
path = path.relative_to(nicegui_base) |
|
except ValueError: |
|
pass |
|
return f'{hash_file_path(path.parent)}/{path.name}' |
|
|
|
|
|
def get_name(path: Path) -> str: |
|
return path.name.split('.', 1)[0] |
|
|
|
|
|
def generate_resources(prefix: str, elements: Iterable[Element]) -> Tuple[List[str], |
|
List[str], |
|
List[str], |
|
Dict[str, str], |
|
List[str]]: |
|
done_libraries: Set[str] = set() |
|
done_components: Set[str] = set() |
|
vue_scripts: List[str] = [] |
|
vue_html: List[str] = [] |
|
vue_styles: List[str] = [] |
|
js_imports: List[str] = [] |
|
imports: Dict[str, str] = {} |
|
|
|
# build the importmap structure for exposed libraries |
|
for key, library in libraries.items(): |
|
if key not in done_libraries and library.expose: |
|
imports[library.name] = f'{prefix}/_nicegui/{__version__}/libraries/{key}' |
|
done_libraries.add(key) |
|
|
|
# build the none-optimized component (i.e. the Vue component) |
|
for key, vue_component in vue_components.items(): |
|
if key not in done_components: |
|
vue_html.append(vue_component.html) |
|
vue_scripts.append(vue_component.script.replace(f"Vue.component('{vue_component.name}',", |
|
f"app.component('{vue_component.tag}',", 1)) |
|
vue_styles.append(vue_component.style) |
|
done_components.add(key) |
|
|
|
# build the resources associated with the elements |
|
for element in elements: |
|
for library in element.libraries: |
|
if library.key not in done_libraries: |
|
if not library.expose: |
|
url = f'{prefix}/_nicegui/{__version__}/libraries/{library.key}' |
|
js_imports.append(f'import "{url}";') |
|
done_libraries.add(library.key) |
|
if element.component: |
|
js_component = element.component |
|
if js_component.key not in done_components and js_component.path.suffix.lower() == '.js': |
|
url = f'{prefix}/_nicegui/{__version__}/components/{js_component.key}' |
|
js_imports.append(f'import {{ default as {js_component.name} }} from "{url}";') |
|
js_imports.append(f'app.component("{js_component.tag}", {js_component.name});') |
|
done_components.add(js_component.key) |
|
return vue_html, vue_styles, vue_scripts, imports, js_imports
|
|
|