• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / assets / 15180675673

22 May 2025 07:19AM UTC coverage: 95.248% (-0.2%) from 95.436%
15180675673

push

github

dg
DIExtension: corrected namespace

461 of 484 relevant lines covered (95.25%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.23
/src/Assets/ViteMapper.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Nette\Assets;
6

7
use Nette\Utils\FileSystem;
8
use Nette\Utils\Json;
9

10

11
/**
12
 * Maps asset references to Vite-generated files using a Vite manifest.json.
13
 * Supports both development mode (Vite dev server) and production mode.
14
 */
15
class ViteMapper implements Mapper
16
{
17
        private array $chunks;
18
        private array $dependencies = [];
19

20

21
        public function __construct(
1✔
22
                private readonly string $baseUrl,
23
                private readonly string $basePath,
24
                private readonly ?string $manifestPath = null,
25
                private readonly ?string $devServer = null,
26
                private readonly ?Mapper $publicMapper = null,
27
        ) {
28
                if ($devServer !== null && !str_starts_with($devServer, 'http')) {
1✔
29
                        throw new \InvalidArgumentException("Vite devServer must be absolute URL, '$devServer' given");
×
30
                }
31
        }
1✔
32

33

34
        /**
35
         * Retrieves an Asset for a given Vite entry point.
36
         * @throws AssetNotFoundException when the asset cannot be found in the manifest
37
         */
38
        public function getAsset(string $reference, array $options = []): Asset
1✔
39
        {
40
                Helpers::checkOptions($options);
1✔
41

42
                $this->chunks ??= $this->readChunks();
1✔
43
                $chunk = $this->chunks[$reference] ?? null;
1✔
44

45
                if ($chunk) {
1✔
46
                        $entry = isset($chunk['isEntry']) || isset($chunk['isDynamicEntry']);
1✔
47
                        if (str_starts_with($reference, '_') && !$entry) {
1✔
48
                                throw new AssetNotFoundException("Cannot directly access internal chunk '$reference'");
1✔
49
                        }
50

51
                        $dependencies = $this->collectDependencies($reference);
1✔
52
                        unset($dependencies[$chunk['file']]);
1✔
53

54
                        if ($this->devServer) {
1✔
55
                                return $dependencies
1✔
56
                                        ? new EntryAsset(
1✔
57
                                                url: $this->devServer . '/' . $chunk['src'],
1✔
58
                                                mimeType: Helpers::guessMimeTypeFromExtension($chunk['src']),
1✔
59
                                                imports: [new ScriptAsset($this->devServer . '/@vite/client', type: 'module')],
1✔
60
                                        )
61
                                        : Helpers::createAssetFromUrl($this->devServer . '/' . $chunk['src']);
1✔
62
                        }
63

64
                        return $dependencies
1✔
65
                                ? new EntryAsset(
1✔
66
                                        url: $this->baseUrl . '/' . $chunk['file'],
1✔
67
                                        mimeType: Helpers::guessMimeTypeFromExtension($chunk['file']),
1✔
68
                                        file: $this->basePath . '/' . $chunk['file'],
1✔
69
                                        imports: array_values(array_filter($dependencies, fn($asset) => $asset instanceof StyleAsset)),
1✔
70
                                        preloads: array_values(array_filter($dependencies, fn($asset) => $asset instanceof ScriptAsset)),
1✔
71
                                        crossorigin: true,
1✔
72
                                )
73
                                : Helpers::createAssetFromUrl(
1✔
74
                                        $this->baseUrl . '/' . $chunk['file'],
1✔
75
                                        $this->basePath . '/' . $chunk['file'],
1✔
76
                                        ['crossorigin' => true],
1✔
77
                                );
78

79
                } elseif ($this->publicMapper) {
1✔
80
                        if ($this->devServer) {
1✔
81
                                return Helpers::createAssetFromUrl($this->devServer . '/' . $reference);
×
82
                        }
83
                        return $this->publicMapper->getAsset($reference);
1✔
84

85
                } else {
86
                        throw new AssetNotFoundException("File '$reference' not found in Vite manifest");
1✔
87
                }
88
        }
89

90

91
        /**
92
         * Recursively collects all imports (including nested) from a chunk.
93
         */
94
        private function collectDependencies(string $chunkId): array
1✔
95
        {
96
                $deps = &$this->dependencies[$chunkId];
1✔
97
                if ($deps === null) {
1✔
98
                        $deps = [];
1✔
99
                        $chunk = $this->chunks[$chunkId] ?? [];
1✔
100
                        foreach ($chunk['css'] ?? [] as $file) {
1✔
101
                                $deps[$file] = Helpers::createAssetFromUrl(
1✔
102
                                        $this->baseUrl . '/' . $file,
1✔
103
                                        $this->basePath . '/' . $file,
1✔
104
                                        ['crossorigin' => true],
1✔
105
                                );
106
                        }
107
                        foreach ($chunk['imports'] ?? [] as $id) {
1✔
108
                                $file = $this->chunks[$id]['file'];
1✔
109
                                $deps[$file] = Helpers::createAssetFromUrl(
1✔
110
                                        $this->baseUrl . '/' . $file,
1✔
111
                                        $this->basePath . '/' . $file,
1✔
112
                                        ['type' => 'module', 'crossorigin' => true],
1✔
113
                                );
114
                                $deps += $this->collectDependencies($id);
1✔
115
                        }
116
                }
117
                return $deps;
1✔
118
        }
119

120

121
        private function readChunks(): array
122
        {
123
                $path = $this->manifestPath ?? $this->basePath . '/.vite/manifest.json';
1✔
124
                try {
125
                        $res = Json::decode(FileSystem::read($path), forceArrays: true);
1✔
126
                } catch (\Throwable $e) {
×
127
                        throw new \RuntimeException('Failed to parse Vite manifest: ' . $e->getMessage(), 0, $e);
×
128
                }
129
                if (!is_array($res)) {
1✔
130
                        throw new \RuntimeException('Invalid Vite manifest format in ' . $path);
×
131
                }
132
                return $res;
1✔
133
        }
134

135

136
        /**
137
         * Returns the base URL for this mapper.
138
         */
139
        public function getBaseUrl(): string
140
        {
141
                return $this->baseUrl;
×
142
        }
143

144

145
        /**
146
         * Returns the base path for this mapper.
147
         */
148
        public function getBasePath(): string
149
        {
150
                return $this->basePath;
×
151
        }
152
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc