8000 [AssetMapper] Automatically preload CSS files if WebLink available by weaverryan · Pull Request #51829 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content
8000

[AssetMapper] Automatically preload CSS files if WebLink available #51829

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@
param('kernel.charset'),
abstract_arg('polyfill URL'),
abstract_arg('script HTML attributes'),
service('request_stack'),
])

->set('asset_mapper.importmap.auditor', ImportMapAuditor::class)
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/AssetMapper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CHANGELOG
* Add "entrypoints" concept to the importmap
* Always download packages locally instead of using a CDN
* Allow relative path strings in the importmap
* Automatically set `_links` attribute for preload CSS files for WebLink integration
* Add `PreAssetsCompileEvent` event when running `asset-map:compile`
* Add support for importmap paths to use the Asset component (for subdirectories)
* Removed the `importmap:export` command
Expand Down
32 changes: 32 additions & 0 deletions src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@

namespace Symfony\Component\AssetMapper\ImportMap;

use Psr\Link\EvolvableLinkProviderInterface;
use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
use Symfony\Component\WebLink\GenericLinkProvider;
use Symfony\Component\WebLink\Link;

/**
* @author Kévin Dunglas <kevin@dunglas.dev>
Expand All @@ -27,6 +33,7 @@ public function __construct(
private readonly string $charset = 'UTF-8',
private readonly string|false $polyfillUrl = ImportMapManager::POLYFILL_URL,
private readonly array $scriptAttributes = [],
private readonly ?RequestStack $requestStack = null,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Injecting the request stack here feels a bit odd, but it's the best way I can think of to handle this.

) {
}

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

if (class_exists(AddLinkHeaderListener::class) && $request = $this->requestStack?->getCurrentRequest()) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same logic as in AbstractController

$this->addWebLinkPreloads($request, $cssLinks);
}

$scriptAttributes = $this->createAttributesString($attributes);
$importMapJson = json_encode(['imports' => $importMap], \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG);
$output .= <<<HTML
Expand Down Expand Up @@ -131,4 +142,25 @@ private function createAttributesString(array $attributes): string

return $attributeString;
}

private function addWebLinkPreloads(Request $request, array $cssLinks): void
{
$cssPreloadLinks = array_map(fn ($url) => new Link('preload', $url), $cssLinks);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but shouldn't the nopush attribute be better set here? So that the css files are not sent again and again, even if they are already in client cache.

From https://developer.chrome.com/blog/early-hints/:

with HTTP2/Push it was extremely hard to avoid pushing sub-resources that the browser already had. This "over-pushing" effect resulted in a less efficient usage of the network bandwidth, which significantly hindered the performance benefits.

In current Chrome versions it wouldn't make any difference, because Server Push is already disabled there. But afaik other browsers still support Server Push in current versions.

- $cssPreloadLinks = array_map(fn ($url) => new Link('preload', $url), $cssLinks);
+ $cssPreloadLinks = array_map(fn ($url) => (new Link('preload', $url))->withAttribute('nopush', true), $cssLinks);


if (null === $linkProvider = $request->attributes->get('_links')) {
$request->attributes->set('_links', new GenericLinkProvider($cssPreloadLinks));

return;
}

if (!$linkProvider instanceof EvolvableLinkProviderInterface) {
return;
}

foreach ($cssPreloadLinks as $link) {
$linkProvider = $linkProvider->withLink($link);
}

$request->attributes->set('_links', $linkProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
use Symfony\Component\Asset\Packages;
use Symfony\Component\AssetMapper\ImportMap\ImportMapManager;
use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\WebLink\GenericLinkProvider;

class ImportMapRendererTest extends TestCase
{
Expand Down Expand Up @@ -130,4 +133,40 @@ private function createBasicImportMapManager(): ImportMapManager

return $importMapManager;
}

public function testItAddsPreloadLinks()
{
$importMapManager = $this->createMock(ImportMapManager::class);
$importMapManager->expects($this->once())
->method('getImportMapData')
->willReturn([
'app_js_preload' => [
'path' => '/assets/app-preload-d1g35t.js',
'type' => 'js',
'preload' => true,
],
'app_css_preload' => [
'path' => '/assets/styles/app-preload-d1g35t.css',
'type' => 'css',
'preload' => true,
],
'app_css_no_preload' => [
'path' => '/assets/styles/app-nopreload-d1g35t.css',
'type' => 'css',
],
]);

$request = Request::create('/foo');
$requestStack = new RequestStack();
$requestStack->push($request);

$renderer = new ImportMapRenderer($importMapManager, requestStack: $requestStack);
$renderer->render(['app']);

$linkProvider = $request->attributes->get('_links');
$this->assertInstanceOf(GenericLinkProvider::class, $linkProvider);
$this->assertCount(1, $linkProvider->getLinks());
$this->assertSame(['preload'], $linkProvider->getLinks()[0]->getRels());
$this->assertSame('/assets/styles/app-preload-d1g35t.css', $linkProvider->getLinks()[0]->getHref());
}
}
3 changes: 2 additions & 1 deletion src/Symfony/Component/AssetMapper/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"symfony/finder": "^5.4|^6.0|^7.0",
"symfony/framework-bundle": "^6.4|^7.0",
"symfony/http-foundation": "^5.4|^6.0|^7.0",
"symfony/http-kernel": "^5.4|^6.0|^7.0"
"symfony/http-kernel": "^5.4|^6.0|^7.0",
"symfony/web-link": "^5.4|^6.0|^7.0"
},
"conflict": {
"symfony/framework-bundle": "<6.4"
Expand Down
0