Skip to content

Commit 18df11d

Browse files
committed
minor symfony#52156 [AssetMapper] Optimize memory usage (load only "compilable" assets) (smnandre)
This PR was squashed before being merged into the 6.4 branch. Discussion ---------- [AssetMapper] Optimize memory usage (load only "compilable" assets) | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | no | Deprecations? | no | License | MIT A bit late for 6.4... but this PR will have a sensible impact on performances. Currently if the assets directory contains 500 images, they are all loaded in memory during the compile command. Their contents are also serialized/unserialized and stored in cache. This PR allow to load/store in memory/cache only the assets supported by the compilers (JS/CSS files today) Commits ------- 9f63c81 [AssetMapper] Optimize memory usage (load only "compilable" assets)
2 parents 0284e95 + 9f63c81 commit 18df11d

File tree

5 files changed

+52
-27
lines changed

5 files changed

+52
-27
lines changed

src/Symfony/Component/AssetMapper/AssetMapperCompiler.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,15 @@ public function compile(string $content, MappedAsset $asset): string
4242

4343
return $content;
4444
}
45+
46+
public function supports(MappedAsset $asset): bool
47+
{
48+
foreach ($this->assetCompilers as $compiler) {
49+
if ($compiler->supports($asset)) {
50+
return true;
51+
}
52+
}
53+
54+
return false;
55+
}
4556
}

src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Psr\Cache\CacheItemPoolInterface;
1515
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16+
use Symfony\Component\HttpFoundation\BinaryFileResponse;
1617
use Symfony\Component\HttpFoundation\Response;
1718
use Symfony\Component\HttpKernel\Event\RequestEvent;
1819
use Symfony\Component\HttpKernel\Event\ResponseEvent;
@@ -131,17 +132,20 @@ public function onKernelRequest(RequestEvent $event): void
131132

132133
$this->profiler?->disable();
133134

134-
$mediaType = $this->getMediaType($asset->publicPath);
135-
$response = (new Response(
136-
$asset->content,
137-
headers: $mediaType ? ['Content-Type' => $mediaType] : [],
138-
))
135+
if (null !== $asset->content) {
136+
$response = new Response($asset->content);
137+
} else {
138+
$response = new BinaryFileResponse($asset->sourcePath, autoLastModified: false);
139+
}
140+
$response
139141
->setPublic()
140-
->setMaxAge(604800)
142+
->setMaxAge(604800) // 1 week
141143
->setImmutable()
142144
->setEtag($asset->digest)
143145
;
144-
146+
if ($mediaType = $this->getMediaType($asset->publicPath)) {
147+
$response->headers->set('Content-Type', $mediaType);
148+
}
145149
$response->headers->set('X-Assets-Dev', true);
146150

