Coverage for src/kwai/frontend/vite.py: 96%
71 statements
« prev ^ index » next coverage.py v7.6.10, created at 2024-01-01 00:00 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2024-01-01 00:00 +0000
1"""Module for defining a class that handles files and assets created with vite."""
3from abc import ABC, abstractmethod
4from pathlib import Path
6from kwai.frontend.manifest import Chunk, Manifest
9class Vite(ABC):
10 """Interface for a Vite runtime."""
12 @abstractmethod
13 def init(self, *entries: str):
14 """Initialize the Vite runtime for the given entries."""
15 raise NotImplementedError
17 @abstractmethod
18 def get_scripts(self, base_url: str) -> list[str]:
19 """Get the scripts for the given entries."""
20 raise NotImplementedError
22 @abstractmethod
23 def get_css(self, base_url: str) -> list[str]:
24 """Get the css files for the given entries."""
25 raise NotImplementedError
27 @abstractmethod
28 def get_preloads(self, base_url: str) -> list[str]:
29 """Get the preloads for the given entries."""
30 raise NotImplementedError
32 @abstractmethod
33 def get_asset_path(self, asset_path: Path) -> Path | None:
34 """Return the path for an asset.
36 None is returned when the file should not be processed. This is the case
37 in the development environment, because Vite will serve the assets.
39 None should also be returned when the file does not exist in the dist folder.
40 This way, the url path will be handled by Vue Router.
42 When the path starts with public, the path without 'public' is used to search
43 the file in the dist folder.
44 """
47class DevelopmentVite(Vite):
48 """Vite implementation for development."""
50 def __init__(self, server_url: str):
51 """Initialize the development version of vite.
53 Args:
54 server_url: The url for the vite server
56 !!! Note
57 When vite is configured (see vite.config.ts) with a base then make sure that
58 this base is also part of the server_url. For example: when base is
59 '/apps/author' and the server is running on localhost with port 3001, then
60 server_url should be: 'http://localhost:3001/apps/author'.
61 """
62 self._server_url = server_url
63 self._entries: list[str] = []
65 def init(self, *entries: str):
66 self._entries = entries
68 def get_scripts(self, base_url: str) -> list[str]:
69 scripts = [f"{self._server_url}/@vite/client"]
70 for entry in self._entries:
71 scripts.append(f"{self._server_url}/{entry}")
72 return scripts
74 def get_css(self, base_url: str) -> list[str]:
75 return []
77 def get_preloads(self, base_url: str) -> list[str]:
78 return []
80 def get_asset_path(self, asset_path: Path) -> Path | None:
81 return None
84class ProductionVite(Vite):
85 """Vite implementation for production."""
87 def __init__(self, manifest_filepath: Path, base_path: Path):
88 """Initialize the production Vite runtime.
90 Args:
91 manifest_filepath: Path to the manifest file.
92 base_path: Path to the dist folder.
94 !!! Note
95 base_path is the path where the dist folder of an application is installed
96 For example '/website/frontend/apps/portal' must contain a dist folder.
97 """
98 self._manifest_filepath: Path = manifest_filepath
99 self._base_path: Path = base_path
100 self._manifest: Manifest | None = None
101 self._imported_chunks: dict[str, Chunk] = {} # chunks imported by the entries
103 def init(self, *entries: str):
104 """Load the manifest file."""
105 self._manifest = Manifest.load_from_file(self._manifest_filepath)
106 self._imported_chunks = self._find_imported_chunks(*entries)
108 def get_scripts(self, base_url: str) -> list[str]:
109 scripts = []
111 for chunk in self._imported_chunks.values():
112 if chunk.entry:
113 scripts.append(base_url + chunk.file)
115 return scripts
117 def get_css(self, base_url: str) -> list[str]:
118 styles = []
119 for chunk in self._imported_chunks.values():
120 for css in chunk.css:
121 styles.append(base_url + css)
122 return styles
124 def get_preloads(self, base_url: str) -> list[str]:
125 preloads = []
127 for chunk in self._imported_chunks.values():
128 if chunk.file.endswith(".js") and not chunk.entry:
129 preloads.append(base_url + chunk.file)
131 return preloads
133 def _find_imported_chunks(self, *entries: str) -> dict[str, Chunk]:
134 chunks = {}
136 for entry in entries:
137 if not self._manifest.has_chunk(entry):
138 raise ValueError(f"{entry} is not a chunk in {self._manifest_filepath}")
140 chunk = self._manifest.get_chunk(entry)
141 if not chunk.entry:
142 raise ValueError(
143 f"{entry} is not an entry point in {self._manifest_filepath}"
144 )
146 chunks[entry] = chunk
148 imports = chunk.imports
149 while imports:
150 import_ = imports.pop()
151 if import_ not in chunks:
152 import_chunk = self._manifest.get_chunk(import_)
153 chunks[import_] = import_chunk
154 imports.extend(import_chunk.imports)
156 return chunks
158 def get_asset_path(self, path: Path) -> Path | None:
159 dist_path = self._base_path / "dist"
160 asset_file = dist_path / path
161 if asset_file.is_relative_to(dist_path) and asset_file.is_file():
162 return asset_file
164 return None