Skip to content

Commit ca90ed8

Browse files
weaverryanfabpot
authored andcommitted
[AssetMapper] Fix: also download files referenced by url() in CSS
1 parent b85a083 commit ca90ed8

File tree

8 files changed

+305
-46
lines changed

8 files changed

+305
-46
lines changed

src/Symfony/Component/AssetMapper/Command/ImportMapInstallCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6464
}
6565

6666
$io->success(sprintf(
67-
'Downloaded %d asset%s into %s.',
67+
'Downloaded %d package%s into %s.',
6868
\count($downloadedPackages),
6969
1 === \count($downloadedPackages) ? '' : 's',
7070
str_replace($this->projectDir.'/', '', $this->packageDownloader->getVendorDir()),

src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public function downloadPackages(callable $progressCallback = null): array
5252
isset($installed[$entry->importName])
5353
&& $installed[$entry->importName]['version'] === $entry->version
5454
&& $this->remotePackageStorage->isDownloaded($entry)
55+
&& $this->areAllExtraFilesDownloaded($entry, $installed[$entry->importName]['extraFiles'])
5556
) {
5657
$newInstalled[$entry->importName] = $installed[$entry->importName];
5758
continue;
@@ -72,9 +73,14 @@ public function downloadPackages(callable $progressCallback = null): array
7273
}
7374

7475
$this->remotePackageStorage->save($entry, $contents[$package]['content']);
76+
foreach ($contents[$package]['extraFiles'] as $extraFilename => $extraFileContents) {
77+
$this->remotePackageStorage->saveExtraFile($entry, $extraFilename, $extraFileContents);
78+
}
79+
7580
$newInstalled[$package] = [
7681
'version' => $entry->version,
7782
'dependencies' => $contents[$package]['dependencies'] ?? [],
83+
'extraFiles' => array_keys($contents[$package]['extraFiles']),
7884
];
7985

8086
$downloadedPackages[] = $package;
@@ -109,7 +115,7 @@ public function getVendorDir(): string
109115
}
110116

111117
/**
112-
* @return array<string, array{path: string, version: string, dependencies: array<string, string>}>
118+
* @return array<string, array{path: string, version: string, dependencies: array<string, string>, extraFiles: array<string, string>}>
113119
*/
114120
private function loadInstalled(): array
115121
{
@@ -128,6 +134,10 @@ private function loadInstalled(): array
128134
if (!isset($data['dependencies'])) {
129135
throw new \LogicException(sprintf('The package "%s" is missing its dependencies.', $package));
130136
}
137+
138+
if (!isset($data['extraFiles'])) {
139+
$installed[$package]['extraFiles'] = [];
140+
}
131141
}
132142

133143
return $this->installed = $installed;
@@ -138,4 +148,15 @@ private function saveInstalled(array $installed): void
138148
$this->installed = $installed;
139149
file_put_contents($this->remotePackageStorage->getStorageDir().'/installed.php', sprintf('<?php return %s;', var_export($installed, true)));
140150
}
151+
152+
private function areAllExtraFilesDownloaded(ImportMapEntry $entry, array $extraFilenames): bool
153+
{
154+
foreach ($extraFilenames as $extraFilename) {
155+
if (!$this->remotePackageStorage->isExtraFileDownloaded($entry, $extraFilename)) {
156+
return false;
157+
}
158+
}
159+
160+
return true;
161+
}
141162
}

src/Symfony/Component/AssetMapper/ImportMap/RemotePackageStorage.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ public function isDownloaded(ImportMapEntry $entry): bool
3434
return is_file($this->getDownloadPath($entry->packageModuleSpecifier, $entry->type));
3535
}
3636

37+
public function isExtraFileDownloaded(ImportMapEntry $entry, string $extraFilename): bool
38+
{
39+
if (!$entry->isRemotePackage()) {
40+
throw new \InvalidArgumentException(sprintf('The entry "%s" is not a remote package.', $entry->importName));
41+
}
42+
43+
return is_file($this->getExtraFileDownloadPath($entry, $extraFilename));
44+
}
45+
3746
public function save(ImportMapEntry $entry, string $contents): void
3847
{
3948
if (!$entry->isRemotePackage()) {
@@ -46,6 +55,18 @@ public function save(ImportMapEntry $entry, string $contents): void
4655
file_put_contents($vendorPath, $contents);
4756
}
4857

58+
public function saveExtraFile(ImportMapEntry $entry, string $extraFilename, string $contents): void
59+
{
60+
if (!$entry->isRemotePackage()) {
61+
throw new \InvalidArgumentException(sprintf('The entry "%s" is not a remote package.', $entry->importName));
62+
}
63+
64+
$vendorPath = $this->getExtraFileDownloadPath($entry, $extraFilename);
65+
66+
@mkdir(\dirname($vendorPath), 0777, true);
67+
file_put_contents($vendorPath, $contents);
68+
}
69+
4970
/**
5071
* The local file path where a downloaded package should be stored.
5172
*/
@@ -68,4 +89,9 @@ public function getDownloadPath(string $packageModuleSpecifier, ImportMapType $i
6889

6990
return $this->vendorDir.'/'.$filename;
7091
}
92+
93+
private function getExtraFileDownloadPath(ImportMapEntry $entry, string $extraFilename): string
94+
{
95+
return $this->vendorDir.'/'.$entry->getPackageName().'/'.ltrim($extraFilename, '/');
96+
}
7197
}

