Source code for litestar_vite.fragments._response

"""ComponentResponse for server-rendered UI component fragments."""

import contextlib
from typing import TYPE_CHECKING, Any, Literal, cast

from litestar.enums import MediaType
from litestar.exceptions import ImproperlyConfiguredException
from litestar.response.base import ASGIResponse, Response
from litestar.status_codes import HTTP_200_OK

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from litestar.background_tasks import BackgroundTask, BackgroundTasks
    from litestar.connection import Request
    from litestar.types import ResponseCookies, ResponseHeaders

__all__ = ("ComponentResponse",)


[docs] class ComponentResponse(Response[str]): """HTTP Response that renders a server-side component fragment. Renders UI component fragments (React, Vue, Svelte, Astro) into HTML markup for HTMX partial swaps and micro-frontend architectures. """ __slots__ = ("component", "mode", "props")
[docs] def __init__( self, component: str, props: dict[str, Any] | None = None, mode: Literal["static", "island"] = "static", status_code: int = HTTP_200_OK, headers: "ResponseHeaders | None" = None, cookies: "ResponseCookies | None" = None, background: "BackgroundTask | BackgroundTasks | None" = None, encoding: str = "utf-8", ) -> None: """Initialize the ComponentResponse. Args: component: Path to the component entrypoint. props: Optional dictionary of component props. mode: Rendering mode ('static' for zero JS or 'island' for interactive). status_code: HTTP response status code, defaults to 200. headers: Optional dictionary or sequence of response headers. cookies: Optional response cookies. background: Optional background task to execute after response completion. encoding: Content character encoding, defaults to 'utf-8'. """ super().__init__( content="", status_code=status_code, headers=headers, cookies=cookies, background=background, encoding=encoding, media_type=MediaType.HTML, ) self.component = component self.props = props or {} self.mode = mode
[docs] def to_asgi_response( self, app: Any, request: "Request[Any, Any, Any]", *, background: "BackgroundTask | BackgroundTasks | None" = None, cookies: Any = None, encoded_headers: Any = None, headers: dict[str, str] | None = None, is_head_response: bool = False, media_type: Any = None, status_code: int | None = None, type_encoders: Any = None, ) -> ASGIResponse: """Construct the ASGI response that renders the component fragment. Args: app: The Litestar application instance. request: The incoming request. background: Optional background tasks. cookies: Optional cookies. encoded_headers: Optional encoded headers. headers: Optional headers dictionary. is_head_response: Whether the request is a HEAD request. media_type: Response media type. status_code: Optional status code override. type_encoders: Type encoders mapping. Returns: An ASGIResponse callable that executes the fragment rendering. """ from litestar_vite.plugin import VitePlugin resolved_headers = self.headers if headers is None else {**headers, **self.headers} resolved_status = status_code if status_code is not None else self.status_code async def asgi_app(scope: Any, receive: Any, send: Any) -> None: plugins: Any = getattr(request.app, "plugins", None) vite_plugin: Any = None if plugins is not None: with contextlib.suppress(KeyError, AttributeError): vite_plugin = plugins.get(VitePlugin) if vite_plugin is None: with contextlib.suppress(KeyError, AttributeError): vite_plugin = plugins.get("VitePlugin") if vite_plugin is None: msg = "VitePlugin must be registered on the application to render ComponentResponse." raise ImproperlyConfiguredException(msg) rendered_html: str = "" render_fn: Any = getattr(vite_plugin, "render_fragment", None) if callable(render_fn): coro = cast("Callable[..., Awaitable[Any]]", render_fn)( component=self.component, props=self.props, mode=self.mode ) rendered_html = str(await coro) else: engine_obj: Any = getattr(vite_plugin, "fragment_engine", None) engine_render_fn: Any = getattr(engine_obj, "render_fragment", None) if engine_obj else None if callable(engine_render_fn): coro = cast("Callable[..., Awaitable[Any]]", engine_render_fn)( component=self.component, props=self.props, mode=self.mode ) rendered_html = str(await coro) else: msg = "Active VitePlugin does not support fragment rendering." raise ImproperlyConfiguredException(msg) body_bytes = rendered_html.encode(self.encoding) inner_response = ASGIResponse( body=body_bytes, status_code=resolved_status, headers=resolved_headers, cookies=cookies or self.cookies, encoded_headers=encoded_headers, encoding=self.encoding, media_type=MediaType.HTML, is_head_response=is_head_response, background=background or self.background, ) await inner_response(scope, receive, send) return cast("ASGIResponse", asgi_app)