|
@@ -1,5 +1,6 @@
|
|
from __future__ import annotations
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
+import hashlib
|
|
from dataclasses import dataclass
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Dict, List, Set, Tuple
|
|
from typing import TYPE_CHECKING, Dict, List, Set, Tuple
|
|
@@ -17,6 +18,7 @@ if TYPE_CHECKING:
|
|
class Component:
|
|
class Component:
|
|
key: str
|
|
key: str
|
|
name: str
|
|
name: str
|
|
|
|
+ path: Path
|
|
|
|
|
|
@property
|
|
@property
|
|
def tag(self) -> str:
|
|
def tag(self) -> str:
|
|
@@ -32,7 +34,7 @@ class VueComponent(Component):
|
|
|
|
|
|
@dataclass(**KWONLY_SLOTS)
|
|
@dataclass(**KWONLY_SLOTS)
|
|
class JsComponent(Component):
|
|
class JsComponent(Component):
|
|
- path: Path
|
|
|
|
|
|
+ pass
|
|
|
|
|
|
|
|
|
|
@dataclass(**KWONLY_SLOTS)
|
|
@dataclass(**KWONLY_SLOTS)
|
|
@@ -48,52 +50,59 @@ js_components: Dict[str, JsComponent] = {}
|
|
libraries: Dict[str, Library] = {}
|
|
libraries: Dict[str, Library] = {}
|
|
|
|
|
|
|
|
|
|
-def register_vue_component(location: Path, base_path: Path = Path(__file__).parent / 'elements') -> Component:
|
|
|
|
|
|
+def register_vue_component(path: Path) -> Component:
|
|
"""Register a .vue or .js Vue component.
|
|
"""Register a .vue or .js Vue component.
|
|
|
|
|
|
Single-file components (.vue) are built right away
|
|
Single-file components (.vue) are built right away
|
|
to delegate this "long" process to the bootstrap phase
|
|
to delegate this "long" process to the bootstrap phase
|
|
and to avoid building the component on every single request.
|
|
and to avoid building the component on every single request.
|
|
-
|
|
|
|
- :param location: location to the library relative to the base_path (used as the resource identifier, must be URL-safe)
|
|
|
|
- :param base_path: base path where your libraries are located
|
|
|
|
- :return: registered component
|
|
|
|
"""
|
|
"""
|
|
- path, key, name, suffix = deconstruct_location(location, base_path)
|
|
|
|
- if suffix == '.vue':
|
|
|
|
|
|
+ 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}'
|
|
assert key not in vue_components, f'Duplicate VUE component {key}'
|
|
- build = vbuild.VBuild(name, path.read_text())
|
|
|
|
- vue_components[key] = VueComponent(key=key, name=name, html=build.html, script=build.script, style=build.style)
|
|
|
|
|
|
+ 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]
|
|
return vue_components[key]
|
|
- if suffix == '.js':
|
|
|
|
|
|
+ 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}'
|
|
assert key not in js_components, f'Duplicate JS component {key}'
|
|
js_components[key] = JsComponent(key=key, name=name, path=path)
|
|
js_components[key] = JsComponent(key=key, name=name, path=path)
|
|
return js_components[key]
|
|
return js_components[key]
|
|
- raise ValueError(f'Unsupported component type "{suffix}"')
|
|
|
|
|
|
+ raise ValueError(f'Unsupported component type "{path.suffix}"')
|
|
|
|
|
|
|
|
|
|
-def register_library(location: Path, base_path: Path = Path(__file__).parent / 'elements' / 'lib', *,
|
|
|
|
- expose: bool = False) -> Library:
|
|
|
|
- """Register a *.js library.
|
|
|
|
-
|
|
|
|
- :param location: location to the library relative to the base_path (used as the resource identifier, must be URL-safe)
|
|
|
|
- :param base_path: base path where your libraries are located
|
|
|
|
- :param expose: whether to expose library as an ESM module (exposed modules will NOT be imported)
|
|
|
|
- :return: registered library
|
|
|
|
- """
|
|
|
|
- path, key, name, suffix = deconstruct_location(location, base_path)
|
|
|
|
- if suffix in {'.js', '.mjs'}:
|
|
|
|
|
|
+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}'
|
|
assert key not in libraries, f'Duplicate js library {key}'
|
|
libraries[key] = Library(key=key, name=name, path=path, expose=expose)
|
|
libraries[key] = Library(key=key, name=name, path=path, expose=expose)
|
|
return libraries[key]
|
|
return libraries[key]
|
|
- raise ValueError(f'Unsupported library type "{suffix}"')
|
|
|
|
|
|
+ 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'{hashlib.sha256(str(path.parent).encode()).hexdigest()}/{path.name}'
|
|
|
|
|
|
|
|
|
|
-def deconstruct_location(location: Path, base_path: Path) -> Tuple[Path, str, str, str]:
|
|
|
|
- """Deconstruct a location into its parts: full path, relative path, name, suffix."""
|
|
|
|
- abs_path = location if location.is_absolute() else base_path / location
|
|
|
|
- rel_path = location if not location.is_absolute() else location.relative_to(base_path)
|
|
|
|
- return abs_path, str(rel_path), location.name.split('.', 1)[0], location.suffix.lower()
|
|
|
|
|
|
+def get_name(path: Path) -> str:
|
|
|
|
+ return path.name.split('.', 1)[0]
|
|
|
|
|
|
|
|
|
|
def generate_resources(prefix: str, elements: List[Element]) -> Tuple[List[str],
|
|
def generate_resources(prefix: str, elements: List[Element]) -> Tuple[List[str],
|
|
@@ -129,13 +138,14 @@ def generate_resources(prefix: str, elements: List[Element]) -> Tuple[List[str],
|
|
for library in element.libraries:
|
|
for library in element.libraries:
|
|
if library.key not in done_libraries:
|
|
if library.key not in done_libraries:
|
|
if not library.expose:
|
|
if not library.expose:
|
|
- js_imports.append(f'import "{prefix}/_nicegui/{__version__}/libraries/{library.key}";')
|
|
|
|
|
|
+ url = f'{prefix}/_nicegui/{__version__}/libraries/{library.key}'
|
|
|
|
+ js_imports.append(f'import "{url}";')
|
|
done_libraries.add(library.key)
|
|
done_libraries.add(library.key)
|
|
- for component in element.components:
|
|
|
|
|
|
+ if element.component:
|
|
|
|
+ component = element.component
|
|
if component.key not in done_components and component.path.suffix.lower() == '.js':
|
|
if component.key not in done_components and component.path.suffix.lower() == '.js':
|
|
- js_imports.extend([
|
|
|
|
- f'import {{ default as {component.name} }} from "{prefix}/_nicegui/{__version__}/components/{component.key}";',
|
|
|
|
- f'app.component("{component.tag}", {component.name});',
|
|
|
|
- ])
|
|
|
|
|
|
+ url = f'{prefix}/_nicegui/{__version__}/components/{component.key}'
|
|
|
|
+ js_imports.append(f'import {{ default as {component.name} }} from "{url}";')
|
|
|
|
+ js_imports.append(f'app.component("{component.tag}", {component.name});')
|
|
done_components.add(component.key)
|
|
done_components.add(component.key)
|
|
return vue_html, vue_styles, vue_scripts, imports, js_imports
|
|
return vue_html, vue_styles, vue_scripts, imports, js_imports
|