litestar-vite 0.1.1__py3-none-any.whl → 0.15.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- litestar_vite/__init__.py +54 -4
- litestar_vite/__metadata__.py +12 -7
- litestar_vite/cli.py +1048 -10
- litestar_vite/codegen/__init__.py +48 -0
- litestar_vite/codegen/_export.py +229 -0
- litestar_vite/codegen/_inertia.py +619 -0
- litestar_vite/codegen/_openapi.py +280 -0
- litestar_vite/codegen/_routes.py +720 -0
- litestar_vite/codegen/_ts.py +235 -0
- litestar_vite/codegen/_utils.py +141 -0
- litestar_vite/commands.py +73 -0
- litestar_vite/config/__init__.py +997 -0
- litestar_vite/config/_constants.py +97 -0
- litestar_vite/config/_deploy.py +70 -0
- litestar_vite/config/_inertia.py +241 -0
- litestar_vite/config/_paths.py +63 -0
- litestar_vite/config/_runtime.py +235 -0
- litestar_vite/config/_spa.py +93 -0
- litestar_vite/config/_types.py +94 -0
- litestar_vite/deploy.py +366 -0
- litestar_vite/doctor.py +1181 -0
- litestar_vite/exceptions.py +78 -0
- litestar_vite/executor.py +360 -0
- litestar_vite/handler/__init__.py +9 -0
- litestar_vite/handler/_app.py +612 -0
- litestar_vite/handler/_routing.py +130 -0
- litestar_vite/html_transform.py +569 -0
- litestar_vite/inertia/__init__.py +77 -0
- litestar_vite/inertia/_utils.py +119 -0
- litestar_vite/inertia/exception_handler.py +178 -0
- litestar_vite/inertia/helpers.py +1571 -0
- litestar_vite/inertia/middleware.py +54 -0
- litestar_vite/inertia/plugin.py +199 -0
- litestar_vite/inertia/precognition.py +274 -0
- litestar_vite/inertia/request.py +334 -0
- litestar_vite/inertia/response.py +802 -0
- litestar_vite/inertia/types.py +335 -0
- litestar_vite/loader.py +464 -123
- litestar_vite/plugin/__init__.py +687 -0
- litestar_vite/plugin/_process.py +185 -0
- litestar_vite/plugin/_proxy.py +689 -0
- litestar_vite/plugin/_proxy_headers.py +244 -0
- litestar_vite/plugin/_static.py +37 -0
- litestar_vite/plugin/_utils.py +489 -0
- litestar_vite/py.typed +0 -0
- litestar_vite/scaffolding/__init__.py +20 -0
- litestar_vite/scaffolding/generator.py +270 -0
- litestar_vite/scaffolding/templates.py +437 -0
- litestar_vite/templates/__init__.py +0 -0
- litestar_vite/templates/addons/tailwindcss/tailwind.css.j2 +1 -0
- litestar_vite/templates/angular/index.html.j2 +12 -0
- litestar_vite/templates/angular/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/angular/package.json.j2 +36 -0
- litestar_vite/templates/angular/src/app/app.component.css.j2 +3 -0
- litestar_vite/templates/angular/src/app/app.component.html.j2 +1 -0
- litestar_vite/templates/angular/src/app/app.component.ts.j2 +9 -0
- litestar_vite/templates/angular/src/app/app.config.ts.j2 +5 -0
- litestar_vite/templates/angular/src/main.ts.j2 +9 -0
- litestar_vite/templates/angular/src/styles.css.j2 +9 -0
- litestar_vite/templates/angular/tsconfig.app.json.j2 +34 -0
- litestar_vite/templates/angular/tsconfig.json.j2 +20 -0
- litestar_vite/templates/angular/vite.config.ts.j2 +21 -0
- litestar_vite/templates/angular-cli/.postcssrc.json.j2 +5 -0
- litestar_vite/templates/angular-cli/angular.json.j2 +36 -0
- litestar_vite/templates/angular-cli/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/angular-cli/package.json.j2 +28 -0
- litestar_vite/templates/angular-cli/proxy.conf.json.j2 +18 -0
- litestar_vite/templates/angular-cli/src/app/app.component.css.j2 +3 -0
- litestar_vite/templates/angular-cli/src/app/app.component.html.j2 +1 -0
- litestar_vite/templates/angular-cli/src/app/app.component.ts.j2 +9 -0
- litestar_vite/templates/angular-cli/src/app/app.config.ts.j2 +5 -0
- litestar_vite/templates/angular-cli/src/index.html.j2 +13 -0
- litestar_vite/templates/angular-cli/src/main.ts.j2 +6 -0
- litestar_vite/templates/angular-cli/src/styles.css.j2 +10 -0
- litestar_vite/templates/angular-cli/tailwind.config.js.j2 +4 -0
- litestar_vite/templates/angular-cli/tsconfig.app.json.j2 +16 -0
- litestar_vite/templates/angular-cli/tsconfig.json.j2 +26 -0
- litestar_vite/templates/angular-cli/tsconfig.spec.json.j2 +9 -0
- litestar_vite/templates/astro/astro.config.mjs.j2 +28 -0
- litestar_vite/templates/astro/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/astro/src/layouts/Layout.astro.j2 +63 -0
- litestar_vite/templates/astro/src/pages/index.astro.j2 +36 -0
- litestar_vite/templates/astro/src/styles/global.css.j2 +1 -0
- litestar_vite/templates/base/.gitignore.j2 +42 -0
- litestar_vite/templates/base/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/base/package.json.j2 +39 -0
- litestar_vite/templates/base/resources/vite-env.d.ts.j2 +1 -0
- litestar_vite/templates/base/tsconfig.json.j2 +37 -0
- litestar_vite/templates/htmx/src/main.js.j2 +8 -0
- litestar_vite/templates/htmx/templates/base.html.j2.j2 +56 -0
- litestar_vite/templates/htmx/templates/index.html.j2.j2 +13 -0
- litestar_vite/templates/htmx/vite.config.ts.j2 +40 -0
- litestar_vite/templates/nuxt/app.vue.j2 +29 -0
- litestar_vite/templates/nuxt/composables/useApi.ts.j2 +33 -0
- litestar_vite/templates/nuxt/nuxt.config.ts.j2 +31 -0
- litestar_vite/templates/nuxt/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/nuxt/pages/index.vue.j2 +54 -0
- litestar_vite/templates/react/index.html.j2 +13 -0
- litestar_vite/templates/react/src/App.css.j2 +56 -0
- litestar_vite/templates/react/src/App.tsx.j2 +19 -0
- litestar_vite/templates/react/src/main.tsx.j2 +10 -0
- litestar_vite/templates/react/vite.config.ts.j2 +39 -0
- litestar_vite/templates/react-inertia/index.html.j2 +14 -0
- litestar_vite/templates/react-inertia/package.json.j2 +47 -0
- litestar_vite/templates/react-inertia/resources/App.css.j2 +68 -0
- litestar_vite/templates/react-inertia/resources/main.tsx.j2 +17 -0
- litestar_vite/templates/react-inertia/resources/pages/Home.tsx.j2 +18 -0
- litestar_vite/templates/react-inertia/resources/ssr.tsx.j2 +19 -0
- litestar_vite/templates/react-inertia/vite.config.ts.j2 +59 -0
- litestar_vite/templates/react-router/index.html.j2 +12 -0
- litestar_vite/templates/react-router/src/App.css.j2 +17 -0
- litestar_vite/templates/react-router/src/App.tsx.j2 +7 -0
- litestar_vite/templates/react-router/src/main.tsx.j2 +10 -0
- litestar_vite/templates/react-router/vite.config.ts.j2 +39 -0
- litestar_vite/templates/react-tanstack/index.html.j2 +12 -0
- litestar_vite/templates/react-tanstack/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/react-tanstack/src/App.css.j2 +17 -0
- litestar_vite/templates/react-tanstack/src/main.tsx.j2 +21 -0
- litestar_vite/templates/react-tanstack/src/routeTree.gen.ts.j2 +7 -0
- litestar_vite/templates/react-tanstack/src/routes/__root.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/src/routes/books.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/src/routes/index.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/vite.config.ts.j2 +39 -0
- litestar_vite/templates/svelte/index.html.j2 +13 -0
- litestar_vite/templates/svelte/src/App.svelte.j2 +30 -0
- litestar_vite/templates/svelte/src/app.css.j2 +45 -0
- litestar_vite/templates/svelte/src/main.ts.j2 +8 -0
- litestar_vite/templates/svelte/src/vite-env.d.ts.j2 +2 -0
- litestar_vite/templates/svelte/svelte.config.js.j2 +5 -0
- litestar_vite/templates/svelte/vite.config.ts.j2 +39 -0
- litestar_vite/templates/svelte-inertia/index.html.j2 +14 -0
- litestar_vite/templates/svelte-inertia/resources/app.css.j2 +21 -0
- litestar_vite/templates/svelte-inertia/resources/main.ts.j2 +11 -0
- litestar_vite/templates/svelte-inertia/resources/pages/Home.svelte.j2 +43 -0
- litestar_vite/templates/svelte-inertia/resources/vite-env.d.ts.j2 +2 -0
- litestar_vite/templates/svelte-inertia/svelte.config.js.j2 +5 -0
- litestar_vite/templates/svelte-inertia/vite.config.ts.j2 +37 -0
- litestar_vite/templates/sveltekit/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/sveltekit/src/app.css.j2 +40 -0
- litestar_vite/templates/sveltekit/src/app.html.j2 +12 -0
- litestar_vite/templates/sveltekit/src/hooks.server.ts.j2 +55 -0
- litestar_vite/templates/sveltekit/src/routes/+layout.svelte.j2 +12 -0
- litestar_vite/templates/sveltekit/src/routes/+page.svelte.j2 +34 -0
- litestar_vite/templates/sveltekit/svelte.config.js.j2 +12 -0
- litestar_vite/templates/sveltekit/tsconfig.json.j2 +14 -0
- litestar_vite/templates/sveltekit/vite.config.ts.j2 +31 -0
- litestar_vite/templates/vue/env.d.ts.j2 +7 -0
- litestar_vite/templates/vue/index.html.j2 +13 -0
- litestar_vite/templates/vue/src/App.vue.j2 +28 -0
- litestar_vite/templates/vue/src/main.ts.j2 +5 -0
- litestar_vite/templates/vue/src/style.css.j2 +45 -0
- litestar_vite/templates/vue/vite.config.ts.j2 +39 -0
- litestar_vite/templates/vue-inertia/env.d.ts.j2 +7 -0
- litestar_vite/templates/vue-inertia/index.html.j2 +14 -0
- litestar_vite/templates/vue-inertia/package.json.j2 +50 -0
- litestar_vite/templates/vue-inertia/resources/main.ts.j2 +18 -0
- litestar_vite/templates/vue-inertia/resources/pages/Home.vue.j2 +22 -0
- litestar_vite/templates/vue-inertia/resources/ssr.ts.j2 +21 -0
- litestar_vite/templates/vue-inertia/resources/style.css.j2 +21 -0
- litestar_vite/templates/vue-inertia/vite.config.ts.j2 +59 -0
- litestar_vite-0.15.0.dist-info/METADATA +230 -0
- litestar_vite-0.15.0.dist-info/RECORD +164 -0
- {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0.dist-info}/WHEEL +1 -1
- litestar_vite/config.py +0 -100
- litestar_vite/plugin.py +0 -45
- litestar_vite/template_engine.py +0 -103
- litestar_vite-0.1.1.dist-info/METADATA +0 -68
- litestar_vite-0.1.1.dist-info/RECORD +0 -11
- {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0.dist-info}/licenses/LICENSE +0 -0
litestar_vite/loader.py
CHANGED
|
@@ -1,193 +1,534 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""Vite Asset Loader.
|
|
2
|
+
|
|
3
|
+
This module provides the ViteAssetLoader class for loading and rendering
|
|
4
|
+
Vite-managed assets. The loader handles both development mode (with HMR)
|
|
5
|
+
and production mode (with manifest-based asset resolution).
|
|
6
|
+
|
|
7
|
+
Key features:
|
|
8
|
+
- Async initialization for non-blocking I/O during app startup
|
|
9
|
+
- Manifest parsing for production asset resolution
|
|
10
|
+
- HMR client script generation for development
|
|
11
|
+
- React Fast Refresh support
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import hashlib
|
|
15
|
+
import html
|
|
16
|
+
from functools import cached_property
|
|
4
17
|
from pathlib import Path
|
|
5
|
-
from
|
|
18
|
+
from textwrap import dedent
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
6
20
|
from urllib.parse import urljoin
|
|
7
21
|
|
|
8
|
-
|
|
22
|
+
import anyio
|
|
23
|
+
import markupsafe
|
|
24
|
+
from litestar.exceptions import SerializationException
|
|
25
|
+
from litestar.serialization import decode_json
|
|
26
|
+
|
|
27
|
+
from litestar_vite.exceptions import AssetNotFoundError, ManifestNotFoundError
|
|
9
28
|
|
|
10
29
|
if TYPE_CHECKING:
|
|
30
|
+
from collections.abc import Mapping
|
|
31
|
+
|
|
32
|
+
from litestar.connection import Request
|
|
33
|
+
|
|
11
34
|
from litestar_vite.config import ViteConfig
|
|
35
|
+
from litestar_vite.plugin import VitePlugin
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _get_request_from_context(context: "Mapping[str, Any]") -> "Request[Any, Any, Any]":
|
|
39
|
+
"""Get the request from the template context.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
context: The template context.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The request object from the template context.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If 'request' is not found in the template context.
|
|
49
|
+
TypeError: If 'request' is not a Litestar Request object.
|
|
50
|
+
"""
|
|
51
|
+
from litestar.connection import Request
|
|
52
|
+
|
|
53
|
+
request = context.get("request")
|
|
54
|
+
if request is None:
|
|
55
|
+
msg = "Request not found in template context. Ensure 'request' is passed to the template."
|
|
56
|
+
raise ValueError(msg)
|
|
57
|
+
if not isinstance(request, Request): # pyright: ignore[reportUnknownVariableType]
|
|
58
|
+
msg = f"Expected Request object, got {type(request)}"
|
|
59
|
+
raise TypeError(msg)
|
|
60
|
+
return request # pyright: ignore[reportReturnType,reportUnknownVariableType]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _get_vite_plugin(context: "Mapping[str, Any]") -> "VitePlugin | None":
|
|
64
|
+
"""Return the VitePlugin from the template context, if registered.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The VitePlugin instance, or None if not registered.
|
|
68
|
+
"""
|
|
69
|
+
request = _get_request_from_context(context)
|
|
70
|
+
return request.app.plugins.get("VitePlugin")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def render_hmr_client(context: "Mapping[str, Any]", /) -> "markupsafe.Markup":
|
|
74
|
+
"""Render the HMR client script tag.
|
|
75
|
+
|
|
76
|
+
This is a Jinja2 template callable that renders the Vite HMR client
|
|
77
|
+
script tag for development mode.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
context: The template context containing the request.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
HTML markup for the HMR client script, or empty markup if
|
|
84
|
+
VitePlugin is not registered.
|
|
85
|
+
"""
|
|
86
|
+
vite_plugin = _get_vite_plugin(context)
|
|
87
|
+
if vite_plugin is None:
|
|
88
|
+
return markupsafe.Markup("")
|
|
89
|
+
return vite_plugin.asset_loader.render_hmr_client()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def render_asset_tag(
|
|
93
|
+
context: "Mapping[str, Any]", /, path: "str | list[str]", scripts_attrs: "dict[str, str] | None" = None
|
|
94
|
+
) -> "markupsafe.Markup":
|
|
95
|
+
"""Render asset tags for the specified path(s).
|
|
96
|
+
|
|
97
|
+
This is a Jinja2 template callable that renders script/link tags
|
|
98
|
+
for Vite-managed assets. Also works for HTMX partial responses.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
context: The template context containing the request.
|
|
102
|
+
path: Single path or list of paths to assets.
|
|
103
|
+
scripts_attrs: Optional attributes for script tags.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
HTML markup for the asset tags, or empty markup if VitePlugin
|
|
107
|
+
is not registered.
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
In a Jinja2 template:
|
|
111
|
+
{{ vite_asset("src/main.ts") }}
|
|
112
|
+
{{ vite_asset("src/components/UserProfile.tsx") }}
|
|
113
|
+
"""
|
|
114
|
+
vite_plugin = _get_vite_plugin(context)
|
|
115
|
+
if vite_plugin is None:
|
|
116
|
+
return markupsafe.Markup("")
|
|
117
|
+
return vite_plugin.asset_loader.render_asset_tag(path, scripts_attrs)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def render_static_asset(context: "Mapping[str, Any]", /, path: str) -> str:
|
|
121
|
+
"""Render a static asset URL.
|
|
122
|
+
|
|
123
|
+
This is a Jinja2 template callable that returns the URL for a static asset.
|
|
12
124
|
|
|
13
|
-
|
|
125
|
+
Args:
|
|
126
|
+
context: The template context containing the request.
|
|
127
|
+
path: Path to the static asset.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The full URL to the static asset, or empty string if VitePlugin
|
|
131
|
+
is not registered.
|
|
132
|
+
"""
|
|
133
|
+
vite_plugin = _get_vite_plugin(context)
|
|
134
|
+
if vite_plugin is None:
|
|
135
|
+
return ""
|
|
136
|
+
return vite_plugin.asset_loader.get_static_asset(path)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def render_routes(
|
|
140
|
+
context: "Mapping[str, Any]",
|
|
141
|
+
/,
|
|
142
|
+
*,
|
|
143
|
+
only: "list[str] | None" = None,
|
|
144
|
+
exclude: "list[str] | None" = None,
|
|
145
|
+
include_components: bool = False,
|
|
146
|
+
) -> "markupsafe.Markup":
|
|
147
|
+
"""Render inline script tag with route definitions.
|
|
148
|
+
|
|
149
|
+
This is a Jinja2 template callable that renders an inline script tag
|
|
150
|
+
containing route metadata for client-side type-safe routing.
|
|
151
|
+
|
|
152
|
+
The script defines a global `window.Litestar.routes` object that can be
|
|
153
|
+
used by frontend routers.
|
|
154
|
+
|
|
155
|
+
Uses Litestar's built-in serializers, picking up any custom type encoders
|
|
156
|
+
configured on the app.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
context: The template context containing the request.
|
|
160
|
+
only: Optional list of route patterns to include.
|
|
161
|
+
exclude: Optional list of route patterns to exclude.
|
|
162
|
+
include_components: Include Inertia component names.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
HTML markup for the inline routes script containing route metadata
|
|
166
|
+
as a JSON object.
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
In a Jinja2 template:
|
|
170
|
+
{{ vite_routes() }}
|
|
171
|
+
{{ vite_routes(exclude=['/api/internal']) }}
|
|
172
|
+
"""
|
|
173
|
+
from litestar.serialization import encode_json, get_serializer
|
|
174
|
+
|
|
175
|
+
from litestar_vite.codegen import generate_routes_json
|
|
176
|
+
|
|
177
|
+
request = _get_request_from_context(context)
|
|
178
|
+
app = request.app
|
|
179
|
+
|
|
180
|
+
routes_data = generate_routes_json(app, only=only, exclude=exclude, include_components=include_components)
|
|
181
|
+
|
|
182
|
+
serializer = get_serializer(app.type_encoders)
|
|
183
|
+
routes_json = encode_json(routes_data, serializer=serializer).decode("utf-8")
|
|
184
|
+
|
|
185
|
+
script = dedent(f"""\
|
|
186
|
+
<script type="text/javascript">
|
|
187
|
+
(function() {{
|
|
188
|
+
window.Litestar = window.Litestar || {{}};
|
|
189
|
+
window.Litestar.routes = {routes_json};
|
|
190
|
+
}})();
|
|
191
|
+
</script>""")
|
|
192
|
+
|
|
193
|
+
return markupsafe.Markup(script)
|
|
14
194
|
|
|
15
195
|
|
|
16
196
|
class ViteAssetLoader:
|
|
17
|
-
"""Vite
|
|
197
|
+
"""Vite asset loader for managing frontend assets.
|
|
198
|
+
|
|
199
|
+
This class handles loading and rendering of Vite-managed assets.
|
|
200
|
+
It supports both development mode (with HMR) and production mode
|
|
201
|
+
(with manifest-based asset resolution).
|
|
202
|
+
|
|
203
|
+
The loader is designed to be instantiated per-app (not a singleton)
|
|
204
|
+
and supports async initialization for non-blocking file I/O.
|
|
18
205
|
|
|
19
|
-
|
|
206
|
+
Attributes:
|
|
207
|
+
config: The Vite configuration.
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
loader = ViteAssetLoader(config)
|
|
211
|
+
await loader.initialize()
|
|
212
|
+
html = loader.render_asset_tag("src/main.ts")
|
|
20
213
|
"""
|
|
21
214
|
|
|
22
|
-
|
|
215
|
+
def __init__(self, config: "ViteConfig") -> None:
|
|
216
|
+
"""Initialize the asset loader.
|
|
23
217
|
|
|
24
|
-
|
|
218
|
+
Args:
|
|
219
|
+
config: The Vite configuration.
|
|
220
|
+
"""
|
|
25
221
|
self._config = config
|
|
26
222
|
self._manifest: dict[str, Any] = {}
|
|
223
|
+
self._manifest_content: str = ""
|
|
224
|
+
self._vite_base_path: "str | None" = None
|
|
225
|
+
self._initialized: bool = False
|
|
226
|
+
self._is_hot_dev = self._config.hot_reload and self._config.is_dev_mode
|
|
27
227
|
|
|
28
228
|
@classmethod
|
|
29
|
-
def initialize_loader(cls, config: ViteConfig) -> ViteAssetLoader:
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
229
|
+
def initialize_loader(cls, config: "ViteConfig") -> "ViteAssetLoader":
|
|
230
|
+
"""Synchronously initialize a loader instance.
|
|
231
|
+
|
|
232
|
+
This is a convenience method for synchronous initialization.
|
|
233
|
+
For async contexts, prefer using `initialize()` after construction.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
config: The Vite configuration.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
An initialized ViteAssetLoader instance.
|
|
240
|
+
"""
|
|
241
|
+
loader = cls(config=config)
|
|
242
|
+
loader.parse_manifest()
|
|
243
|
+
return loader
|
|
244
|
+
|
|
245
|
+
async def initialize(self) -> None:
|
|
246
|
+
"""Asynchronously initialize the loader.
|
|
247
|
+
|
|
248
|
+
This method performs async file I/O to load the manifest or hot file.
|
|
249
|
+
Call this during app startup in an async context.
|
|
250
|
+
"""
|
|
251
|
+
if self._initialized:
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
await (self._load_hot_file_async() if self._is_hot_dev else self._load_manifest_async())
|
|
255
|
+
self._initialized = True
|
|
35
256
|
|
|
36
257
|
def parse_manifest(self) -> None:
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
258
|
+
"""Synchronously parse the Vite manifest file.
|
|
259
|
+
|
|
260
|
+
This method reads the manifest.json file in production mode
|
|
261
|
+
or the hot file in development mode.
|
|
262
|
+
|
|
263
|
+
Note: For async contexts, use `initialize()` instead.
|
|
264
|
+
"""
|
|
265
|
+
(self._load_hot_file_sync() if self._is_hot_dev else self._load_manifest_sync())
|
|
266
|
+
|
|
267
|
+
def _get_manifest_path(self) -> Path:
|
|
268
|
+
"""Get the path to the manifest file.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Absolute path to the Vite manifest file.
|
|
272
|
+
"""
|
|
273
|
+
return self._config.resolve_manifest_path()
|
|
274
|
+
|
|
275
|
+
def _get_hot_file_path(self) -> Path:
|
|
276
|
+
"""Get the path to the hot file.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Path to the Vite hot file used for dev server URL discovery.
|
|
280
|
+
"""
|
|
281
|
+
bundle_dir = self._config.bundle_dir
|
|
282
|
+
if not bundle_dir.is_absolute():
|
|
283
|
+
bundle_dir = self._config.root_dir / bundle_dir
|
|
284
|
+
return bundle_dir / self._config.hot_file
|
|
285
|
+
|
|
286
|
+
async def _load_manifest_async(self) -> None:
|
|
287
|
+
"""Asynchronously load and parse the Vite manifest file.
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
ManifestNotFoundError: If the manifest file cannot be read or parsed.
|
|
291
|
+
"""
|
|
292
|
+
manifest_path = anyio.Path(self._get_manifest_path())
|
|
293
|
+
try:
|
|
294
|
+
if await manifest_path.exists():
|
|
295
|
+
content = await manifest_path.read_text()
|
|
296
|
+
self._manifest_content = content
|
|
297
|
+
self._manifest = decode_json(content)
|
|
298
|
+
else:
|
|
299
|
+
self._manifest = {}
|
|
300
|
+
except (OSError, UnicodeDecodeError, SerializationException) as exc:
|
|
301
|
+
raise ManifestNotFoundError(str(manifest_path)) from exc
|
|
302
|
+
|
|
303
|
+
def _load_manifest_sync(self) -> None:
|
|
304
|
+
"""Synchronously load and parse the Vite manifest file.
|
|
61
305
|
|
|
62
306
|
Raises:
|
|
63
|
-
|
|
307
|
+
ManifestNotFoundError: If the manifest file cannot be read or parsed.
|
|
64
308
|
"""
|
|
309
|
+
manifest_path = self._get_manifest_path()
|
|
310
|
+
try:
|
|
311
|
+
if manifest_path.exists():
|
|
312
|
+
self._manifest_content = manifest_path.read_text()
|
|
313
|
+
self._manifest = decode_json(self._manifest_content)
|
|
314
|
+
else:
|
|
315
|
+
self._manifest = {}
|
|
316
|
+
except (OSError, UnicodeDecodeError, SerializationException) as exc:
|
|
317
|
+
raise ManifestNotFoundError(str(manifest_path)) from exc
|
|
318
|
+
|
|
319
|
+
async def _load_hot_file_async(self) -> None:
|
|
320
|
+
"""Asynchronously read the hot file for dev server URL."""
|
|
321
|
+
hot_file_path = anyio.Path(self._get_hot_file_path())
|
|
322
|
+
if await hot_file_path.exists():
|
|
323
|
+
self._vite_base_path = await hot_file_path.read_text()
|
|
324
|
+
|
|
325
|
+
def _load_hot_file_sync(self) -> None:
|
|
326
|
+
"""Synchronously read the hot file for dev server URL."""
|
|
327
|
+
hot_file_path = self._get_hot_file_path()
|
|
328
|
+
if hot_file_path.exists():
|
|
329
|
+
self._vite_base_path = hot_file_path.read_text()
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def manifest_content(self) -> str:
|
|
333
|
+
"""Get the raw manifest content.
|
|
65
334
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
self._manifest = json.loads(manifest_content)
|
|
71
|
-
except Exception as exc: # noqa: BLE001
|
|
72
|
-
msg = "Cannot read Vite manifest file at %s"
|
|
73
|
-
raise RuntimeError(
|
|
74
|
-
msg,
|
|
75
|
-
Path(self._config.static_dir / self._config.manifest_name),
|
|
76
|
-
) from exc
|
|
335
|
+
Returns:
|
|
336
|
+
The raw JSON string content of the Vite manifest file.
|
|
337
|
+
"""
|
|
338
|
+
return self._manifest_content
|
|
77
339
|
|
|
78
|
-
|
|
79
|
-
|
|
340
|
+
@manifest_content.setter
|
|
341
|
+
def manifest_content(self, value: str) -> None:
|
|
342
|
+
"""Set the manifest content.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
value: The raw JSON string content to set.
|
|
346
|
+
"""
|
|
347
|
+
self._manifest_content = value
|
|
348
|
+
|
|
349
|
+
@cached_property
|
|
350
|
+
def version_id(self) -> str:
|
|
351
|
+
"""Get the version ID of the manifest.
|
|
352
|
+
|
|
353
|
+
The version ID is used for cache busting and Inertia.js asset versioning.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
A hash of the manifest content, or "1.0" if no manifest.
|
|
357
|
+
"""
|
|
358
|
+
if self._manifest_content:
|
|
359
|
+
return hashlib.sha256(self._manifest_content.encode("utf-8")).hexdigest()
|
|
360
|
+
return "1.0"
|
|
361
|
+
|
|
362
|
+
def render_hmr_client(self) -> "markupsafe.Markup":
|
|
363
|
+
"""Render the HMR client script tags.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
HTML markup containing React HMR and Vite client script tags.
|
|
367
|
+
"""
|
|
368
|
+
return markupsafe.Markup(f"{self.generate_react_hmr_tags()}{self.generate_ws_client_tags()}")
|
|
369
|
+
|
|
370
|
+
def render_asset_tag(
|
|
371
|
+
self, path: "str | list[str]", scripts_attrs: "dict[str, str] | None" = None
|
|
372
|
+
) -> "markupsafe.Markup":
|
|
373
|
+
"""Render asset tags for the specified path(s).
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
path: Single path or list of paths to assets.
|
|
377
|
+
scripts_attrs: Optional attributes for script tags.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
HTML markup for script and link tags.
|
|
381
|
+
"""
|
|
382
|
+
paths = [str(p) for p in path] if isinstance(path, list) else [str(path)]
|
|
383
|
+
return markupsafe.Markup("".join(self.generate_asset_tags(p, scripts_attrs=scripts_attrs) for p in paths))
|
|
384
|
+
|
|
385
|
+
def get_static_asset(self, path: str) -> str:
|
|
386
|
+
"""Get the URL for a static asset.
|
|
80
387
|
|
|
81
|
-
|
|
388
|
+
Args:
|
|
389
|
+
path: The path to the asset.
|
|
82
390
|
|
|
83
391
|
Returns:
|
|
84
|
-
|
|
392
|
+
The full URL to the asset.
|
|
393
|
+
|
|
394
|
+
Raises:
|
|
395
|
+
AssetNotFoundError: If the asset is not in the manifest.
|
|
85
396
|
"""
|
|
86
|
-
if
|
|
87
|
-
return
|
|
397
|
+
if self._is_hot_dev:
|
|
398
|
+
return self._vite_server_url(path)
|
|
399
|
+
|
|
400
|
+
if path not in self._manifest:
|
|
401
|
+
raise AssetNotFoundError(path, str(self._get_manifest_path()))
|
|
402
|
+
|
|
403
|
+
return urljoin(self._config.asset_url, self._manifest[path]["file"])
|
|
404
|
+
|
|
405
|
+
def generate_ws_client_tags(self) -> str:
|
|
406
|
+
"""Generate the Vite HMR client script tag.
|
|
88
407
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
408
|
+
Only generates output in development mode with hot reload enabled.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
Script tag HTML or empty string in production.
|
|
412
|
+
"""
|
|
413
|
+
if self._is_hot_dev:
|
|
414
|
+
return self._script_tag(self._vite_server_url("@vite/client"), {"type": "module"})
|
|
415
|
+
return ""
|
|
93
416
|
|
|
94
417
|
def generate_react_hmr_tags(self) -> str:
|
|
95
|
-
"""Generate
|
|
418
|
+
"""Generate React Fast Refresh preamble script.
|
|
96
419
|
|
|
97
|
-
Only
|
|
420
|
+
Only generates output when React mode is enabled in development.
|
|
98
421
|
|
|
99
422
|
Returns:
|
|
100
|
-
|
|
423
|
+
React refresh script HTML or empty string.
|
|
101
424
|
"""
|
|
102
|
-
if self._config.is_react and self.
|
|
103
|
-
|
|
104
|
-
|
|
425
|
+
if self._config.is_react and self._is_hot_dev:
|
|
426
|
+
nonce = self._config.csp_nonce
|
|
427
|
+
nonce_attr = f' nonce="{html.escape(nonce, quote=True)}"' if nonce else ""
|
|
428
|
+
return dedent(f"""
|
|
429
|
+
<script type="module"{nonce_attr}>
|
|
105
430
|
import RefreshRuntime from '{self._vite_server_url()}@react-refresh'
|
|
106
431
|
RefreshRuntime.injectIntoGlobalHook(window)
|
|
107
432
|
window.$RefreshReg$ = () => {{}}
|
|
108
433
|
window.$RefreshSig$ = () => (type) => type
|
|
109
434
|
window.__vite_plugin_react_preamble_installed__=true
|
|
110
435
|
</script>
|
|
111
|
-
"""
|
|
436
|
+
""")
|
|
112
437
|
return ""
|
|
113
438
|
|
|
114
|
-
def generate_asset_tags(self, path: str, scripts_attrs: dict[str, str] | None = None) -> str:
|
|
115
|
-
"""Generate all
|
|
439
|
+
def generate_asset_tags(self, path: "str | list[str]", scripts_attrs: "dict[str, str] | None" = None) -> str:
|
|
440
|
+
"""Generate all asset tags for the specified file(s).
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
path: Path or list of paths to assets.
|
|
444
|
+
scripts_attrs: Optional attributes for script tags.
|
|
116
445
|
|
|
117
446
|
Returns:
|
|
118
|
-
|
|
447
|
+
HTML string with all necessary script and link tags.
|
|
448
|
+
|
|
449
|
+
Raises:
|
|
450
|
+
ImproperlyConfiguredException: If asset not found in manifest.
|
|
119
451
|
"""
|
|
120
|
-
|
|
121
|
-
return self._script_tag(
|
|
122
|
-
self._vite_server_url(path),
|
|
123
|
-
{"type": "module", "async": "", "defer": ""},
|
|
124
|
-
)
|
|
452
|
+
from litestar.exceptions import ImproperlyConfiguredException
|
|
125
453
|
|
|
126
|
-
if path
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
454
|
+
paths = [path] if isinstance(path, str) else list(path)
|
|
455
|
+
|
|
456
|
+
if self._is_hot_dev:
|
|
457
|
+
return "".join(
|
|
458
|
+
self._style_tag(self._vite_server_url(p))
|
|
459
|
+
if p.endswith(".css")
|
|
460
|
+
else self._script_tag(self._vite_server_url(p), {"type": "module", "async": "", "defer": ""})
|
|
461
|
+
for p in paths
|
|
132
462
|
)
|
|
133
463
|
|
|
464
|
+
missing = [p for p in paths if p not in self._manifest]
|
|
465
|
+
if missing:
|
|
466
|
+
msg = "Cannot find %s in Vite manifest at %s. Did you forget to build your assets after an update?"
|
|
467
|
+
raise ImproperlyConfiguredException(msg, missing, self._get_manifest_path())
|
|
468
|
+
|
|
134
469
|
tags: list[str] = []
|
|
135
|
-
|
|
470
|
+
manifest_entries = {p: self._manifest[p] for p in paths if p}
|
|
471
|
+
|
|
136
472
|
if not scripts_attrs:
|
|
137
473
|
scripts_attrs = {"type": "module", "async": "", "defer": ""}
|
|
138
474
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
for css_path in
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
attrs=scripts_attrs
|
|
156
|
-
),
|
|
157
|
-
)
|
|
475
|
+
asset_url_base = self._config.asset_url
|
|
476
|
+
|
|
477
|
+
for manifest in manifest_entries.values():
|
|
478
|
+
if "css" in manifest:
|
|
479
|
+
tags.extend(self._style_tag(urljoin(asset_url_base, css_path)) for css_path in manifest.get("css", []))
|
|
480
|
+
|
|
481
|
+
if "imports" in manifest:
|
|
482
|
+
tags.extend(
|
|
483
|
+
self.generate_asset_tags(vendor_path, scripts_attrs=scripts_attrs)
|
|
484
|
+
for vendor_path in manifest.get("imports", [])
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
file_path = manifest.get("file", "")
|
|
488
|
+
if file_path.endswith(".css"):
|
|
489
|
+
tags.append(self._style_tag(urljoin(asset_url_base, file_path)))
|
|
490
|
+
else:
|
|
491
|
+
tags.append(self._script_tag(urljoin(asset_url_base, file_path), attrs=scripts_attrs))
|
|
158
492
|
|
|
159
493
|
return "".join(tags)
|
|
160
494
|
|
|
161
|
-
def _vite_server_url(self, path: str | None = None) -> str:
|
|
162
|
-
"""Generate
|
|
495
|
+
def _vite_server_url(self, path: "str | None" = None) -> str:
|
|
496
|
+
"""Generate a URL to an asset on the Vite development server.
|
|
163
497
|
|
|
164
|
-
|
|
165
|
-
path:
|
|
498
|
+
Args:
|
|
499
|
+
path: Optional path to append to the base URL.
|
|
166
500
|
|
|
167
501
|
Returns:
|
|
168
|
-
|
|
502
|
+
Full URL to the asset on the dev server.
|
|
169
503
|
"""
|
|
170
|
-
base_path = f"{self._config.protocol}://{self._config.host}:{self._config.port}"
|
|
171
|
-
return urljoin(
|
|
172
|
-
base_path,
|
|
173
|
-
urljoin(self._config.static_url, path if path is not None else ""),
|
|
174
|
-
)
|
|
504
|
+
base_path = self._vite_base_path or f"{self._config.protocol}://{self._config.host}:{self._config.port}"
|
|
505
|
+
return urljoin(base_path, urljoin(self._config.asset_url, path if path is not None else ""))
|
|
175
506
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if attrs is not None:
|
|
180
|
-
attrs_str = " ".join([f'{key}="{value}"' for key, value in attrs.items()])
|
|
507
|
+
@staticmethod
|
|
508
|
+
def _script_tag(src: str, attrs: "dict[str, str] | None" = None) -> str:
|
|
509
|
+
"""Generate an HTML script tag.
|
|
181
510
|
|
|
182
|
-
|
|
511
|
+
Args:
|
|
512
|
+
src: The source URL for the script.
|
|
513
|
+
attrs: Optional attributes for the script tag.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
HTML script tag string.
|
|
517
|
+
"""
|
|
518
|
+
if attrs is None:
|
|
519
|
+
attrs = {}
|
|
520
|
+
attrs_str = " ".join(f'{key}="{value}"' for key, value in attrs.items())
|
|
521
|
+
attrs_prefix = f"{attrs_str} " if attrs_str else ""
|
|
522
|
+
return f'<script {attrs_prefix}src="{src}"></script>'
|
|
183
523
|
|
|
184
|
-
|
|
185
|
-
|
|
524
|
+
@staticmethod
|
|
525
|
+
def _style_tag(href: str) -> str:
|
|
526
|
+
"""Generate an HTML link tag for CSS.
|
|
186
527
|
|
|
187
528
|
Args:
|
|
188
|
-
href: CSS file
|
|
529
|
+
href: The URL to the CSS file.
|
|
189
530
|
|
|
190
531
|
Returns:
|
|
191
|
-
|
|
532
|
+
HTML link tag string.
|
|
192
533
|
"""
|
|
193
534
|
return f'<link rel="stylesheet" href="{href}" />'
|