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

tempestphp / tempest-framework / 14721086338

28 Apr 2025 11:11AM UTC coverage: 80.275% (+0.03%) from 80.248%
14721086338

push

github

web-flow
refactor(container): rename #[Lazy] to #[Proxy] (#1180)

2 of 2 new or added lines in 1 file covered. (100.0%)

7 existing lines in 4 files now uncovered.

11859 of 14773 relevant lines covered (80.27%)

106.8 hits per line

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

96.09
/src/Tempest/Vite/src/TagsResolver/ManifestTagsResolver.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Vite\TagsResolver;
6

7
use Tempest\Vite\Exceptions\ManifestEntrypointNotFoundException;
8
use Tempest\Vite\Manifest\Chunk;
9
use Tempest\Vite\Manifest\Manifest;
10
use Tempest\Vite\PrefetchStrategy;
11
use Tempest\Vite\TagCompiler\TagCompiler;
12
use Tempest\Vite\ViteConfig;
13

14
use function Tempest\root_path;
15
use function Tempest\Support\arr;
16
use function Tempest\Support\str;
17
use function Tempest\Support\Str\ensure_starts_with;
18

19
final readonly class ManifestTagsResolver implements TagsResolver
20
{
21
    public function __construct(
13✔
22
        private ViteConfig $viteConfig,
23
        private TagCompiler $tagCompiler,
24
        private Manifest $manifest,
25
    ) {}
13✔
26

27
    public function resolveTags(array $entrypoints): array
13✔
28
    {
29
        return arr($entrypoints)
13✔
30
            ->flatMap(function (string $entrypoint) {
13✔
31
                $path = $this->fileToAssetPath($entrypoint);
13✔
32

33
                if (! ($chunk = $this->manifest->chunks->get($path))) {
13✔
34
                    throw new ManifestEntrypointNotFoundException($entrypoint);
1✔
35
                }
36

37
                return $this->resolveEntryPointTags($chunk);
12✔
38
            })
13✔
39
            ->toArray();
13✔
40
    }
41

42
    private function resolveEntryPointTags(Chunk $entrypoint): array
12✔
43
    {
44
        return arr()
12✔
45
            ->append(...$this->getPreloadTags($entrypoint))
12✔
46
            ->append(...$this->getStyleTags($entrypoint))
12✔
47
            ->append($this->resolveChunkTag($entrypoint))
12✔
48
            ->append($this->resolvePrefetchingScript($entrypoint))
12✔
49
            ->unique()
12✔
50
            ->filter()
12✔
51
            ->toArray();
12✔
52
    }
53

54
    private function resolveChunkTag(Chunk $chunk): string
12✔
55
    {
56
        if (str_ends_with($chunk->file, '.css')) {
12✔
57
            return $this->tagCompiler->compileStyleTag($this->getChunkPath($chunk), $chunk);
×
58
        }
59

60
        if ($chunk->isEntry) {
12✔
61
            return $this->tagCompiler->compileScriptTag($this->getChunkPath($chunk), $chunk);
12✔
62
        }
63

64
        return $this->tagCompiler->compilePreloadTag($this->getChunkPath($chunk), $chunk);
×
65
    }
66

67
    private function getStyleTags(Chunk $chunk): array
12✔
68
    {
69
        $seenFiles = [];
12✔
70

71
        $getStyleChunks = function (Chunk $chunk) use (&$seenFiles, &$getStyleChunks) {
12✔
72
            $styleChunks = [];
12✔
73

74
            foreach ($chunk->imports as $importFile) {
12✔
75
                if (isset($seenFiles[$importFile])) {
4✔
76
                    continue;
×
77
                }
78

79
                $seenFiles[$importFile] = true;
4✔
80
                $importChunk = $this->manifest->chunks[$importFile] ?? null;
4✔
81

82
                if ($importChunk) {
4✔
83
                    $styleChunks = array_merge(
4✔
84
                        $styleChunks,
4✔
85
                        $getStyleChunks($importChunk),
4✔
86
                    );
4✔
87
                }
88
            }
89

90
            return array_merge(
12✔
91
                $styleChunks,
12✔
92
                array_map(fn (string $path) => ['file' => $path], $chunk->css),
12✔
93
            );
12✔
94
        };
12✔
95

96
        $styleChunks = $getStyleChunks($chunk);
12✔
97

98
        return arr($styleChunks)
12✔
99
            ->map(fn (array $styleChunk) => $this->tagCompiler->compileStyleTag($this->getAssetPath($styleChunk['file'])))
12✔
100
            ->unique()
12✔
101
            ->toArray();
12✔
102
    }
103

104
    private function getPreloadTags(Chunk $chunk): array
12✔
105
    {
106
        $seenFiles = [];
12✔
107
        $findPreloadableChunks = function (Chunk $chunk) use (&$seenFiles, &$findPreloadableChunks) {
12✔
108
            $preloadChunks = [];
12✔
109

110
            foreach ($chunk->imports as $importFile) {
12✔
111
                if (isset($seenFiles[$importFile])) {
4✔
112
                    continue;
×
113
                }
114

115
                $seenFiles[$importFile] = true;
4✔
116

117
                if ($importChunk = $this->manifest->chunks[$importFile] ?? null) {
4✔
118
                    $preloadChunks = [
4✔
119
                        ...$preloadChunks,
4✔
120
                        ...$findPreloadableChunks($importChunk),
4✔
121
                        $importChunk,
4✔
122
                    ];
4✔
123
                }
124
            }
125

126
            return $preloadChunks;
12✔
127
        };
12✔
128

129
        $preloadChunks = $findPreloadableChunks($chunk);
12✔
130

131
        return arr($preloadChunks)
12✔
132
            ->map(fn (Chunk $preloadChunk) => $this->tagCompiler->compilePreloadTag($this->getChunkPath($preloadChunk), $preloadChunk))
12✔
133
            ->unique()
12✔
134
            ->toArray();
12✔
135
    }
136

137
    private function resolvePrefetchingScript(Chunk $chunk): ?string
12✔
138
    {
139
        if ($this->viteConfig->prefetching->strategy === PrefetchStrategy::NONE) {
12✔
140
            return null;
10✔
141
        }
142

143
        $seenFiles = [];
2✔
144
        $findPrefetchableAssets = function (Chunk $chunk) use (&$seenFiles, &$findPrefetchableAssets) {
2✔
145
            $assets = [];
2✔
146
            $importsToProcess = array_merge($chunk->imports, $chunk->dynamicImports);
2✔
147

148
            foreach ($importsToProcess as $importFile) {
2✔
149
                if (isset($seenFiles[$importFile])) {
2✔
150
                    continue;
2✔
151
                }
152

153
                $seenFiles[$importFile] = true;
2✔
154

155
                if ($importChunk = $this->manifest->chunks[$importFile] ?? null) {
2✔
156
                    $assets = array_merge($assets, $findPrefetchableAssets($importChunk));
2✔
157

158
                    foreach ($importChunk->css as $cssFile) {
2✔
159
                        $assets[] = [
2✔
160
                            'rel' => 'prefetch',
2✔
161
                            'fetchpriority' => 'low',
2✔
162
                            'href' => $this->getAssetPath($cssFile),
2✔
163
                        ];
2✔
164
                    }
165

166
                    if (str_ends_with($importChunk->file, '.js')) {
2✔
167
                        $assets[] = [
2✔
168
                            'rel' => 'prefetch',
2✔
169
                            'fetchpriority' => 'low',
2✔
170
                            'href' => $this->getAssetPath($importChunk->file),
2✔
171
                        ];
2✔
172
                    }
173
                }
174
            }
175

176
            return $assets;
2✔
177
        };
2✔
178

179
        $assets = json_encode(array_values(array_map(
2✔
180
            callback: fn (array $asset) => array_map('strval', $asset),
2✔
181
            array: array_unique($findPrefetchableAssets($chunk), flags: SORT_REGULAR),
2✔
182
        )));
2✔
183

184
        $script = match ($this->viteConfig->prefetching->strategy) {
2✔
185
            PrefetchStrategy::AGGRESSIVE => <<<JS
2✔
186
                window.addEventListener('{$this->viteConfig->prefetching->prefetchEvent}', () => window.setTimeout(() => {
2✔
187
                    function makeLink(asset) {
188
                        const link = document.createElement('link')
189
                        Object.keys(asset).forEach((attribute) => link.setAttribute(attribute, asset[attribute]))
190
                        return link
191
                    }
192

193
                    const fragment = new DocumentFragment()
194
                    {$assets}.forEach((asset) => fragment.append(makeLink(asset)))
2✔
195
                    document.head.append(fragment)
196
                }))
197
            JS,
2✔
198
            PrefetchStrategy::WATERFALL => <<<JS
1✔
199
                window.addEventListener('{$this->viteConfig->prefetching->prefetchEvent}', () => {
1✔
200
                    function makeLink(asset) {
201
                        const link = document.createElement('link')
202
                        Object.entries(asset).forEach(([key, value]) => link.setAttribute(key, value))
203
                        return link
204
                    }
205

206
                    function loadNext(assets, count) {
207
                        if (!assets.length) return
208

209
                        const fragment = new DocumentFragment()
210
                        const limit = Math.min(count, assets.length)
211

212
                        for (let i = 0; i < limit; i++) {
213
                            const link = makeLink(assets.shift())
214
                            fragment.append(link)
215

216
                            if (assets.length) {
217
                                link.onload = () => loadNext(assets, 1)
218
                                link.onerror = () => loadNext(assets, 1)
219
                            }
220
                        }
221

222
                        document.head.append(fragment)
223
                    }
224

225
                    setTimeout(() => loadNext({$assets}, {$this->viteConfig->prefetching->concurrent}))
1✔
226
                })
227
            JS,
1✔
UNCOV
228
            PrefetchStrategy::NONE => '',
×
229
        };
2✔
230

231
        return $this->tagCompiler->compilePrefetchTag($script, $chunk);
2✔
232
    }
233

234
    private function getChunkPath(Chunk $chunk): string
12✔
235
    {
236
        return $this->getAssetPath($chunk->file);
12✔
237
    }
238

239
    private function getAssetPath(string $path): string
12✔
240
    {
241
        return ensure_starts_with($this->viteConfig->buildDirectory . '/' . $path, prefix: '/');
12✔
242
    }
243

244
    private function fileToAssetPath(string $file): string
13✔
245
    {
246
        return str($file)
13✔
247
            ->when(
13✔
248
                condition: fn ($file) => $file->startsWith('./'),
13✔
249
                callback: fn ($file) => str(realpath(root_path($file->toString()))),
13✔
250
            )
13✔
251
            ->replaceStart(root_path('public'), '')
13✔
252
            ->replaceStart(root_path(), '')
13✔
253
            ->replaceStart('/', '')
13✔
254
            ->toString();
13✔
255
    }
256
}
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