Coverage for webapp/vite_integration/impl/prod.py: 97%

71 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-17 22:07 +0000

1from os import path 

2import json 

3from typing import List, Set 

4from functools import cache 

5 

6from webapp.config import VITE_OUTPUT_DIR 

7from webapp.vite_integration.impl.base import _AbstractViteIntegration 

8from webapp.vite_integration.types import Manifest, ManifestChunk 

9from webapp.vite_integration.exceptions import ( 

10 AssetPathException, 

11 ManifestContentException, 

12 ManifestPathException, 

13) 

14 

15 

16class ProdViteIntegration(_AbstractViteIntegration): 

17 OUT_DIR = VITE_OUTPUT_DIR 

18 BUILD_MANIFEST = ".vite/manifest.json" 

19 manifest = None # we cache the manifest contents in a static attribute 

20 

21 def __init__(self): 

22 if ProdViteIntegration.manifest: 

23 # manifest has already been parsed 

24 return 

25 

26 # print("Initializing Vite manifest") 

27 manifest_path = path.join( 

28 ProdViteIntegration.OUT_DIR, ProdViteIntegration.BUILD_MANIFEST 

29 ) 

30 if not path.isfile(manifest_path): 

31 raise ManifestPathException("Bad path to Vite manifest") 

32 

33 manifest: Manifest = {} 

34 with open(manifest_path) as f: 

35 manifest = json.load(f) 

36 ProdViteIntegration.manifest = manifest 

37 

38 def get_dev_tools(self) -> str: 

39 return "" 

40 

41 @cache 

42 def get_asset_url(self, asset_name: str) -> str: 

43 entry = ProdViteIntegration.manifest.get(asset_name) 

44 if not entry: 

45 raise ManifestContentException( 

46 f'Asset "{asset_name}" not declared in Vite build manifest' 

47 ) 

48 

49 entry_path = path.join(ProdViteIntegration.OUT_DIR, entry["file"]) 

50 if not path.isfile(entry_path): 

51 raise AssetPathException( 

52 f'Path to asset file "{entry_path}" doesn\'t exist; check your' 

53 "VITE_OUTPUT_DIR env variable" 

54 ) 

55 

56 return f"/{entry_path}" 

57 

58 @cache 

59 def _recursive_get_chunks(self, asset_name: str) -> List[ManifestChunk]: 

60 seen: Set[str] = set() 

61 

62 # recursively visit the manifest to build the import tree for the 

63 # given `asset_name` 

64 def __recursive_get_chunks( 

65 chunk: ManifestChunk, 

66 ) -> List[ManifestChunk]: 

67 chunks: List[ManifestChunk] = [] 

68 for file in chunk.get("imports", []): 

69 importee = ProdViteIntegration.manifest[file] 

70 if file in seen: 

71 continue 

72 seen.add(file) 

73 chunks.extend(__recursive_get_chunks(importee)) 

74 chunks.append(importee) 

75 return chunks 

76 

77 entry = ProdViteIntegration.manifest.get(asset_name) 

78 if not entry: 

79 raise ManifestContentException( 

80 f'Asset "{asset_name}" not declared in Vite build manifest' 

81 ) 

82 

83 return [entry] + __recursive_get_chunks(entry) 

84 

85 @cache 

86 def get_imported_chunks(self, asset_name: str) -> List[str]: 

87 chunks = self._recursive_get_chunks(asset_name) 

88 files = [ 

89 path.join(ProdViteIntegration.OUT_DIR, f["file"]) 

90 for f in chunks[1:] # first chunk is asset_name 

91 ] 

92 

93 for f in files: 

94 if not path.isfile(f): 

95 raise AssetPathException( 

96 f'Path to asset file "{f}" doesn\'t exist; check your' 

97 "VITE_OUTPUT_DIR env variable" 

98 ) 

99 

100 # only build filesystem paths for all chunks 

101 urls = [f"/{f}" for f in files] 

102 

103 return urls 

104 

105 @cache 

106 def get_imported_css(self, asset_name: str) -> List[str]: 

107 chunks = self._recursive_get_chunks(asset_name) 

108 files = [] 

109 for chunk in chunks: 

110 for file in chunk.get("css", []): 

111 files.append(path.join(ProdViteIntegration.OUT_DIR, file)) 

112 

113 for f in files: 

114 if not path.isfile(f): 

115 raise AssetPathException( 

116 f'Path to asset file "{f}" doesn\'t exist; check your' 

117 "VITE_OUTPUT_DIR env variable" 

118 ) 

119 

120 # only build filesystem paths for all chunks 

121 urls = [f"/{f}" for f in files] 

122 

123 return urls