1 | <?php |
||||
2 | /** |
||||
3 | * Vite plugin for Craft CMS |
||||
4 | * |
||||
5 | * Allows the use of the Vite.js next generation frontend tooling with Craft CMS |
||||
6 | * |
||||
7 | * @link https://nystudio107.com |
||||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||||
8 | * @copyright Copyright (c) 2021 nystudio107 |
||||
0 ignored issues
–
show
|
|||||
9 | */ |
||||
0 ignored issues
–
show
|
|||||
10 | |||||
11 | namespace nystudio107\pluginvite\helpers; |
||||
12 | |||||
13 | use Craft; |
||||
14 | use craft\helpers\Json as JsonHelper; |
||||
15 | |||||
16 | /** |
||||
0 ignored issues
–
show
|
|||||
17 | * @author nystudio107 |
||||
0 ignored issues
–
show
Content of the @author tag must be in the form "Display Name <[email protected]>"
![]() |
|||||
18 | * @package Vite |
||||
0 ignored issues
–
show
|
|||||
19 | * @since 1.0.5 |
||||
0 ignored issues
–
show
|
|||||
20 | */ |
||||
0 ignored issues
–
show
|
|||||
21 | class ManifestHelper |
||||
22 | { |
||||
23 | // Constants |
||||
24 | // ========================================================================= |
||||
25 | |||||
26 | public const LEGACY_EXTENSION = '-legacy.'; |
||||
27 | |||||
28 | // Protected Static Properties |
||||
29 | // ========================================================================= |
||||
30 | |||||
31 | /** |
||||
0 ignored issues
–
show
|
|||||
32 | * @var array|null |
||||
33 | */ |
||||
34 | protected static $manifest; |
||||
35 | |||||
36 | /** |
||||
0 ignored issues
–
show
|
|||||
37 | * @var array|null |
||||
38 | */ |
||||
39 | protected static $assetFiles; |
||||
40 | |||||
41 | // Public Static Methods |
||||
42 | // ========================================================================= |
||||
43 | |||||
44 | /** |
||||
45 | * Fetch and memoize the manifest file |
||||
46 | * |
||||
47 | * @param string $manifestPath |
||||
0 ignored issues
–
show
|
|||||
48 | */ |
||||
0 ignored issues
–
show
|
|||||
49 | public static function fetchManifest(string $manifestPath) |
||||
50 | { |
||||
51 | // Grab the manifest |
||||
52 | $pathOrUrl = (string)Craft::parseEnv($manifestPath); |
||||
0 ignored issues
–
show
The function
Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
53 | $manifest = FileHelper::fetch($pathOrUrl, [JsonHelper::class, 'decodeIfJson']); |
||||
54 | // If no manifest file is found, log it |
||||
55 | if ($manifest === null) { |
||||
56 | Craft::error('Manifest not found at ' . $manifestPath, __METHOD__); |
||||
57 | } |
||||
58 | // Ensure we're dealing with an array |
||||
59 | self::$manifest = (array)$manifest; |
||||
60 | } |
||||
61 | |||||
62 | /** |
||||
63 | * Return an array of tags from the manifest, for both modern and legacy builds |
||||
64 | * |
||||
65 | * @param string $path |
||||
0 ignored issues
–
show
|
|||||
66 | * @param bool $asyncCss |
||||
0 ignored issues
–
show
|
|||||
67 | * @param array $scriptTagAttrs |
||||
0 ignored issues
–
show
|
|||||
68 | * @param array $cssTagAttrs |
||||
0 ignored issues
–
show
|
|||||
69 | * |
||||
70 | * @return array |
||||
71 | */ |
||||
72 | public static function manifestTags(string $path, bool $asyncCss = true, array $scriptTagAttrs = [], array $cssTagAttrs = []): array |
||||
73 | { |
||||
74 | // Get the modern tags for this $path |
||||
75 | return self::extractManifestTags($path, $asyncCss, $scriptTagAttrs, $cssTagAttrs); |
||||
76 | } |
||||
77 | |||||
78 | /** |
||||
79 | * Return an array of data describing the script, module link, and CSS link tags for the |
||||
80 | * script from the manifest.json file |
||||
81 | * |
||||
82 | * @param string $path |
||||
0 ignored issues
–
show
|
|||||
83 | * @param bool $asyncCss |
||||
0 ignored issues
–
show
|
|||||
84 | * @param array $scriptTagAttrs |
||||
0 ignored issues
–
show
|
|||||
85 | * @param array $cssTagAttrs |
||||
0 ignored issues
–
show
|
|||||
86 | * @param bool $legacy |
||||
0 ignored issues
–
show
|
|||||
87 | * |
||||
88 | * @return array |
||||
89 | */ |
||||
90 | public static function extractManifestTags(string $path, bool $asyncCss = true, array $scriptTagAttrs = [], array $cssTagAttrs = [], bool $legacy = false): array |
||||
91 | { |
||||
92 | if (self::$manifest === null) { |
||||
93 | return []; |
||||
94 | } |
||||
95 | $tags = []; |
||||
96 | // Set the async CSS args |
||||
97 | $asyncCssOptions = []; |
||||
98 | if ($asyncCss) { |
||||
99 | $asyncCssOptions = [ |
||||
100 | 'media' => 'print', |
||||
101 | 'onload' => "this.media='all'", |
||||
102 | ]; |
||||
103 | } |
||||
104 | // Set the script args |
||||
105 | $scriptOptions = [ |
||||
106 | 'type' => 'module', |
||||
107 | 'crossorigin' => true, |
||||
108 | ]; |
||||
109 | if ($legacy) { |
||||
110 | $scriptOptions = [ |
||||
111 | 'nomodule' => true, |
||||
112 | ]; |
||||
113 | } |
||||
114 | // Iterate through the manifest |
||||
115 | foreach (self::$manifest as $manifestKey => $entry) { |
||||
116 | // If it's not an entry, skip it |
||||
117 | if (!isset($entry['isEntry']) || !$entry['isEntry']) { |
||||
118 | continue; |
||||
119 | } |
||||
120 | // If there's no file, skip it |
||||
121 | if (!isset($entry['file'])) { |
||||
122 | continue; |
||||
123 | } |
||||
124 | // If the $path isn't in the $manifestKey, and vice versus, skip it |
||||
125 | if (strpos($manifestKey, $path) === false && strpos($path, $manifestKey) === false) { |
||||
126 | continue; |
||||
127 | } |
||||
128 | // Handle optional `integrity` tags |
||||
129 | $integrityAttributes = []; |
||||
130 | if (isset($entry['integrity'])) { |
||||
131 | $integrityAttributes = [ |
||||
132 | 'integrity' => $entry['integrity'], |
||||
133 | ]; |
||||
134 | } |
||||
135 | // Add an onload event so listeners can know when the event has fired |
||||
136 | $tagOptions = array_merge( |
||||
137 | $scriptOptions, |
||||
138 | [ |
||||
139 | 'onload' => "e=new CustomEvent('vite-script-loaded', {detail:{path: '$manifestKey'}});document.dispatchEvent(e);", |
||||
140 | ], |
||||
141 | $integrityAttributes, |
||||
142 | $scriptTagAttrs |
||||
143 | ); |
||||
144 | // Include the entry script |
||||
145 | $tags[$manifestKey] = [ |
||||
146 | 'type' => 'file', |
||||
147 | 'url' => $entry['file'], |
||||
148 | 'options' => $tagOptions, |
||||
149 | ]; |
||||
150 | // Include any imports |
||||
151 | $importFiles = []; |
||||
152 | // Only include import tags for the non-legacy scripts |
||||
153 | if (!$legacy) { |
||||
154 | self::extractImportFiles(self::$manifest, $manifestKey, $importFiles); |
||||
155 | foreach ($importFiles as $importKey => $importFile) { |
||||
156 | $tags[$importFile] = [ |
||||
157 | 'crossorigin' => $tagOptions['crossorigin'] ?? true, |
||||
158 | 'type' => 'import', |
||||
159 | 'url' => $importFile, |
||||
160 | 'integrity' => self::$manifest[$importKey]['integrity'] ?? '', |
||||
161 | ]; |
||||
162 | } |
||||
163 | } |
||||
164 | // Include any CSS tags |
||||
165 | $cssFiles = []; |
||||
166 | self::extractCssFiles(self::$manifest, $manifestKey, $cssFiles); |
||||
167 | foreach ($cssFiles as $cssFile) { |
||||
168 | $tags[$cssFile] = [ |
||||
169 | 'type' => 'css', |
||||
170 | 'url' => $cssFile, |
||||
171 | 'options' => array_merge([ |
||||
0 ignored issues
–
show
|
|||||
172 | 'rel' => 'stylesheet', |
||||
173 | ], $asyncCssOptions, $cssTagAttrs), |
||||
0 ignored issues
–
show
For multi-line function calls, the closing parenthesis should be on a new line.
If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line: someFunctionCall(
$firstArgument,
$secondArgument,
$thirdArgument
); // Closing parenthesis on a new line.
![]() |
|||||
174 | ]; |
||||
175 | } |
||||
176 | } |
||||
177 | |||||
178 | return $tags; |
||||
179 | } |
||||
180 | |||||
181 | /** |
||||
182 | * Return an array of tags from the manifest, for both modern and legacy builds |
||||
183 | * |
||||
184 | * @param string $path |
||||
0 ignored issues
–
show
|
|||||
185 | * @param bool $asyncCss |
||||
0 ignored issues
–
show
|
|||||
186 | * @param array $scriptTagAttrs |
||||
0 ignored issues
–
show
|
|||||
187 | * @param array $cssTagAttrs |
||||
0 ignored issues
–
show
|
|||||
188 | * |
||||
189 | * @return array |
||||
190 | */ |
||||
191 | public static function legacyManifestTags(string $path, bool $asyncCss = true, array $scriptTagAttrs = [], array $cssTagAttrs = []): array |
||||
192 | { |
||||
193 | // Get the legacy tags for this $path |
||||
194 | $parts = pathinfo($path); |
||||
195 | $legacyPath = $parts['dirname'] |
||||
196 | . '/' |
||||
197 | . $parts['filename'] |
||||
198 | . self::LEGACY_EXTENSION |
||||
199 | . $parts['extension']; |
||||
200 | |||||
201 | return self::extractManifestTags($legacyPath, $asyncCss, $scriptTagAttrs, $cssTagAttrs, true); |
||||
202 | } |
||||
203 | |||||
204 | /** |
||||
205 | * Extract an entry file URL from all of the entries in the manifest |
||||
206 | * |
||||
207 | * @param string $path |
||||
0 ignored issues
–
show
|
|||||
208 | * @return string |
||||
0 ignored issues
–
show
|
|||||
209 | */ |
||||
210 | public static function extractEntry(string $path): string |
||||
211 | { |
||||
212 | foreach (self::$manifest as $entryKey => $entry) { |
||||
213 | if (strpos($entryKey, $path) !== false) { |
||||
214 | return $entry['file'] ?? ''; |
||||
215 | } |
||||
216 | // Check CSS |
||||
217 | $styles = $entry['css'] ?? []; |
||||
218 | foreach ($styles as $style) { |
||||
219 | $styleKey = self::filenameWithoutHash($style); |
||||
220 | if (strpos($styleKey, $path) !== false) { |
||||
221 | return $style; |
||||
222 | } |
||||
223 | } |
||||
224 | // Check assets |
||||
225 | $assets = $entry['assets'] ?? []; |
||||
226 | foreach ($assets as $asset) { |
||||
227 | $assetKey = self::filenameWithoutHash($asset); |
||||
228 | if (strpos($assetKey, $path) !== false) { |
||||
229 | return $asset; |
||||
230 | } |
||||
231 | } |
||||
232 | } |
||||
233 | |||||
234 | return ''; |
||||
235 | } |
||||
236 | |||||
237 | /** |
||||
238 | * Extract an integrity hash for the given $path from the entries in the manifest |
||||
239 | * |
||||
240 | * @param string $path |
||||
0 ignored issues
–
show
|
|||||
241 | * @return string |
||||
0 ignored issues
–
show
|
|||||
242 | */ |
||||
243 | public static function extractIntegrity(string $path): string |
||||
244 | { |
||||
245 | foreach (self::$manifest as $entryKey => $entry) { |
||||
246 | if (strpos($entryKey, $path) !== false) { |
||||
247 | return $entry['integrity'] ?? ''; |
||||
248 | } |
||||
249 | } |
||||
250 | |||||
251 | return ''; |
||||
252 | } |
||||
253 | |||||
254 | /** |
||||
255 | * Extract any asset files from all of the entries in the manifest |
||||
256 | * |
||||
257 | * @return array |
||||
258 | */ |
||||
259 | public static function extractAssetFiles(): array |
||||
260 | { |
||||
261 | // Used the memoized version if available |
||||
262 | if (self::$assetFiles !== null) { |
||||
263 | return self::$assetFiles; |
||||
264 | } |
||||
265 | $assetFiles = []; |
||||
266 | foreach (self::$manifest as $entry) { |
||||
267 | $assets = $entry['assets'] ?? []; |
||||
268 | foreach ($assets as $asset) { |
||||
269 | $assetKey = self::filenameWithoutHash($asset); |
||||
270 | $assetFiles[$assetKey] = $asset; |
||||
271 | } |
||||
272 | } |
||||
273 | self::$assetFiles = $assetFiles; |
||||
274 | |||||
275 | return $assetFiles; |
||||
276 | } |
||||
277 | |||||
278 | // Protected Static Methods |
||||
279 | // ========================================================================= |
||||
280 | |||||
281 | /** |
||||
282 | * Extract any import files from entries recursively |
||||
283 | * |
||||
284 | * @param array $manifest |
||||
0 ignored issues
–
show
|
|||||
285 | * @param string $manifestKey |
||||
0 ignored issues
–
show
|
|||||
286 | * @param array $importFiles |
||||
0 ignored issues
–
show
|
|||||
287 | * |
||||
288 | * @return array |
||||
289 | */ |
||||
290 | protected static function extractImportFiles(array $manifest, string $manifestKey, array &$importFiles): array |
||||
291 | { |
||||
292 | $entry = $manifest[$manifestKey] ?? null; |
||||
293 | if (!$entry) { |
||||
294 | return []; |
||||
295 | } |
||||
296 | |||||
297 | $imports = $entry['imports'] ?? []; |
||||
298 | foreach ($imports as $import) { |
||||
299 | $importFiles[$import] = $manifest[$import]['file']; |
||||
300 | self::extractImportFiles($manifest, $import, $importFiles); |
||||
301 | } |
||||
302 | |||||
303 | return $importFiles; |
||||
304 | } |
||||
305 | |||||
306 | /** |
||||
307 | * Extract any CSS files from entries recursively |
||||
308 | * |
||||
309 | * @param array $manifest |
||||
0 ignored issues
–
show
|
|||||
310 | * @param string $manifestKey |
||||
0 ignored issues
–
show
|
|||||
311 | * @param array $cssFiles |
||||
0 ignored issues
–
show
|
|||||
312 | * |
||||
313 | * @return array |
||||
314 | */ |
||||
315 | protected static function extractCssFiles(array $manifest, string $manifestKey, array &$cssFiles): array |
||||
316 | { |
||||
317 | $entry = $manifest[$manifestKey] ?? null; |
||||
318 | if (!$entry) { |
||||
319 | return []; |
||||
320 | } |
||||
321 | $cssFiles = array_merge($cssFiles, $entry['css'] ?? []); |
||||
322 | $imports = $entry['imports'] ?? []; |
||||
323 | foreach ($imports as $import) { |
||||
324 | self::extractCssFiles($manifest, $import, $cssFiles); |
||||
325 | } |
||||
326 | |||||
327 | return $cssFiles; |
||||
328 | } |
||||
329 | |||||
330 | /** |
||||
331 | * Return a file name from the passed in $path, with any version hash removed from it |
||||
332 | * |
||||
333 | * @param string $path |
||||
0 ignored issues
–
show
|
|||||
334 | * @return string |
||||
0 ignored issues
–
show
|
|||||
335 | */ |
||||
336 | protected static function filenameWithoutHash(string $path): string |
||||
337 | { |
||||
338 | $pathInfo = pathinfo($path); |
||||
339 | $filename = $pathInfo['filename']; |
||||
340 | $extension = $pathInfo['extension']; |
||||
341 | $hashPos = strrpos($filename, '.') ?: strlen($filename); |
||||
342 | $hash = substr($filename, $hashPos); |
||||
343 | // Vite 5 now uses a `-` to separate the version hash, so account for that as well |
||||
344 | if (empty($hash) && str_contains($filename, '-')) { |
||||
345 | $hash = substr($filename, strrpos($filename, '-')); |
||||
346 | } |
||||
347 | $filename = str_replace($hash, '', $filename); |
||||
348 | |||||
349 | return implode('.', [$filename, $extension]); |
||||
350 | } |
||||
351 | } |
||||
352 |