EasyAdmin assets are not compatible with Symfony `base_urls` asset setting (using default EasyAdmin templates)
Describe the bug According to https://github.com/EasyCorp/EasyAdminBundle/issues/2276#issuecomment-459075221 and https://github.com/EasyCorp/EasyAdminBundle/issues/5264#issuecomment-1149419438, I should be able to use the Symfony assets configuration to put EasyAdmin assets on a CDN (in my case I'm trying to point to S3).
However, when setting base_urls in the yaml config, the login form and dashboard template are still rendered with relative URLs to /bundles/easyadmin/[x].[js|css]. I've tried adding the base_urls globally (which affects everything but EasyAdmin assets), and also adding a specific package entry for easyadmin.assets.package, which is the resolved constant EasyCorp\\Bundle\\EasyAdminBundle\\Asset\\AssetPackage::PACKAGE_NAME taken from the use of {{ asset('app.js', constant('EasyCorp\\Bundle\\EasyAdminBundle\\Asset\\AssetPackage::PACKAGE_NAME')) }} in login_minimal.html.twig (which login.html.twig extends).
Every time (no matter how much I refresh, clear cache, tear-down and rebuild the project, etc), the assets are linked to
<link rel="stylesheet" href="/bundles/easyadmin/app.0bf72e55.css">
<script src="/bundles/easyadmin/app.1e1ba55d.js"></script>
In desperation, I've tried setting the base_urls in both framework.yaml and webpack_encore.yaml... :D
framework:
assets:
base_urls:
- '%env(ASSETS_BASE_URL)%' # works for all assets, EXCEPT EasyAdmin
packages:
easyadmin.assets.package: # Not even this works :/
base_urls:
- '%env(ASSETS_BASE_URL)%'
I looked at the content of \EasyCorp\Bundle\EasyAdminBundle\Asset\AssetPackage and in the constructor a new PathPackage is being created each time with the path /bundles/easyadmin.
If I modify this as so,
final class AssetPackage implements PackageInterface
{
public const PACKAGE_NAME = 'easyadmin.assets.package';
// ...
public function __construct(RequestStack $requestStack)
{
$this->package = new UrlPackage(
[
'https://yahoo.com/'
],
new JsonManifestVersionStrategy(__DIR__ . '/../Resources/public/manifest.json'),
new RequestStackContext($requestStack)
);
// $this->package = new PathPackage(
// '/bundles/easyadmin',
// new JsonManifestVersionStrategy(__DIR__.'/../Resources/public/manifest.json'),
// new RequestStackContext($requestStack)
// );
}
Then the rendered asset links are:
<link rel="stylesheet" href="https://yahoo.com/app.0bf72e55.css">
<script src="https://yahoo.com/app.1e1ba55d.js"></script>
I believe that setting a new PathPackage in the constructor of the AssetPackage class used is preventing the linked assets from inheriting the value from Symfony. Unfortunately I don't know enough about the Symfony asset system to know how's best to investigate further from here
To Reproduce
Version: "easycorp/easyadmin-bundle": "^4.3", Symfony 6.1
- Install
EasyAdminBundle - Use the default
login.html.twigand CRUDController rendering - Try setting
base_urlsto any CDN URL in eitherframework.assetsorframework.assets.packagesin eitherframework.yamlorwebpack_encore.yaml - Notice that all other assets in the application (non-EasyAdmin) are linked to the CDN
- EasyAdmin assets are not linked to the CDN
In the meantime, I found a workaround for this, which is as follows:
Create an assets package with a UrlPackage inside it, rather than the PathPackage that ships as default
<?php
namespace App\Controller\Admin;
use Symfony\Component\Asset\Context\RequestStackContext;
use Symfony\Component\Asset\PackageInterface;
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
use Symfony\Component\HttpFoundation\RequestStack;
class CDNAssetsPackage implements PackageInterface
{
private PackageInterface $package;
public function __construct(RequestStack $requestStack, string|array $cdnBaseUrls)
{
$this->package = new UrlPackage(
$cdnBaseUrls,
// make sure the path to manifest.json is correct relative to this class,
// because we need this in order to inherit EasyAdmin's asset versioning
new JsonManifestVersionStrategy(__DIR__ . '/../../../vendor/easycorp/easyadmin-bundle/src/Resources/public/manifest.json'),
new RequestStackContext($requestStack)
);
}
public function getUrl(string $assetPath): string
{
return $this->package->getUrl($assetPath);
}
public function getVersion(string $assetPath): string
{
return $this->package->getVersion($assetPath);
}
}
Wire it in services.yaml as a decorator of the default EasyAdmin one when in prod. I also tried replacing it altogether, but that ended up being more messy. Although we 'decorate' the EasyAdmin one, we don't actually use it at all:
services:
App\Controller\Admin\CDNAssetsPackage:
arguments:
$requestStack: "@request_stack"
$cdnBaseUrls:
- '%env(ASSETS_BASE_URL)%'
when@prod:
services:
App\Controller\Admin\CDNAssetsPackage:
decorates: EasyCorp\Bundle\EasyAdminBundle\Asset\AssetPackage
arguments:
$requestStack: "@request_stack"
$cdnBaseUrls:
- '%env(ASSETS_BASE_URL)%'
Unfortunately this requires additional configuration to just adding the the package's fully qualified name in framework.yaml under framework.assets.packages, but I couldn't find another way of getting the CDN link and the versioning strategy working.
For some reason, Symfony also demands that the arguments for CDNAssetsPackage are wired in both places. I might come back and clean that up later, but honestly I'm running out of patience on this now :D
Hi, What's the status about this issue, is there any PR fixing the problem and adding support for symfony's base_url and base_path config ?
im facing the same issue: using PathPackage instead of UrlPackage from symfony seems to be the problem as twig function asset will just call this class function getUrl() and give us a relative path…
@nealio82 solution seems to be a good workaround
at the end of the design.rst file there is this: "However, if you want total control over the backend styles, you can use Webpack
to integrate the SCSS and JavaScript source files provided in the assets/
directory. The only caveat is that EasyAdmin doesn't use Webpack Encore yet when
loading the assets, so you can't use features like versioning. This will be
fixed in future versions."
then we should probably add all assets from easy admin to our own webpack.config.js and use them
finally we went with the solution of @nealio82 which is simple and good enough.
<?php
declare(strict_types=1);
namespace App\Admin;
use Symfony\Component\Asset\Context\RequestStackContext;
use Symfony\Component\Asset\PackageInterface;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* taken from: https://github.com/EasyCorp/EasyAdminBundle/issues/5382#issuecomment-1229448568.
*/
class CDNAssetsPackage implements PackageInterface
{
private PackageInterface $package;
public function __construct(RequestStack $requestStack, string $cdnBaseUrl)
{
if ('' === $cdnBaseUrl) {
$this->package = new PathPackage(
'/bundles/easyadmin',
// make sure the path to manifest.json is correct relative to this class,
// because we need this in order to inherit EasyAdmin's asset versioning
new JsonManifestVersionStrategy(__DIR__.'/../../public/bundles/easyadmin/manifest.json'),
new RequestStackContext($requestStack)
);
} else {
$this->package = new UrlPackage(
$cdnBaseUrl.'/bundles/easyadmin',
// make sure the path to manifest.json is correct relative to this class,
// because we need this in order to inherit EasyAdmin's asset versioning
new JsonManifestVersionStrategy(__DIR__.'/../../public/bundles/easyadmin/manifest.json'),
new RequestStackContext($requestStack)
);
}
}
public function getUrl(string $path): string
{
return $this->package->getUrl($path);
}
public function getVersion(string $path): string
{
return $this->package->getVersion($path);
}
}
and in services.yaml, no need for when@prod
services:
App\Admin\CDNAssetsPackage:
decorates: EasyCorp\Bundle\EasyAdminBundle\Asset\AssetPackage
arguments:
$requestStack: '@request_stack'
$cdnBaseUrl: '%env(ASSETS_BASE_URL)%'
thanks a lot @nealio82 for your answer
Hi @jgroc-de could you give me the path and name of the first file (the one implementing the CDNAssetsPackage)
i do not understand your question, nothing implement CDNAssetsPackage. You need to create it (CDNAssetsPackage.php. I did ./src/admin in my project, could be in ./src/services/easyAdminOrWhatever if you prefer) and to add the conf with the right namespace