litestar-vite 0.1.3__py3-none-any.whl → 0.1.5__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.

Potentially problematic release.


This version of litestar-vite might be problematic. Click here for more details.

litestar_vite/cli.py CHANGED
@@ -1,9 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import sys
4
+ from pathlib import Path
3
5
  from typing import TYPE_CHECKING
4
6
 
5
- from click import group, option
6
- from litestar.cli._utils import LitestarGroup, console
7
+ from click import Context, group, option
8
+ from click import Path as ClickPath
9
+ from litestar.cli._utils import (
10
+ LitestarEnv,
11
+ LitestarGroup,
12
+ )
7
13
 
8
14
  if TYPE_CHECKING:
9
15
  from litestar import Litestar
@@ -14,6 +20,150 @@ def vite_group() -> None:
14
20
  """Manage Vite Tasks."""
15
21
 
16
22
 
23
+ @vite_group.command( # type: ignore # noqa: PGH003
24
+ name="init",
25
+ help="Initialize vite for your project.",
26
+ )
27
+ @option(
28
+ "--bundle-path",
29
+ type=ClickPath(dir_okay=True, file_okay=False, path_type=Path),
30
+ help="The path for the built Vite assets. This is the where the output of `npm run build` will write files.",
31
+ default=None,
32
+ required=False,
33
+ )
34
+ @option(
35
+ "--resource-path",
36
+ type=ClickPath(dir_okay=True, file_okay=False, path_type=Path),
37
+ help="The path to your Javascript/Typescript source and associated assets. If this were a standalone Vue or React app, this would point to your `src/` folder.",
38
+ default=None,
39
+ required=False,
40
+ )
41
+ @option(
42
+ "--asset-path",
43
+ type=ClickPath(dir_okay=True, file_okay=False, path_type=Path),
44
+ help="The path to your Javascript/Typescript source and associated assets. If this were a standalone Vue or React app, this would point to your `src/` folder.",
45
+ default=None,
46
+ required=False,
47
+ )
48
+ @option("--asset-url", type=str, help="Base url to serve assets from.", default=None, required=False)
49
+ @option(
50
+ "--vite-port",
51
+ type=int,
52
+ help="The port to run the vite server against.",
53
+ default=None,
54
+ is_flag=True,
55
+ required=False,
56
+ )
57
+ @option(
58
+ "--enable-ssr",
59
+ type=bool,
60
+ help="Enable SSR Support.",
61
+ required=False,
62
+ show_default=False,
63
+ is_flag=True,
64
+ )
65
+ @option("--overwrite", type=bool, help="Overwrite any files in place.", default=False, is_flag=True)
66
+ @option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
67
+ @option(
68
+ "--no-prompt",
69
+ help="Do not prompt for confirmation and use all defaults for initializing the project.",
70
+ type=bool,
71
+ default=False,
72
+ required=False,
73
+ show_default=True,
74
+ is_flag=True,
75
+ )
76
+ def vite_init(
77
+ ctx: Context,
78
+ vite_port: int | None,
79
+ enable_ssr: bool | None,
80
+ asset_url: str | None,
81
+ asset_path: Path | None,
82
+ bundle_path: Path | None,
83
+ resource_path: Path | None,
84
+ overwrite: bool,
85
+ verbose: bool,
86
+ no_prompt: bool,
87
+ ) -> None: # sourcery skip: low-code-quality
88
+ """Run vite build."""
89
+ from litestar.cli._utils import (
90
+ console,
91
+ )
92
+ from rich.prompt import Confirm
93
+
94
+ from litestar_vite.commands import init_vite
95
+ from litestar_vite.plugin import VitePlugin
96
+
97
+ if callable(ctx.obj):
98
+ ctx.obj = ctx.obj()
99
+ elif verbose:
100
+ ctx.obj.app.debug = True
101
+ env: LitestarEnv = ctx.obj
102
+ plugin = env.app.plugins.get(VitePlugin)
103
+ config = plugin._config # noqa: SLF001
104
+
105
+ console.rule("[yellow]Initializing Vite[/]", align="left")
106
+ resource_path = resource_path or config.resource_dir
107
+ asset_path = asset_path or config.assets_dir
108
+ bundle_path = bundle_path or config.bundle_dir
109
+ enable_ssr = enable_ssr or config.ssr_enabled
110
+ asset_url = asset_url or config.asset_url
111
+ vite_port = vite_port or config.port
112
+ hot_file = Path(bundle_path / config.hot_file)
113
+ root_path = resource_path.parent
114
+ if any(output_path.exists() for output_path in (asset_path, bundle_path, resource_path)) and not any(
115
+ [overwrite, no_prompt],
116
+ ):
117
+ confirm_overwrite = Confirm.ask(
118
+ "Files were found in the paths specified. Are you sure you wish to overwrite the contents?",
119
+ )
120
+ if not confirm_overwrite:
121
+ console.print("Skipping Vite initialization")
122
+ sys.exit(2)
123
+ for output_path in (asset_path, bundle_path, resource_path):
124
+ output_path.mkdir(parents=True, exist_ok=True)
125
+ enable_ssr = (
126
+ True
127
+ if enable_ssr
128
+ else False
129
+ if no_prompt
130
+ else Confirm.ask(
131
+ "Do you intend to use Litestar with any SSR framework?",
132
+ )
133
+ )
134
+ init_vite(
135
+ app=env.app,
136
+ root_path=root_path,
137
+ enable_ssr=enable_ssr,
138
+ vite_port=vite_port,
139
+ asset_url=asset_url,
140
+ resource_path=resource_path,
141
+ asset_path=asset_path,
142
+ bundle_path=bundle_path,
143
+ hot_file=hot_file,
144
+ litestar_port=env.port or 8000,
145
+ )
146
+
147
+
148
+ @vite_group.command( # type: ignore # noqa: PGH003
149
+ name="install",
150
+ help="Install frontend packages.",
151
+ )
152
+ @option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
153
+ def vite_install(app: Litestar, verbose: bool) -> None: # noqa: ARG001
154
+ """Run vite build."""
155
+ from litestar.cli._utils import (
156
+ console,
157
+ )
158
+
159
+ from litestar_vite.commands import run_vite
160
+ from litestar_vite.plugin import VitePlugin
161
+
162
+ console.rule("[yellow]Starting Vite build process[/]", align="left")
163
+ plugin = app.plugins.get(VitePlugin)
164
+ run_vite(" ".join(plugin._config.install_command)) # noqa: SLF001
165
+
166
+
17
167
  @vite_group.command( # type: ignore # noqa: PGH003
18
168
  name="build",
19
169
  help="Building frontend assets with Vite.",
@@ -21,4 +171,32 @@ def vite_group() -> None:
21
171
  @option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
22
172
  def vite_build(app: Litestar, verbose: bool) -> None: # noqa: ARG001
23
173
  """Run vite build."""
174
+ from litestar.cli._utils import (
175
+ console,
176
+ )
177
+
178
+ from litestar_vite.commands import run_vite
179
+ from litestar_vite.plugin import VitePlugin
180
+
24
181
  console.rule("[yellow]Starting Vite build process[/]", align="left")
182
+ plugin = app.plugins.get(VitePlugin)
183
+ run_vite(" ".join(plugin._config.build_command)) # noqa: SLF001
184
+
185
+
186
+ @vite_group.command( # type: ignore # noqa: PGH003
187
+ name="serve",
188
+ help="Serving frontend assets with Vite.",
189
+ )
190
+ @option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
191
+ def vite_serve(app: Litestar, verbose: bool) -> None: # noqa: ARG001
192
+ """Run vite serve."""
193
+ from litestar.cli._utils import (
194
+ console,
195
+ )
196
+
197
+ from litestar_vite.commands import run_vite
198
+ from litestar_vite.plugin import VitePlugin
199
+
200
+ console.rule("[yellow]Starting Vite process[/]", align="left")
201
+ plugin = app.plugins.get(VitePlugin)
202
+ run_vite(" ".join(plugin._config.build_command)) # noqa: SLF001
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any, MutableMapping
6
+
7
+ from jinja2 import select_autoescape
8
+ from litestar.serialization import encode_json
9
+
10
+ if TYPE_CHECKING:
11
+ from jinja2 import Environment, Template
12
+ from litestar import Litestar
13
+
14
+ VITE_INIT_TEMPLATES_PATH = f"{Path(__file__).parent}/templates/init"
15
+ VITE_INIT_TEMPLATES: set[str] = {"package.json.j2", "tsconfig.json.j2", "vite.config.ts.j2"}
16
+
17
+ DEFAULT_DEV_DEPENDENCIES: dict[str, str] = {
18
+ "axios": "^1.6.2",
19
+ "typescript": "^5.3.3",
20
+ "vite": "^5.0.6",
21
+ "litestar-vite-plugin": "^0.3.0",
22
+ "@types/node": "^20.10.3",
23
+ }
24
+ DEFAULT_DEPENDENCIES: dict[str, str] = {}
25
+
26
+
27
+ def to_json(value: Any) -> str:
28
+ """Serialize JSON field values.
29
+
30
+ Args:
31
+ value: Any json serializable value.
32
+
33
+ Returns:
34
+ JSON string.
35
+ """
36
+ return encode_json(value).decode("utf-8")
37
+
38
+
39
+ def init_vite(
40
+ app: Litestar,
41
+ root_path: Path,
42
+ resource_path: Path,
43
+ asset_path: Path,
44
+ asset_url: str,
45
+ bundle_path: Path,
46
+ enable_ssr: bool,
47
+ vite_port: int,
48
+ hot_file: Path,
49
+ litestar_port: int,
50
+ ) -> None:
51
+ """Initialize a new vite project."""
52
+ from jinja2 import Environment, FileSystemLoader
53
+
54
+ entry_point: list[str] = []
55
+ vite_template_env = Environment(
56
+ loader=FileSystemLoader([VITE_INIT_TEMPLATES_PATH]),
57
+ autoescape=select_autoescape(),
58
+ )
59
+
60
+ logger = app.get_logger()
61
+ enabled_templates: set[str] = VITE_INIT_TEMPLATES
62
+ dependencies: dict[str, str] = DEFAULT_DEPENDENCIES
63
+ dev_dependencies: dict[str, str] = DEFAULT_DEV_DEPENDENCIES
64
+ templates: dict[str, Template] = {
65
+ template_name: get_template(environment=vite_template_env, name=template_name)
66
+ for template_name in enabled_templates
67
+ }
68
+ for template_name, template in templates.items():
69
+ target_file_name = template_name.removesuffix(".j2")
70
+ with Path(target_file_name).open(mode="w") as file:
71
+ logger.info("Writing %s", target_file_name)
72
+
73
+ file.write(
74
+ template.render(
75
+ entry_point=entry_point,
76
+ enable_ssr=enable_ssr,
77
+ asset_url=asset_url,
78
+ root_path=str(root_path.relative_to(Path.cwd())),
79
+ resource_path=str(resource_path.relative_to(root_path)),
80
+ bundle_path=str(bundle_path.relative_to(root_path)),
81
+ asset_path=str(asset_path.relative_to(root_path)),
82
+ hot_file=str(hot_file.relative_to(root_path)),
83
+ vite_port=str(vite_port),
84
+ litestar_port=litestar_port,
85
+ dependencies=to_json(dependencies),
86
+ dev_dependencies=to_json(dev_dependencies),
87
+ ),
88
+ )
89
+
90
+
91
+ def get_template(
92
+ environment: Environment,
93
+ name: str | Template,
94
+ parent: str | None = None,
95
+ globals: MutableMapping[str, Any] | None = None, # noqa: A002
96
+ ) -> Template:
97
+ return environment.get_template(name=name, parent=parent, globals=globals)
98
+
99
+
100
+ def run_vite(command_to_run: str) -> None:
101
+ """Run Vite in a subprocess."""
102
+
103
+ import anyio
104
+
105
+ with contextlib.suppress(KeyboardInterrupt):
106
+ anyio.run(_run_vite, command_to_run)
107
+
108
+
109
+ async def _run_vite(command_to_run: str) -> None:
110
+ """Run Vite in a subprocess."""
111
+
112
+ from anyio import open_process
113
+ from anyio.streams.text import TextReceiveStream
114
+ from litestar.cli._utils import console
115
+
116
+ async with await open_process(command_to_run) as vite_process:
117
+ async for text in TextReceiveStream(vite_process.stdout): # type: ignore[arg-type]
118
+ console.print(text)
litestar_vite/config.py CHANGED
@@ -31,34 +31,66 @@ class ViteConfig:
31
31
  'plugins' key.
32
32
  """
33
33
 
34
- static_dir: Path
35
- """Location of the manifest file.
34
+ bundle_dir: Path
35
+ """Location of the compiled assets from Vite.
36
36
 
37
- The path relative to the `static_url` location
37
+ The manifest file will also be found here.
38
+ """
39
+ resource_dir: Path
40
+ """The directory where all typescript/javascript source are written.
41
+
42
+ In a standalone Vue or React application, this would be equivalent to the ``./src`` directory.
43
+ """
44
+ assets_dir: Path
45
+ """These are the assets that Vite will serve when developing.
46
+
47
+ This should include any images, CSS, or other media referenced.
48
+
49
+ These will be included in the bundle directory on build.
38
50
  """
39
51
  templates_dir: Path
40
52
  """Location of the Jinja2 template file.
41
53
  """
42
54
  manifest_name: str = "manifest.json"
43
55
  """Name of the manifest file."""
56
+ hot_file: str = "hot"
57
+ """Name of the hot file.
58
+
59
+ This file contains a single line containing the host, protocol, and port the Vite server is running.
60
+ """
44
61
  hot_reload: bool = False
45
62
  """Enable HMR for Vite development server."""
63
+ ssr_enabled: bool = False
64
+ """Enable SSR."""
65
+ ssr_output_dir: Path | None = None
66
+ """SSR Output path"""
67
+ root_dir: Path | None = None
68
+ """The is the base path to your application.
69
+
70
+ In a standalone Vue or React application, this would be equivalent to the ``./src`` directory.
71
+
72
+ """
46
73
  is_react: bool = False
47
74
  """Enable React components."""
48
- static_url: str = "/static/"
75
+ asset_url: str = "/static/"
49
76
  """Base URL to generate for static asset references.
50
77
 
51
- This should match what you have for the STATIC_URL
78
+ This URL will be prepended to anything generated from Vite.
52
79
  """
53
80
  host: str = "localhost"
54
81
  """Default host to use for Vite server."""
55
82
  protocol: str = "http"
56
83
  """Protocol to use for communication"""
57
- port: int = 3000
84
+ port: int = 5173
58
85
  """Default port to use for Vite server."""
59
- run_command: str = "npm run dev"
86
+ run_command: list[str] = field(default_factory=lambda: ["npm", "run", "dev"])
60
87
  """Default command to use for running Vite."""
61
- build_command: str = "npm run build"
88
+ build_command: list[str] = field(default_factory=lambda: ["npm", "run", "build"])
89
+ """Default command to use for building with Vite."""
90
+ install_command: list[str] = field(default_factory=lambda: ["npm", "install"])
91
+ """Default command to use for installing Vite."""
92
+ use_server_lifespan: bool = False
93
+ """Utilize the server lifespan hook to run Vite."""
62
94
 
63
95
 
64
96
  @dataclass
@@ -89,7 +121,10 @@ class ViteTemplateConfig(Generic[T]):
89
121
 
90
122
  def to_engine(self) -> T:
91
123
  """Instantiate the template engine."""
92
- template_engine = cast("T", self.engine(self.directory, self.config) if isclass(self.engine) else self.engine)
124
+ template_engine = cast(
125
+ "T",
126
+ self.engine(directory=self.directory, config=self.config) if isclass(self.engine) else self.engine,
127
+ )
93
128
  if callable(self.engine_callback):
94
129
  self.engine_callback(template_engine)
95
130
  return template_engine
litestar_vite/loader.py CHANGED
@@ -24,6 +24,7 @@ class ViteAssetLoader:
24
24
  def __init__(self, config: ViteConfig) -> None:
25
25
  self._config = config
26
26
  self._manifest: dict[str, Any] = {}
27
+ self._vite_base_path: str | None = None
27
28
 
28
29
  @classmethod
29
30
  def initialize_loader(cls, config: ViteConfig) -> ViteAssetLoader:
@@ -62,9 +63,15 @@ class ViteAssetLoader:
62
63
  Raises:
63
64
  RuntimeError: if cannot load the file or JSON in file is malformed.
64
65
  """
65
-
66
- if not self._config.hot_reload:
67
- with Path(self._config.static_dir / self._config.manifest_name).open() as manifest_file:
66
+ if self._config.hot_reload:
67
+ if _hot_file_found := Path(
68
+ self._config.bundle_dir / self._config.hot_file,
69
+ ).exists():
70
+ with Path(self._config.bundle_dir / self._config.hot_file).open() as hot_file:
71
+ self._vite_base_path = hot_file.read()
72
+
73
+ else:
74
+ with Path(self._config.bundle_dir / self._config.manifest_name).open() as manifest_file:
68
75
  manifest_content = manifest_file.read()
69
76
  try:
70
77
  self._manifest = json.loads(manifest_content)
@@ -72,7 +79,7 @@ class ViteAssetLoader:
72
79
  msg = "Cannot read Vite manifest file at %s"
73
80
  raise RuntimeError(
74
81
  msg,
75
- Path(self._config.static_dir / self._config.manifest_name),
82
+ Path(self._config.bundle_dir / self._config.manifest_name),
76
83
  ) from exc
77
84
 
78
85
  def generate_ws_client_tags(self) -> str:
@@ -111,36 +118,43 @@ class ViteAssetLoader:
111
118
  """
112
119
  return ""
113
120
 
114
- def generate_asset_tags(self, path: str, scripts_attrs: dict[str, str] | None = None) -> str:
121
+ def generate_asset_tags(self, path: str | list[str], scripts_attrs: dict[str, str] | None = None) -> str:
115
122
  """Generate all assets include tags for the file in argument.
116
123
 
117
124
  Returns:
118
125
  str: All tags to import this asset in your HTML page.
119
126
  """
127
+ if isinstance(path, str):
128
+ path = [path]
120
129
  if self._config.hot_reload:
121
- return self._script_tag(
122
- self._vite_server_url(path),
123
- {"type": "module", "async": "", "defer": ""},
130
+ return "".join(
131
+ [
132
+ self._script_tag(
133
+ self._vite_server_url(p),
134
+ {"type": "module", "async": "", "defer": ""},
135
+ )
136
+ for p in path
137
+ ],
124
138
  )
125
139
 
126
- if path not in self._manifest:
140
+ if any(p for p in path if p not in self._manifest):
127
141
  msg = "Cannot find %s in Vite manifest at %s"
128
142
  raise RuntimeError(
129
143
  msg,
130
144
  path,
131
- Path(self._config.static_dir / self._config.manifest_name),
145
+ Path(self._config.bundle_dir / self._config.manifest_name),
132
146
  )
133
147
 
134
148
  tags: list[str] = []
135
- manifest_entry: dict = self._manifest[path]
149
+ for p in path:
150
+ manifest_entry: dict = self._manifest[p]
136
151
  if not scripts_attrs:
137
152
  scripts_attrs = {"type": "module", "async": "", "defer": ""}
138
153
 
139
154
  # Add dependent CSS
140
155
  if "css" in manifest_entry:
141
156
  tags.extend(
142
- self._style_tag(urljoin(self._config.static_url, css_path))
143
- for css_path in manifest_entry.get("css", {})
157
+ self._style_tag(urljoin(self._config.asset_url, css_path)) for css_path in manifest_entry.get("css", {})
144
158
  )
145
159
  # Add dependent "vendor"
146
160
  if "imports" in manifest_entry:
@@ -151,7 +165,7 @@ class ViteAssetLoader:
151
165
  # Add the script by itself
152
166
  tags.append(
153
167
  self._script_tag(
154
- urljoin(self._config.static_url, manifest_entry["file"]),
168
+ urljoin(self._config.asset_url, manifest_entry["file"]),
155
169
  attrs=scripts_attrs,
156
170
  ),
157
171
  )
@@ -167,18 +181,15 @@ class ViteAssetLoader:
167
181
  Returns:
168
182
  str: Full URL to the asset.
169
183
  """
170
- base_path = f"{self._config.protocol}://{self._config.host}:{self._config.port}"
184
+ base_path = self._vite_base_path or f"{self._config.protocol}://{self._config.host}:{self._config.port}"
171
185
  return urljoin(
172
186
  base_path,
173
- urljoin(self._config.static_url, path if path is not None else ""),
187
+ urljoin(self._config.asset_url, path if path is not None else ""),
174
188
  )
175
189
 
176
190
  def _script_tag(self, src: str, attrs: dict[str, str] | None = None) -> str:
177
191
  """Generate an HTML script tag."""
178
- attrs_str = ""
179
- if attrs is not None:
180
- attrs_str = " ".join([f'{key}="{value}"' for key, value in attrs.items()])
181
-
192
+ attrs_str = " ".join([f'{key}="{value}"' for key, value in attrs.items()]) if attrs is not None else ""
182
193
  return f'<script {attrs_str} src="{src}"></script>'
183
194
 
184
195
  def _style_tag(self, href: str) -> str:
litestar_vite/plugin.py CHANGED
@@ -1,20 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from contextlib import contextmanager
4
+ from typing import TYPE_CHECKING, Iterator
4
5
 
5
- from litestar.plugins import CLIPluginProtocol, InitPluginProtocol
6
-
7
- from litestar_vite.config import ViteTemplateConfig
8
- from litestar_vite.template_engine import ViteTemplateEngine
6
+ from litestar.plugins import CLIPlugin, InitPluginProtocol
9
7
 
10
8
  if TYPE_CHECKING:
11
9
  from click import Group
10
+ from litestar import Litestar
12
11
  from litestar.config.app import AppConfig
13
12
 
14
13
  from litestar_vite.config import ViteConfig
15
14
 
16
15
 
17
- class VitePlugin(InitPluginProtocol, CLIPluginProtocol):
16
+ class VitePlugin(InitPluginProtocol, CLIPlugin):
18
17
  """Vite plugin."""
19
18
 
20
19
  __slots__ = ("_config",)
@@ -39,9 +38,34 @@ class VitePlugin(InitPluginProtocol, CLIPluginProtocol):
39
38
  Args:
40
39
  app_config: The :class:`AppConfig <.config.app.AppConfig>` instance.
41
40
  """
41
+
42
+ from litestar_vite.config import ViteTemplateConfig
43
+ from litestar_vite.template_engine import ViteTemplateEngine
44
+
42
45
  app_config.template_config = ViteTemplateConfig( # type: ignore[assignment]
43
- directory=self._config.templates_dir,
44
46
  engine=ViteTemplateEngine,
45
47
  config=self._config,
48
+ directory=self._config.templates_dir,
46
49
  )
47
50
  return app_config
51
+
52
+ @contextmanager
53
+ def server_lifespan(self, app: Litestar) -> Iterator[None]:
54
+ import multiprocessing
55
+
56
+ from litestar_vite.commands import run_vite
57
+
58
+ if self._config.use_server_lifespan:
59
+ command_to_run = self._config.run_command
60
+
61
+ vite_process = multiprocessing.Process(
62
+ target=run_vite,
63
+ args=[command_to_run],
64
+ )
65
+ try:
66
+ vite_process.start()
67
+ yield
68
+ finally:
69
+ if vite_process.is_alive():
70
+ vite_process.terminate()
71
+ vite_process.join()
@@ -1,23 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, TypeVar
3
+ from typing import TYPE_CHECKING, TypeVar
4
4
 
5
5
  import markupsafe
6
- from jinja2 import TemplateNotFound as JinjaTemplateNotFound
7
- from jinja2 import pass_context
8
6
  from litestar.contrib.jinja import JinjaTemplateEngine
9
- from litestar.exceptions import TemplateNotFoundException
10
- from litestar.template.base import (
11
- TemplateEngineProtocol,
12
- )
7
+ from litestar.template.base import TemplateEngineProtocol
13
8
 
14
- from .loader import ViteAssetLoader
9
+ from litestar_vite.loader import ViteAssetLoader
15
10
 
16
11
  if TYPE_CHECKING:
17
- from collections.abc import Callable
18
12
  from pathlib import Path
19
13
 
20
- from jinja2 import Template as JinjaTemplate
14
+ from jinja2 import Environment
21
15
 
22
16
  from litestar_vite.config import ViteConfig
23
17
 
@@ -27,62 +21,48 @@ T = TypeVar("T", bound=TemplateEngineProtocol)
27
21
  class ViteTemplateEngine(JinjaTemplateEngine):
28
22
  """Jinja Template Engine with Vite Integration."""
29
23
 
30
- def __init__(self, directory: Path | list[Path], config: ViteConfig) -> None:
24
+ def __init__(
25
+ self,
26
+ directory: Path | list[Path] | None = None,
27
+ engine_instance: Environment | None = None,
28
+ config: ViteConfig | None = None,
29
+ ) -> None:
31
30
  """Jinja2 based TemplateEngine.
32
31
 
33
32
  Args:
34
33
  directory: Direct path or list of directory paths from which to serve templates.
34
+ engine_instance: A jinja Environment instance.
35
35
  config: Vite config
36
36
  """
37
37
  super().__init__(directory=directory)
38
+ if config is None:
39
+ msg = "Please configure the `ViteConfig` instance."
40
+ raise ValueError(msg)
38
41
  self.config = config
39
- self.asset_loader = ViteAssetLoader.initialize_loader(config=self.config)
40
- self.engine.globals["vite_hmr_client"] = self.hmr_client
41
- self.engine.globals["vite_asset"] = self.resource
42
-
43
- def get_template(self, template_name: str) -> JinjaTemplate:
44
- """Retrieve a template by matching its name (dotted path) with files in the directory or directories provided.
45
-
46
- Args:
47
- template_name: A dotted path
48
-
49
- Returns:
50
- JinjaTemplate instance
51
-
52
- Raises:
53
- TemplateNotFoundException: if no template is found.
54
- """
55
- try:
56
- return self.engine.get_template(name=template_name)
57
- except JinjaTemplateNotFound as exc:
58
- raise TemplateNotFoundException(template_name=template_name) from exc
59
42
 
60
- def register_template_callable(self, key: str, template_callable: Callable[[dict[str, Any]], Any]) -> None:
61
- """Register a callable on the template engine.
62
-
63
- Args:
64
- key: The callable key, i.e. the value to use inside the template to call the callable.
65
- template_callable: A callable to register.
66
-
67
- Returns:
68
- None
69
- """
70
- self.engine.globals[key] = pass_context(template_callable)
43
+ self.asset_loader = ViteAssetLoader.initialize_loader(config=self.config)
44
+ self.engine.globals.update({"vite_hmr": self.get_hmr_client, "vite": self.get_asset_tag})
71
45
 
72
- def hmr_client(self) -> markupsafe.Markup:
46
+ def get_hmr_client(self) -> markupsafe.Markup:
73
47
  """Generate the script tag for the Vite WS client for HMR.
74
48
 
75
49
  Only used when hot module reloading is enabled, in production this method returns an empty string.
76
50
 
51
+ Arguments:
52
+ context: The template context.
77
53
 
78
54
  Returns:
79
55
  str: The script tag or an empty string.
80
56
  """
81
- tags = [self.asset_loader.generate_react_hmr_tags()]
82
- tags.append(self.asset_loader.generate_ws_client_tags())
83
- return markupsafe.Markup("".join(tags))
84
-
85
- def resource(self, path: str, scripts_attrs: dict[str, str] | None = None) -> markupsafe.Markup:
57
+ return markupsafe.Markup(
58
+ f"{self.asset_loader.generate_react_hmr_tags()}{self.asset_loader.generate_ws_client_tags()}",
59
+ )
60
+
61
+ def get_asset_tag(
62
+ self,
63
+ path: str | list[str],
64
+ scripts_attrs: dict[str, str] | None = None,
65
+ ) -> markupsafe.Markup:
86
66
  """Generate all assets include tags for the file in argument.
87
67
 
88
68
  Generates all scripts tags for this file and all its dependencies
@@ -92,6 +72,7 @@ class ViteTemplateEngine(JinjaTemplateEngine):
92
72
  (this function marks automatically <script> as "async" and "defer").
93
73
 
94
74
  Arguments:
75
+ context: The template context.
95
76
  path: Path to a Vite asset to include.
96
77
  scripts_attrs: script attributes
97
78
 
File without changes
File without changes
@@ -0,0 +1 @@
1
+ window.htmx = require("htmx.org");
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html class="h-full">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <!--IE compatibility-->
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
7
+ <meta
8
+ name="viewport"
9
+ content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
10
+ />
11
+ </head>
12
+
13
+ <body class="font-sans leading-none text-gray-700 antialiased">
14
+ <div id="app"></div>
15
+ {{ vite_hmr() }}
16
+ {{ vite('main.ts') }}
17
+ </body>
18
+ </html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "scripts": {
5
+ "dev": "vite",
6
+ "build": "vite build{% if enable_ssr %} && vite build --ssr{% endif %}"
7
+ },
8
+ "dependencies": {{ dependencies }},
9
+ "devDependencies": {{ dev_dependencies }}
10
+ }
@@ -0,0 +1,9 @@
1
+ const App = () => {
2
+ return (
3
+ <div>
4
+ <h1>Litestar + Vite + React</h1>
5
+ </div>
6
+ );
7
+ };
8
+
9
+ export default App;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import App from "./App";
4
+
5
+ ReactDOM.createRoot(document.getElementById("root")!).render(
6
+ <React.StrictMode>
7
+ <App />
8
+ </React.StrictMode>
9
+ );
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ content: [ "{{ resource_path }}/**/*.{js,jsx,ts,tsx,vue,j2,html}"],
3
+ theme: {
4
+ extend: {},
5
+ },
6
+ plugins: [
7
+ require("@tailwindcss/forms"),
8
+ ],
9
+ };
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "node",
7
+ "strict": true,
8
+ "jsx": "preserve",
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "esModuleInterop": true,
12
+ "lib": ["ESNext", "DOM"],
13
+ "skipLibCheck": true,
14
+ "noEmit": true,
15
+ "composite": true,
16
+ "allowSyntheticDefaultImports": true,
17
+ "baseUrl": "{{ resource_path }}",
18
+ "paths": {
19
+ "@/*": ["./*"]
20
+ },
21
+ "types": ["vite/client", "node"]
22
+ },
23
+ "include": [
24
+ {% if include_react %}"{{ resource_path }}/**/*.tsx","{{ resource_path }}/**/*.jsx",{% endif %}{% if include_vue %} "{{ resource_path }}/**/*.vue",{% endif %}
25
+ "**/*.d.ts",
26
+ "{{ resource_path }}/**/*.js" ,
27
+ "{{ resource_path }}/**/*.ts" ,
28
+ "vite.config.ts"
29
+ ]
30
+ }
File without changes
@@ -0,0 +1,27 @@
1
+ import { defineConfig } from "vite";
2
+ {% if include_vue %}import vue from "@vitejs/plugin-vue";{% endif %}
3
+ {% if include_react %}import vue from "@vitejs/plugin-react";{% endif %}
4
+ import litestar from "litestar-vite-plugin";
5
+
6
+ export default defineConfig({
7
+ root: "{{ root_path }}",
8
+ plugins: [
9
+ {% if include_vue %}vue(),{% endif %}
10
+ {% if include_react %}react(),{% endif %}
11
+ litestar({
12
+ input: [
13
+ {% if entry_point %}"{{ entry_point | join(', \"') }}"{% endif %}
14
+ ],
15
+ assetUrl: "{{ asset_url }}",
16
+ assetDirectory: "{{ asset_path }}",
17
+ bundleDirectory: "{{ bundle_path }}",
18
+ resourceDirectory: "{{ resource_path }}",
19
+ hotFile: "{{ hot_file }}"
20
+ }),
21
+ ],
22
+ resolve: {
23
+ alias: {
24
+ "@": "{{ resource_path }}"
25
+ },
26
+ },
27
+ });
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div>
3
+ Hello World
4
+ </div>
5
+ </template>
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: litestar-vite
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Vite plugin for Litestar
5
5
  Project-URL: Changelog, https://cofin.github.io/litestar-vite/latest/changelog
6
6
  Project-URL: Discord, https://discord.gg/X3FJqy8d2j
@@ -30,7 +30,9 @@ Classifier: Topic :: Database :: Database Engines/Servers
30
30
  Classifier: Topic :: Software Development
31
31
  Classifier: Typing :: Typed
32
32
  Requires-Python: >=3.8
33
- Requires-Dist: litestar[cli,jinja]>=2.0.1
33
+ Requires-Dist: litestar[cli,jinja]>=2.4.0
34
+ Provides-Extra: nodeenv
35
+ Requires-Dist: nodeenv; extra == 'nodeenv'
34
36
  Description-Content-Type: text/markdown
35
37
 
36
38
  # Litestar Vite
@@ -0,0 +1,28 @@
1
+ litestar_vite/__init__.py,sha256=9X3i67Q8DJN2lQYAMa9NSPTZGzHASGn8X8yeWJqBvWg,357
2
+ litestar_vite/__metadata__.py,sha256=Eml1c9xezV-GSodmysksrT8jPWqE__x0ENO1wM5g6q0,319
3
+ litestar_vite/cli.py,sha256=gmrh2krPf1_N9idw5x8bwFSNjecDB4nGd9VzFlRoy8g,6418
4
+ litestar_vite/commands.py,sha256=wkvp5BgE3zPVPO8PedumC9awPk7Vu9Qaz3ExOW4eIPo,3723
5
+ litestar_vite/config.py,sha256=b9J0jakMHiCxNRczp0bfL9Rt7WGx_ngGZa3s7thrF48,4656
6
+ litestar_vite/loader.py,sha256=oft1CTyZWbzSNfFv07Ihvyio42Q-QA-JH3NBie4SckM,7149
7
+ litestar_vite/plugin.py,sha256=41UvPw-BXcWiLLPRvbG1dDyHic6ZumeGmATgTSjOU80,2051
8
+ litestar_vite/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ litestar_vite/template_engine.py,sha256=s7fV4Hhi8Sd95ywylims24UpyTBFXmthEcMp4TLaRYg,2962
10
+ litestar_vite/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ litestar_vite/templates/init/index.html.j2,sha256=aW_u8HyZ1t3bAmqWmRDIuymanZ0Ccq5yT9kT5vkY9DY,443
12
+ litestar_vite/templates/init/package.json.j2,sha256=zGs_Dow9SxvggnitJfC1ujYNBBvDBK3sgk_nXphujyU,260
13
+ litestar_vite/templates/init/tsconfig.json.j2,sha256=323T2tsrc5_J5ClfA2ClPzPR02vpj3Jt2LQTCupFHww,832
14
+ litestar_vite/templates/init/vite.config.ts.j2,sha256=BOje1C-G2dRWiw16iLrUyrqW72Koh9hWUEiLE_WQxrs,785
15
+ litestar_vite/templates/init/htmx/main.css.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ litestar_vite/templates/init/htmx/main.js.j2,sha256=Fzic2sDw7QKnEcYtKuXB91rgounuP2FJmtidI0czc1E,35
17
+ litestar_vite/templates/init/react/App.tsx.j2,sha256=mXns5PRkbxOVFevO5CIb6ZTC_MoWSvW3TEk4rAzNB6w,120
18
+ litestar_vite/templates/init/react/main.tsx.j2,sha256=zpzzfSuAVfTQ01HiBjBG1ywO4605FL9kIuMfUyzxfjI,222
19
+ litestar_vite/templates/init/tailwindcss/main.css.j2,sha256=zBp60NAZ3bHTLQ7LWIugrCbOQdhiXdbDZjSLJfg6KOw,59
20
+ litestar_vite/templates/init/tailwindcss/postcss.config.ts.j2,sha256=JR7N3UZyyc9GdUfj3FNd4ArSEp3ybn565yj6TlrEX8U,82
21
+ litestar_vite/templates/init/tailwindcss/tailwind.config.js.j2,sha256=DyP-ShjY06M_i6QenX96jT7yOaUKAhywdx0oU1uMZWY,195
22
+ litestar_vite/templates/init/vanilla/main.css.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ litestar_vite/templates/init/vue/App.vue.j2,sha256=r73CGdnzcg8KTNw1XEgg78nrw7IFyBAADJcAGkMfTT4,63
24
+ litestar_vite/templates/init/vue/main.ts.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ litestar_vite-0.1.5.dist-info/METADATA,sha256=NvLyP3itCXgOCsbX-UC3X66MIJ1S-VPEX2isa_t3-IA,2248
26
+ litestar_vite-0.1.5.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
27
+ litestar_vite-0.1.5.dist-info/licenses/LICENSE,sha256=HeTiEfEgvroUXZe_xAmYHxtTBgw--mbXyZLsWDYabHc,1069
28
+ litestar_vite-0.1.5.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- litestar_vite/__init__.py,sha256=9X3i67Q8DJN2lQYAMa9NSPTZGzHASGn8X8yeWJqBvWg,357
2
- litestar_vite/__metadata__.py,sha256=Eml1c9xezV-GSodmysksrT8jPWqE__x0ENO1wM5g6q0,319
3
- litestar_vite/cli.py,sha256=8MDS91THfyyWnI_zxANI3rdnGooDsHfVW1cKxpX5eNo,691
4
- litestar_vite/config.py,sha256=yWGY-Fdbe2OLjfGR65bPL2YJ_BxUJ_dmj_2KVz7GU_c,3303
5
- litestar_vite/loader.py,sha256=mHvvDD_1ES7E6Ig8AntWrMZTxCPDKp9zmkEUoarzb7Q,6580
6
- litestar_vite/plugin.py,sha256=h3EiCOXmZpJLMth3Asq8_mZsiYCtMGW1JQFrldHM9dU,1334
7
- litestar_vite/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- litestar_vite/template_engine.py,sha256=2ttxaBvEpXljTxBH1vInVUSdCie4ZPqM063TLiXxADk,3806
9
- litestar_vite-0.1.3.dist-info/METADATA,sha256=D5tZDFTdU1PLhZJrBY-xcRhSGVdM3A2m9NoAjlhA_0M,2181
10
- litestar_vite-0.1.3.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
11
- litestar_vite-0.1.3.dist-info/licenses/LICENSE,sha256=HeTiEfEgvroUXZe_xAmYHxtTBgw--mbXyZLsWDYabHc,1069
12
- litestar_vite-0.1.3.dist-info/RECORD,,