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

nette / assets / 15480186418

06 Jun 2025 12:39AM UTC coverage: 91.984% (+0.03%) from 91.952%
15480186418

push

github

dg
added php.ini

459 of 499 relevant lines covered (91.98%)

0.92 hits per line

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

90.16
/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
                if ($this->devServer) {
1✔
43
                        return preg_match('~\.(js|mjs|ts)$~i', $reference)
1✔
44
                                ? new EntryAsset(
1✔
45
                                        url: $this->devServer . '/' . $reference,
1✔
46
                                        imports: [new ScriptAsset($this->devServer . '/@vite/client', type: 'module')],
1✔
47
                                )
48
                                : Helpers::createAssetFromUrl($this->devServer . '/' . $reference);
1✔
49
                }
50

51
                $this->chunks ??= $this->readChunks();
1✔
52
                $chunk = $this->chunks[$reference] ?? null;
1✔
53

54
                if ($chunk) {
1✔
55
                        $entry = isset($chunk['isEntry']) || isset($chunk['isDynamicEntry']);
1✔
56
                        if (str_starts_with($reference, '_') && !$entry) {
1✔
57
                                throw new AssetNotFoundException("Cannot directly access internal chunk '$reference'");
1✔
58
                        }
59

60
                        $dependencies = $this->collectDependencies($reference);
1✔
61
                        unset($dependencies[$chunk['file']]);
1✔
62

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

77
                } elseif ($this->publicMapper) {
1✔
78
                        return $this->publicMapper->getAsset($reference);
1✔
79

80
                } else {
81
                        throw new AssetNotFoundException("File '$reference' not found in Vite manifest");
1✔
82
                }
83
        }
84

85

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

115

116
        private function readChunks(): array
117
        {
118
                $path = $this->manifestPath ?? $this->basePath . '/.vite/manifest.json';
1✔
119
                try {
120
                        $res = Json::decode(FileSystem::read($path), forceArrays: true);
1✔
121
                } catch (\Throwable $e) {
×
122
                        throw new \RuntimeException("Failed to read Vite manifest from '$path'. Did you run 'npm run build'?", 0, $e);
×
123
                }
124
                if (!is_array($res)) {
1✔
125
                        throw new \RuntimeException("Invalid Vite manifest format in '$path'");
×
126
                }
127
                return $res;
1✔
128
        }
129

130

131
        /**
132
         * Returns the base URL for this mapper.
133
         */
134
        public function getBaseUrl(): string
135
        {
136
                return $this->baseUrl;
×
137
        }
138

139

140
        /**
141
         * Returns the base path for this mapper.
142
         */
143
        public function getBasePath(): string
144
        {
145
                return $this->basePath;
×
146
        }
147
}
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