Simplify webui Vite asset injection

This commit is contained in:
Antti Kettunen 2026-04-07 21:10:23 +03:00
parent ed8ff46c9c
commit 736f243d5c
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7
2 changed files with 34 additions and 59 deletions

View file

@ -372,22 +372,14 @@ def _log_rejected_socketio_origin():
) )
# --- WebUI asset injection --- # --- WebUI asset injection ---
_webui_react_manifest = None _webui_vite_manifest = None
_webui_react_manifest_mtime = None _webui_vite_manifest_mtime = None
_webui_react_manifest_path = Path(base_dir) / 'webui' / 'static' / 'dist' / '.vite' / 'manifest.json' _webui_vite_entry = 'src/app/main.tsx'
_webui_vite_manifest_path = Path(base_dir) / 'webui' / 'static' / 'dist' / '.vite' / 'manifest.json'
_webui_vite_dev = os.environ.get('SOULSYNC_WEBUI_VITE_DEV', '').lower() in ('1', 'true', 'yes', 'on') _webui_vite_dev = os.environ.get('SOULSYNC_WEBUI_VITE_DEV', '').lower() in ('1', 'true', 'yes', 'on')
_webui_vite_url = os.environ.get('SOULSYNC_WEBUI_VITE_URL', 'http://127.0.0.1:5173').rstrip('/') _webui_vite_url = os.environ.get('SOULSYNC_WEBUI_VITE_URL', 'http://127.0.0.1:5173').rstrip('/')
_webui_vite_base = '/static/dist/' _webui_vite_base = '/static/dist/'
_webui_react_apps = {
'router': {
'entry': 'src/app/main.tsx',
'dev_entry': 'src/app/main.tsx',
'mount_id': None,
'mount_class': None,
},
}
_webui_client_route_map = { _webui_client_route_map = {
'/dashboard': 'dashboard', '/dashboard': 'dashboard',
'/sync': 'sync', '/sync': 'sync',
@ -449,53 +441,52 @@ def _should_serve_webui_spa(pathname: str) -> bool:
return not normalized.startswith(excluded_prefixes) return not normalized.startswith(excluded_prefixes)
def _load_webui_react_manifest(): def _webui_vite_dev_asset_url(asset_path: str) -> str:
global _webui_react_manifest, _webui_react_manifest_mtime return f'{_webui_vite_url}{_webui_vite_base.rstrip("/")}/{asset_path.lstrip("/")}'
def _load_webui_vite_manifest():
global _webui_vite_manifest, _webui_vite_manifest_mtime
manifest_mtime = None manifest_mtime = None
if _webui_react_manifest_path.exists(): if _webui_vite_manifest_path.exists():
try: try:
manifest_mtime = _webui_react_manifest_path.stat().st_mtime manifest_mtime = _webui_vite_manifest_path.stat().st_mtime
except OSError: except OSError:
manifest_mtime = None manifest_mtime = None
if _webui_react_manifest is not None and manifest_mtime == _webui_react_manifest_mtime: if _webui_vite_manifest is not None and manifest_mtime == _webui_vite_manifest_mtime:
return _webui_react_manifest return _webui_vite_manifest
if _webui_react_manifest_path.exists(): if _webui_vite_manifest_path.exists():
try: try:
with _webui_react_manifest_path.open('r', encoding='utf-8') as handle: with _webui_vite_manifest_path.open('r', encoding='utf-8') as handle:
_webui_react_manifest = json.load(handle) _webui_vite_manifest = json.load(handle)
_webui_react_manifest_mtime = manifest_mtime _webui_vite_manifest_mtime = manifest_mtime
except Exception as exc: except Exception as exc:
logger.warning(f"Failed to load webui manifest: {exc}") logger.warning(f"Failed to load webui manifest: {exc}")
_webui_react_manifest = {} _webui_vite_manifest = {}
_webui_react_manifest_mtime = manifest_mtime _webui_vite_manifest_mtime = manifest_mtime
else: else:
_webui_react_manifest = {} _webui_vite_manifest = {}
_webui_react_manifest_mtime = None _webui_vite_manifest_mtime = None
return _webui_react_manifest return _webui_vite_manifest
def _build_webui_react_assets(app_name='issues', placement='body'): def _build_webui_vite_assets(placement='body'):
app_config = _webui_react_apps.get(app_name)
if not app_config:
return ''
if placement not in ('head', 'body'): if placement not in ('head', 'body'):
return '' return ''
if _webui_vite_dev: if _webui_vite_dev:
if placement == 'head': if placement == 'head':
return '' return ''
vite_base_url = f'{_webui_vite_url}{_webui_vite_base.rstrip("/")}'
return '\n'.join([ return '\n'.join([
f'<script type="module" src="{vite_base_url}/@vite/client"></script>', f'<script type="module" src="{_webui_vite_dev_asset_url("@vite/client")}"></script>',
f'<script type="module" src="{vite_base_url}/{app_config["dev_entry"]}"></script>', f'<script type="module" src="{_webui_vite_dev_asset_url(_webui_vite_entry)}"></script>',
]) ])
manifest = _load_webui_react_manifest() manifest = _load_webui_vite_manifest()
entry = manifest.get(app_config['entry']) entry = manifest.get(_webui_vite_entry)
if not entry: if not entry:
return '' return ''
@ -506,30 +497,15 @@ def _build_webui_react_assets(app_name='issues', placement='body'):
return '\n'.join(head_assets) return '\n'.join(head_assets)
entry_file = entry.get('file') entry_file = entry.get('file')
if not entry_file:
return ''
return f'<script type="module" src="{url_for("static", filename=f"dist/{entry_file}")}"></script>' return f'<script type="module" src="{url_for("static", filename=f"dist/{entry_file}")}"></script>'
def _build_webui_react_root(app_name='issues'):
app_config = _webui_react_apps.get(app_name)
if not app_config:
return ''
mount_id = app_config.get('mount_id', f'{app_name}-app-root')
mount_class = app_config.get('mount_class', f'{app_name}-app-root')
if not mount_id:
return ''
return f'<div id="{mount_id}" class="{mount_class}" data-react-app="{app_name}"></div>'
@app.context_processor @app.context_processor
def inject_webui_assets(): def inject_webui_assets():
return { return {
'react_assets_for': lambda app_name='issues', placement='body': _build_webui_react_assets(app_name, placement), 'vite_assets': _build_webui_vite_assets,
'react_root': lambda app_name='issues': _build_webui_react_root(app_name),
# Transitional aliases for older templates while the route contract settles.
'react_head_assets': lambda app_name='issues': _build_webui_react_assets(app_name, 'head'),
'react_body_assets': lambda app_name='issues': _build_webui_react_assets(app_name, 'body'),
'react_mount': lambda app_name='issues': _build_webui_react_root(app_name),
} }
# --- Profile Context (before_request hook) --- # --- Profile Context (before_request hook) ---

View file

@ -12,7 +12,7 @@
<link rel="stylesheet" href="{{ url_for('static', filename='style.css', v=static_v) }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css', v=static_v) }}">
<link rel="stylesheet" href="{{ url_for('static', filename='mobile.css', v=static_v) }}"> <link rel="stylesheet" href="{{ url_for('static', filename='mobile.css', v=static_v) }}">
<link rel="stylesheet" href="{{ url_for('static', filename='setup-wizard.css', v=static_v) }}"> <link rel="stylesheet" href="{{ url_for('static', filename='setup-wizard.css', v=static_v) }}">
{{ react_assets_for('router', 'head')|safe }} {{ vite_assets('head')|safe }}
</head> </head>
<body> <body>
@ -7775,8 +7775,7 @@
<script src="{{ url_for('static', filename='vendor/socket.io.min.js', v=static_v) }}"></script> <script src="{{ url_for('static', filename='vendor/socket.io.min.js', v=static_v) }}"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
{{ react_assets_for('router', 'body')|safe }} {{ vite_assets('body')|safe }}
{{ react_assets_for('issues', 'body')|safe }}
<script src="{{ url_for('static', filename='setup-wizard.js', v=static_v) }}"></script> <script src="{{ url_for('static', filename='setup-wizard.js', v=static_v) }}"></script>
<!-- Split modules (was: script.js) — core.js must load first, init.js last --> <!-- Split modules (was: script.js) — core.js must load first, init.js last -->
<script src="{{ url_for('static', filename='core.js', v=static_v) }}"></script> <script src="{{ url_for('static', filename='core.js', v=static_v) }}"></script>