147151
$event->setResponse($response);

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,13 @@ private function createManifestAndWriteFiles(SymfonyStyle $io, string $publicDir
139139
foreach ($allAssets as $asset) {
140140
// $asset->getPublicPath() will start with a "/"
141141
$targetPath = $publicDir.$asset->publicPath;
142-
143-
if (!is_dir($dir = \dirname($targetPath))) {
144-
$this->filesystem->mkdir($dir);
142+
if (null !== $asset->content) {
143+
// The original content has been modified by the AssetMapperCompiler
144+
$this->filesystem->dumpFile($targetPath, $asset->content);
145+
} else {
146+
$this->filesystem->copy($asset->sourcePath, $targetPath, true);
145147
}
146148

147-
$this->filesystem->dumpFile($targetPath, $asset->content);
148149
$manifest[$asset->logicalPath] = $asset->publicPath;
149150
}
150151
ksort($manifest);

src/Symfony/Component/AssetMapper/Factory/MappedAssetFactory.php

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,12 @@ public function __construct(
3737

3838
public function createMappedAsset(string $logicalPath, string $sourcePath): ?MappedAsset
3939
{
40-
if (\in_array($logicalPath, $this->assetsBeingCreated, true)) {
40+
if (isset($this->assetsBeingCreated[$logicalPath])) {
4141
throw new CircularAssetsException(sprintf('Circular reference detected while creating asset for "%s": "%s".', $logicalPath, implode(' -> ', $this->assetsBeingCreated).' -> '.$logicalPath));
4242
}
43+
$this->assetsBeingCreated[$logicalPath] = $logicalPath;
4344

4445
if (!isset($this->assetsCache[$logicalPath])) {
45-
$this->assetsBeingCreated[] = $logicalPath;
46-
4746
$isVendor = $this->isVendor($sourcePath);
4847
$asset = new MappedAsset($logicalPath, $sourcePath, $this->assetsPathResolver->resolvePublicPath($logicalPath), isVendor: $isVendor);
4948

@@ -54,7 +53,7 @@ public function createMappedAsset(string $logicalPath, string $sourcePath): ?Map
5453
$asset->sourcePath,
5554
$asset->publicPathWithoutDigest,
5655
$this->getPublicPath($asset),
57-
$this->calculateContent($asset),
56+
$this->compileContent($asset),
5857
$digest,
5958
$isPredigested,
6059
$isVendor,
@@ -64,10 +63,10 @@ public function createMappedAsset(string $logicalPath, string $sourcePath): ?Map
6463
);
6564

6665
$this->assetsCache[$logicalPath] = $asset;
67-
68-
array_pop($this->assetsBeingCreated);
6966
}
7067

68+
unset($this->assetsBeingCreated[$logicalPath]);
69+
7170
return $this->assetsCache[$logicalPath];
7271
}
7372

@@ -83,28 +82,35 @@ private function getDigest(MappedAsset $asset): array
8382
return [$matches[1], true];
8483
}
8584

85+
// Use the compiled content if any
86+
if (null !== $content = $this->compileContent($asset)) {
87+
return [hash('xxh128', $content), false];
88+
}
89+
8690
return [
87-
hash('xxh128', $this->calculateContent($asset)),
91+
hash_file('xxh128', $asset->sourcePath),
8892
false,
8993
];
9094
}
9195

92-
private function calculateContent(MappedAsset $asset): string
96+
private function compileContent(MappedAsset $asset): ?string
9397
{
94-
if (isset($this->fileContentsCache[$asset->logicalPath])) {
98+
if (\array_key_exists($asset->logicalPath, $this->fileContentsCache)) {
9599
return $this->fileContentsCache[$asset->logicalPath];
96100
}
97101

98102
if (!is_file($asset->sourcePath)) {
99103
throw new RuntimeException(sprintf('Asset source path "%s" could not be found.', $asset->sourcePath));
100104
}
101105

106+
if (!$this->compiler->supports($asset)) {
107+
return $this->fileContentsCache[$asset->logicalPath] = null;
108+
}
109+
102110
$content = file_get_contents($asset->sourcePath);
103111
$content = $this->compiler->compile($content, $asset);
104112

105-
$this->fileContentsCache[$asset->logicalPath] = $content;
106-
107-
return $content;
113+
return $this->fileContentsCache[$asset->logicalPath] = $content;
108114
}
109115

110116
private function getPublicPath(MappedAsset $asset): ?string

src/Symfony/Component/AssetMapper/MappedAsset.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ final class MappedAsset
2424
public readonly string $publicPath;
2525
public readonly string $publicPathWithoutDigest;
2626
public readonly string $publicExtension;
27-
public readonly string $content;
27+
28+
/**
29+
* The final content of this asset, if different from the source.
30+
*/
31+
public readonly ?string $content;
32+
2833
public readonly string $digest;
2934
public readonly bool $isPredigested;
3035
public readonly bool $isVendor;
@@ -73,9 +78,7 @@ public function __construct(
7378
$this->publicPathWithoutDigest = $publicPathWithoutDigest;
7479
$this->publicExtension = pathinfo($publicPathWithoutDigest, \PATHINFO_EXTENSION);
7580
}
76-
if (null !== $content) {
77-
$this->content = $content;
78-
}
81+
$this->content = $content;
7982
if (null !== $digest) {
8083
$this->digest = $digest;
8184
}

0 commit comments

Comments
 (0)