src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
namespace Symfony\Component\AssetMapper\ImportMap\Resolver;
1313

14+
use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler;
1415
use Symfony\Component\AssetMapper\Exception\RuntimeException;
1516
use Symfony\Component\AssetMapper\ImportMap\ImportMapEntry;
1617
use Symfony\Component\AssetMapper\ImportMap\ImportMapType;
1718
use Symfony\Component\AssetMapper\ImportMap\PackageRequireOptions;
19+
use Symfony\Component\Filesystem\Path;
1820
use Symfony\Component\HttpClient\HttpClient;
1921
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
2022
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -157,12 +159,11 @@ public function resolvePackages(array $packagesToRequire): array
157159
/**
158160
* @param ImportMapEntry[] $importMapEntries
159161
*
160-
* @return array<string, array{content: string, dependencies: string[]}>
162+
* @return array<string, array{content: string, dependencies: string[], extraFiles: array<string, string>}>
161163
*/
162164
public function downloadPackages(array $importMapEntries, callable $progressCallback = null): array
163165
{
164166
$responses = [];
165-
166167
foreach ($importMapEntries as $package => $entry) {
167168
if (!$entry->isRemotePackage()) {
168169
throw new \InvalidArgumentException(sprintf('The entry "%s" is not a remote package.', $entry->importName));
@@ -171,12 +172,13 @@ public function downloadPackages(array $importMapEntries, callable $progressCall
171172
$pattern = ImportMapType::CSS === $entry->type ? self::URL_PATTERN_DIST_CSS : self::URL_PATTERN_DIST;
172173
$url = sprintf($pattern, $entry->getPackageName(), $entry->version, $entry->getPackagePathString());
173174

174-
$responses[$package] = $this->httpClient->request('GET', $url);
175+
$responses[$package] = [$this->httpClient->request('GET', $url), $entry];
175176
}
176177

177178
$errors = [];
178179
$contents = [];
179-
foreach ($responses as $package => $response) {
180+
$extraFileResponses = [];
181+
foreach ($responses as $package => [$response, $entry]) {
180182
if (200 !== $response->getStatusCode()) {
181183
$errors[] = [$package, $response];
182184
continue;
@@ -187,10 +189,21 @@ public function downloadPackages(array $importMapEntries, callable $progressCall
187189
}
188190

189191
$dependencies = [];
192+
$extraFiles = [];
193+
/* @var ImportMapEntry $entry */
190194
$contents[$package] = [
191-
'content' => $this->makeImportsBare($response->getContent(), $dependencies),
195+
'content' => $this->makeImportsBare($response->getContent(), $dependencies, $extraFiles, $entry->type, $entry->getPackagePathString()),
192196
'dependencies' => $dependencies,
197+
'extraFiles' => [],
193198
];
199+
200+
if (0 !== \count($extraFiles)) {
201+
$extraFileResponses[$package] = [];
202+
foreach ($extraFiles as $extraFile) {
203+
$extraFileResponses[$package][] = [$this->httpClient->request('GET', sprintf(self::URL_PATTERN_DIST_CSS, $entry->getPackageName(), $entry->version, $extraFile)), $extraFile, $entry->getPackageName(), $entry->version];
204+
}
205+
}
206+
194207
if ($progressCallback) {
195208
$progressCallback($package, 'finished', $response, \count($responses));
196209
}
@@ -205,6 +218,47 @@ public function downloadPackages(array $importMapEntries, callable $progressCall
205218
throw new RuntimeException(sprintf('Error %d downloading packages from jsDelivr for "%s". Check your package names. Response: ', $response->getStatusCode(), $packages).$response->getContent(false), 0, $e);
206219
}
207220

221+
$extraFileErrors = [];
222+
download_extra_files:
223+
$packageFileResponses = $extraFileResponses;
224+
$extraFileResponses = [];
225+
foreach ($packageFileResponses as $package => $responses) {
226+
foreach ($responses as [$response, $extraFile, $packageName, $version]) {
227+
if (200 !== $response->getStatusCode()) {
228+
$extraFileErrors[] = [$package, $response];
229+
continue;
230+
}
231+
232+
$extraFiles = [];
233+
234+
$content = $response->getContent();
235+
if (str_ends_with($extraFile, '.css')) {
236+
$content = $this->makeImportsBare($content, $dependencies, $extraFiles, ImportMapType::CSS, $extraFile);
237+
}
238+
$contents[$package]['extraFiles'][$extraFile] = $content;
239+
240+
if (0 !== \count($extraFiles)) {
241+
$extraFileResponses[$package] = [];
242+
foreach ($extraFiles as $newExtraFile) {
243+
$extraFileResponses[$package][] = [$this->httpClient->request('GET', sprintf(self::URL_PATTERN_DIST_CSS, $packageName, $version, $newExtraFile)), $newExtraFile, $packageName, $version];
244+
}
245+
}
246+
}
247+
}
248+
249+
if ($extraFileResponses) {
250+
goto download_extra_files;
251+
}
252+
253+
try {
254+
($extraFileErrors[0][1] ?? null)?->getHeaders();
255+
} catch (HttpExceptionInterface $e) {
256+
$response = $e->getResponse();
257+
$packages = implode('", "', array_column($extraFileErrors, 0));
258+
259+
throw new RuntimeException(sprintf('Error %d downloading extra imported files from jsDelivr for "%s". Response: ', $response->getStatusCode(), $packages).$response->getContent(false), 0, $e);
260+
}
261+
208262
return $contents;
209263
}
210264

@@ -237,20 +291,37 @@ private function fetchPackageRequirementsFromImports(string $content): array
237291
*
238292
* Replaces those with normal import "package/name" statements.
239293
*/
240-
private function makeImportsBare(string $content, array &$dependencies): string
294+
private function makeImportsBare(string $content, array &$dependencies, array &$extraFiles, ImportMapType $type, string $sourceFilePath): string
241295
{
242-
$content = preg_replace_callback(self::IMPORT_REGEX, function ($matches) use (&$dependencies) {
243-
$packageName = $matches[2].$matches[4]; // add the path if any
244-
$dependencies[] = $packageName;
245-
246-
// replace the "/npm/package@version/+esm" with "package@version"
247-
return str_replace($matches[1], sprintf('"%s"', $packageName), $matches[0]);
248-
}, $content);
249-
250-
// source maps are not also downloaded - so remove the sourceMappingURL
251-
// remove the final one only (in case sourceMappingURL is used in the code)
252-
if (false !== $lastPos = strrpos($content, '//# sourceMappingURL=')) {
253-
$content = substr($content, 0, $lastPos).preg_replace('{//# sourceMappingURL=.*$}m', '', substr($content, $lastPos));
296+
if (ImportMapType::JS === $type) {
297+
$content = preg_replace_callback(self::IMPORT_REGEX, function ($matches) use (&$dependencies) {
298+
$packageName = $matches[2].$matches[4]; // add the path if any
299+
$dependencies[] = $packageName;
300+
301+
// replace the "/npm/package@version/+esm" with "package@version"
302+
return str_replace($matches[1], sprintf('"%s"', $packageName), $matches[0]);
303+
}, $content);
304+
305+
// source maps are not also downloaded - so remove the sourceMappingURL
306+
// remove the final one only (in case sourceMappingURL is used in the code)
307+
if (false !== $lastPos = strrpos($content, '//# sourceMappingURL=')) {
308+
$content = substr($content, 0, $lastPos).preg_replace('{//# sourceMappingURL=.*$}m', '', substr($content, $lastPos));
309+
}
310+
311+
return $content;
312+
}
313+
314+
preg_match_all(CssAssetUrlCompiler::ASSET_URL_PATTERN, $content, $matches);
315+
foreach ($matches[1] as $path) {
316+
if (str_starts_with($path, 'data:')) {
317+
continue;
318+
}
319+
320+
if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
321+
continue;
322+
}
323+
324+
$extraFiles[] = Path::join(\dirname($sourceFilePath), $path);
254325
}
255326

256327
return preg_replace('{/\*# sourceMappingURL=[^ ]*+ \*/}', '', $content);

src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function resolvePackages(array $packagesToRequire): array;
3737
*
3838
* @param array<string, ImportMapEntry> $importMapEntries
3939
*
40-
* @return array<string, array{content: string, dependencies: string[]}>
40+
* @return array<string, array{content: string, dependencies: string[], extraFiles: array<string, string>}>
4141
*/
4242
public function downloadPackages(array $importMapEntries, callable $progressCallback = null): array;
4343
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy