Skip to content

Commit 4488525

Browse files
committed
Allow to add AssetMapper entries in dashboards and CRUDs
1 parent 5d80405 commit 4488525

File tree

9 files changed

+84
-8
lines changed

9 files changed

+84
-8
lines changed

doc/design.rst

+12
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,14 @@ the :doc:`CRUD controllers </crud>` to add your own CSS and JavaScript files::
247247
public function configureAssets(Assets $assets): Assets
248248
{
249249
return $assets
250+
// imports the given entrypoint defined in the importmap.php file of AssetMapper
251+
// it's equivalent to adding this inside the <head> element:
252+
// {{ importmap('admin') }}
253+
->addAssetMapperEntry('admin')
254+
// you can also import multiple entries
255+
// it's equivalent to calling {{ importmap(['app', 'admin']) }}
256+
->addAssetMapperEntry('app', 'admin')
257+
250258
// adds the CSS and JS assets associated to the given Webpack Encore entry
251259
// it's equivalent to adding these inside the <head> element:
252260
// {{ encore_entry_link_tags('...') }} and {{ encore_entry_script_tags('...') }}
@@ -288,6 +296,10 @@ and ``<script>`` tags, pass an ``Asset`` object to the ``addCssFile()``,
288296

289297
->addWebpackEncoreEntry(Asset::new('admin-app')->webpackEntrypointName('...'))
290298

299+
// adding full Asset objects for AssetMapper entries work too, but it's
300+
// useless because entries can't define any property, only their name
301+
->addAssetMapperEntry(Asset::new('admin'))
302+
291303
->addCssFile(Asset::new('build/admin-detail.css')->onlyOnDetail())
292304
->addJsFile(Asset::new('build/admin.js')->onlyWhenCreating())
293305
->addWebpackEncoreEntry(Asset::new('admin-app')->ignoreOnForm())

phpstan.neon.dist

+3
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ parameters:
1212
- vendor/bin/.phpunit/phpunit/vendor/autoload.php
1313
ignoreErrors:
1414
- '#Cannot use array destructuring on callable.#'
15+
- '#Property EasyCorp\\Bundle\\EasyAdminBundle\\Twig\\EasyAdminTwigExtension\:\:\$importMapRenderer has unknown class Symfony\\Component\\AssetMapper\\ImportMap\\ImportMapRenderer as its type\.#'
16+
- '#Parameter \$importMapRenderer of method EasyCorp\\Bundle\\EasyAdminBundle\\Twig\\EasyAdminTwigExtension\:\:__construct\(\) has invalid type Symfony\\Component\\AssetMapper\\ImportMap\\ImportMapRenderer\.#'
17+
- '#Call to method render\(\) on an unknown class Symfony\\Component\\AssetMapper\\ImportMap\\ImportMapRenderer\.#'
1518
treatPhpDocTypesAsCertain: false
1619
reportUnmatchedIgnoredErrors: false

src/Config/Asset.php

+6-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use EasyCorp\Bundle\EasyAdminBundle\Asset\AssetPackage;
66
use EasyCorp\Bundle\EasyAdminBundle\Dto\AssetDto;
7-
use function Symfony\Component\String\u;
87

98
/**
109
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
@@ -24,15 +23,15 @@ public function __toString()
2423
}
2524

2625
/**
27-
* @param string $value The 'path' when adding CSS or JS files and the 'entryName' when adding Webpack Encore entries
26+
* The argument is the 'path' when adding CSS or JS files and the 'entryName' when
27+
* adding Webpack Encore or ImportMap entries:
28+
*
29+
* Asset::new('build/admin.css')
30+
* Asset::new('some/path/admin.js')
31+
* Asset::new('admin-app') (Webpack Encore or AssetMapper entry)
2832
*/
2933
public static function new(string $value): self
3034
{
31-
$isWebpackEncoreEntry = !u($value)->endsWith('.css') && !u($value)->endsWith('.js');
32-
if ($isWebpackEncoreEntry && !class_exists('Symfony\\WebpackEncoreBundle\\WebpackEncoreBundle')) {
33-
throw new \RuntimeException(sprintf('You are trying to add a Webpack Encore entry called "%s" but WebpackEncoreBundle is not installed in your project. Try running "composer require symfony/webpack-encore-bundle"', $value));
34-
}
35-
3635
$dto = new AssetDto($value);
3736

3837
return new self($dto);

src/Config/Assets.php

+19
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,25 @@ public function addWebpackEncoreEntry(Asset|string $entryNameOrAsset): self
3939
return $this;
4040
}
4141

42+
public function addAssetMapperEntry(Asset|string ...$entryNameOrAsset): self
43+
{
44+
if (!class_exists('Symfony\\Component\\AssetMapper\\AssetMapper')) {
45+
$names = array_map(static fn (Asset|string $nameOrAsset) => \is_string($nameOrAsset) ? '"'.$nameOrAsset.'"' : '"'.$nameOrAsset->getAsDto()->getValue().'"', $entryNameOrAsset);
46+
47+
throw new \RuntimeException(sprintf('You are trying to add '.(1 === \count($names) ? 'an AssetMapper entry called %s' : ' some AssetMapper entries (%s)').' but the AssetMapper component is not installed in your project. Try running "composer require symfony/asset-mapper"', implode(', ', $names)));
48+
}
49+
50+
foreach ($entryNameOrAsset as $nameOrAsset) {
51+
if (\is_string($nameOrAsset)) {
52+
$this->dto->addAssetMapperAsset(new AssetDto($nameOrAsset));
53+
} else {
54+
$this->dto->addAssetMapperAsset($nameOrAsset->getAsDto());
55+
}
56+
}
57+
58+
return $this;
59+
}
60+
4261
public function addCssFile(Asset|string $pathOrAsset): self
4362
{
4463
if (\is_string($pathOrAsset)) {

src/Dto/AssetsDto.php

+19
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ final class AssetsDto
1212
/** @var AssetDto[] */
1313
private array $webpackEncoreAssets = [];
1414
/** @var AssetDto[] */
15+
private array $assetMapperAssets = [];
16+
/** @var AssetDto[] */
1517
private array $cssAssets = [];
1618
/** @var AssetDto[] */
1719
private array $jsAssets = [];
@@ -33,6 +35,15 @@ public function addWebpackEncoreAsset(AssetDto $assetDto): void
3335
$this->webpackEncoreAssets[$entryName] = $assetDto;
3436
}
3537

38+
public function addAssetMapperAsset(AssetDto $assetDto): void
39+
{
40+
if (\array_key_exists($entrypointName = $assetDto->getValue(), $this->assetMapperAssets)) {
41+
throw new \InvalidArgumentException(sprintf('The "%s" AssetMapper entry has been added more than once via the addAssetMapperAsset() method, but each entry can only be added once (to not overwrite its configuration).', $entrypointName));
42+
}
43+
44+
$this->assetMapperAssets[$entrypointName] = $assetDto;
45+
}
46+
3647
public function addCssAsset(AssetDto $assetDto): void
3748
{
3849
if (\array_key_exists($cssPath = $assetDto->getValue(), $this->cssAssets)) {
@@ -82,6 +93,14 @@ public function getWebpackEncoreAssets(): array
8293
return $this->webpackEncoreAssets;
8394
}
8495

96+
/**
97+
* @return AssetDto[]
98+
*/
99+
public function getAssetMapperAssets(): array
100+
{
101+
return $this->assetMapperAssets;
102+
}
103+
85104
/**
86105
* @return AssetDto[]
87106
*/

src/Resources/config/services.php

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
->arg(0, service('service_locator_'.AdminUrlGenerator::class))
133133
->arg(1, service(AdminContextProvider::class))
134134
->arg(2, new Reference('security.csrf.token_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE))
135+
->arg(3, new Reference('asset_mapper.importmap.renderer', ContainerInterface::NULL_ON_INVALID_REFERENCE))
135136
->tag('twig.extension')
136137

137138
->set(EaCrudFormTypeExtension::class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{# @var assets \EasyCorp\Bundle\EasyAdminBundle\Dto\AssetDto[] #}
2+
{{ ea_importmap(assets|map(asset => asset.value)) }}

src/Resources/views/layout.html.twig

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030

3131
{% block head_javascript %}
3232
<script src="{{ asset('app.js', ea.assets.defaultAssetPackageName) }}"></script>
33+
34+
{% block importmap %}
35+
{{ include('@EasyAdmin/includes/_importmap.html.twig', { assets: ea.assets.assetMapperAssets ?? [] }, with_context = false) }}
36+
{% endblock %}
3337
{% endblock head_javascript %}
3438

3539
{% block configured_javascripts %}

src/Twig/EasyAdminTwigExtension.php

+18-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
99
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
1010
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGeneratorInterface;
11+
use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer;
1112
use Symfony\Component\DependencyInjection\ServiceLocator;
1213
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
1314
use Twig\Environment;
@@ -29,12 +30,14 @@ class EasyAdminTwigExtension extends AbstractExtension implements GlobalsInterfa
2930
private ServiceLocator $serviceLocator;
3031
private AdminContextProvider $adminContextProvider;
3132
private ?CsrfTokenManagerInterface $csrfTokenManager;
33+
private ?ImportMapRenderer $importMapRenderer;
3234

33-
public function __construct(ServiceLocator $serviceLocator, AdminContextProvider $adminContextProvider, ?CsrfTokenManagerInterface $csrfTokenManager)
35+
public function __construct(ServiceLocator $serviceLocator, AdminContextProvider $adminContextProvider, ?CsrfTokenManagerInterface $csrfTokenManager, ?ImportMapRenderer $importMapRenderer)
3436
{
3537
$this->serviceLocator = $serviceLocator;
3638
$this->adminContextProvider = $adminContextProvider;
3739
$this->csrfTokenManager = $csrfTokenManager;
40+
$this->importMapRenderer = $importMapRenderer;
3841
}
3942

4043
public function getFunctions(): array
@@ -44,6 +47,7 @@ public function getFunctions(): array
4447
new TwigFunction('ea_csrf_token', [$this, 'renderCsrfToken']),
4548
new TwigFunction('ea_call_function_if_exists', [$this, 'callFunctionIfExists'], ['needs_environment' => true, 'is_safe' => ['html' => true]]),
4649
new TwigFunction('ea_create_field_layout', [$this, 'createFieldLayout']),
50+
new TwigFunction('ea_importmap', [$this, 'renderImportmap'], ['is_safe' => ['html']]),
4751
];
4852
}
4953

@@ -201,4 +205,17 @@ public function createFieldLayout(?FieldCollection $fieldDtos): FieldLayoutDto
201205

202206
return FormLayoutFactory::createFromFieldDtos($fieldDtos);
203207
}
208+
209+
/**
210+
* We need to recreate the 'importmap()' Twig function from Symfony because calling it
211+
* via 'ea_call_function_if_exists('importmap', '...')' doesn't work.
212+
*/
213+
public function renderImportmap(string|array $entryPoint = 'app', array $attributes = []): string
214+
{
215+
if ('' === $entryPoint || [] === $entryPoint || null === $this->importMapRenderer) {
216+
return '';
217+
}
218+
219+
return $this->importMapRenderer->render($entryPoint, $attributes);
220+
}
204221
}

0 commit comments

Comments
 (0)