Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f3a09b9

Browse filesBrowse files
committed
feature #51829 [AssetMapper] Automatically preload CSS files if WebLink available (weaverryan)
This PR was squashed before being merged into the 6.4 branch. Discussion ---------- [AssetMapper] Automatically preload CSS files if WebLink available | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | None | License | MIT Hi! In AssetMapper 6.4, we now know which CSS files will be rendered on the page. So, I think there is now downside to adding a `Link` header in the response to load those CSS files, but someone tell me if I'm missing something. The only possible situation I can think of where this isn't wanted is if you decided to handle your critical CSS manually (i.e. with normal `<link rel="stylesheet">` tags in `base.html.twig`) and load a few less-critical CSS files through the ImportMap system. In that case, we would preload only these "less-critical" CSS, which could take a slight priority over the others. But I think the benefit this would give to most apps outweighs this. We could always add an opt-out later. Cheers! Commits ------- 2995c16 [AssetMapper] Automatically preload CSS files if WebLink available
2 parents 4e16f7b + 2995c16 commit f3a09b9
Copy full SHA for f3a09b9

File tree

5 files changed

+75
-1
lines changed
Filter options

5 files changed

+75
-1
lines changed

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
param('kernel.charset'),
175175
abstract_arg('polyfill URL'),
176176
abstract_arg('script HTML attributes'),
177+
service('request_stack'),
177178
])
178179

179180
->set('asset_mapper.importmap.auditor', ImportMapAuditor::class)

‎src/Symfony/Component/AssetMapper/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add "entrypoints" concept to the importmap
1010
* Always download packages locally instead of using a CDN
1111
* Allow relative path strings in the importmap
12+
* Automatically set `_links` attribute for preload CSS files for WebLink integration
1213
* Add `PreAssetsCompileEvent` event when running `asset-map:compile`
1314
* Add support for importmap paths to use the Asset component (for subdirectories)
1415
* Removed the `importmap:export` command

‎src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php
+32Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111

1212
namespace Symfony\Component\AssetMapper\ImportMap;
1313

14+
use Psr\Link\EvolvableLinkProviderInterface;
1415
use Symfony\Component\Asset\Packages;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpFoundation\RequestStack;
18+
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
19+
use Symfony\Component\WebLink\GenericLinkProvider;
20+
use Symfony\Component\WebLink\Link;
1521

1622
/**
1723
* @author Kévin Dunglas <kevin@dunglas.dev>
@@ -27,6 +33,7 @@ public function __construct(
2733
private readonly string $charset = 'UTF-8',
2834
private readonly string|false $polyfillUrl = ImportMapManager::POLYFILL_URL,
2935
private readonly array $scriptAttributes = [],
36+
private readonly ?RequestStack $requestStack = null,
3037
) {
3138
}
3239

@@ -68,6 +75,10 @@ public function render(string|array $entryPoint, array $attributes = []): string
6875
$output .= "\n<link rel=\"stylesheet\" href=\"$url\">";
6976
}
7077

78+
if (class_exists(AddLinkHeaderListener::class) && $request = $this->requestStack?->getCurrentRequest()) {
79+
$this->addWebLinkPreloads($request, $cssLinks);
80+
}
81+
7182
$scriptAttributes = $this->createAttributesString($attributes);
7283
$importMapJson = json_encode(['imports' => $importMap], \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG);
7384
$output .= <<<HTML
@@ -131,4 +142,25 @@ private function createAttributesString(array $attributes): string
131142

132143
return $attributeString;
133144
}
145+
146+
private function addWebLinkPreloads(Request $request, array $cssLinks): void
147+
{
148+
$cssPreloadLinks = array_map(fn ($url) => new Link('preload', $url), $cssLinks);
149+
150+
if (null === $linkProvider = $request->attributes->get('_links')) {
151+
$request->attributes->set('_links', new GenericLinkProvider($cssPreloadLinks));
152+
153+
return;
154+
}
155+
156+
if (!$linkProvider instanceof EvolvableLinkProviderInterface) {
157+
return;
158+
}
159+
160+
foreach ($cssPreloadLinks as $link) {
161+
$linkProvider = $linkProvider->withLink($link);
162+
}
163+
164+
$request->attributes->set('_links', $linkProvider);
165+
}
134166
}

‎src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php
+39Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
use Symfony\Component\Asset\Packages;
1616
use Symfony\Component\AssetMapper\ImportMap\ImportMapManager;
1717
use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer;
18+
use Symfony\Component\HttpFoundation\Request;
19+
use Symfony\Component\HttpFoundation\RequestStack;
20+
use Symfony\Component\WebLink\GenericLinkProvider;
1821

1922
class ImportMapRendererTest extends TestCase
2023
{
@@ -130,4 +133,40 @@ private function createBasicImportMapManager(): ImportMapManager
130133

131134
return $importMapManager;
132135
}
136+
137+
public function testItAddsPreloadLinks()
138+
{
139+
$importMapManager = $this->createMock(ImportMapManager::class);
140+
$importMapManager->expects($this->once())
141+
->method('getImportMapData')
142+
->willReturn([
143+
'app_js_preload' => [
144+
'path' => '/assets/app-preload-d1g35t.js',
145+
'type' => 'js',
146+
'preload' => true,
147+
],
148+
'app_css_preload' => [
149+
'path' => '/assets/styles/app-preload-d1g35t.css',
150+
'type' => 'css',
151+
'preload' => true,
152+
],
153+
'app_css_no_preload' => [
154+
'path' => '/assets/styles/app-nopreload-d1g35t.css',
155+
'type' => 'css',
156+
],
157+
]);
158+
159+
$request = Request::create('/foo');
160+
$requestStack = new RequestStack();
161+
$requestStack->push($request);
162+
163+
$renderer = new ImportMapRenderer($importMapManager, requestStack: $requestStack);
164+
$renderer->render(['app']);
165+
166+
$linkProvider = $request->attributes->get('_links');
167+
$this->assertInstanceOf(GenericLinkProvider::class, $linkProvider);
168+
$this->assertCount(1, $linkProvider->getLinks());
169+
$this->assertSame(['preload'], $linkProvider->getLinks()[0]->getRels());
170+
$this->assertSame('/assets/styles/app-preload-d1g35t.css', $linkProvider->getLinks()[0]->getHref());
171+
}
133172
}

‎src/Symfony/Component/AssetMapper/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/composer.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"symfony/finder": "^5.4|^6.0|^7.0",
3030
"symfony/framework-bundle": "^6.4|^7.0",
3131
"symfony/http-foundation": "^5.4|^6.0|^7.0",
32-
"symfony/http-kernel": "^5.4|^6.0|^7.0"
32+
"symfony/http-kernel": "^5.4|^6.0|^7.0",
33+
"symfony/web-link": "^5.4|^6.0|^7.0"
3334
},
3435
"conflict": {
3536
"symfony/framework-bundle": "<6.4"

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.