Skip to content

Adding support for AssetMapper 6.4 to StimulusBundle, etc #1240

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
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
16 changes: 10 additions & 6 deletions src/React/src/AssetMapper/ReactControllerLoaderAssetCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@

namespace Symfony\UX\React\AssetMapper;

use Symfony\Component\AssetMapper\AssetDependency;
use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface;
use Symfony\Component\AssetMapper\Compiler\AssetCompilerPathResolverTrait;
use Symfony\Component\AssetMapper\MappedAsset;
use Symfony\Component\Filesystem\Path;
use Symfony\Component\Finder\Finder;

/**
Expand All @@ -24,8 +25,6 @@
*/
class ReactControllerLoaderAssetCompiler implements AssetCompilerInterface
{
use AssetCompilerPathResolverTrait;

public function __construct(
private string $controllerPath,
private array $nameGlobs,
Expand All @@ -41,10 +40,15 @@ public function compile(string $content, MappedAsset $asset, AssetMapperInterfac
{
$importLines = [];
$componentParts = [];
$loaderPublicPath = $asset->publicPathWithoutDigest;
foreach ($this->findControllerAssets($assetMapper) as $name => $mappedAsset) {
$controllerPublicPath = $mappedAsset->publicPathWithoutDigest;
$relativeImportPath = $this->createRelativePath($loaderPublicPath, $controllerPublicPath);
// @legacy: backwards compatibility with Symfony 6.3
if (class_exists(AssetDependency::class)) {
$controllerPublicPath = $mappedAsset->publicPathWithoutDigest;
$loaderPublicPath = $asset->publicPathWithoutDigest;
$relativeImportPath = Path::makeRelative($controllerPublicPath, \dirname($loaderPublicPath));
} else {
$relativeImportPath = Path::makeRelative($mappedAsset->sourcePath, \dirname($asset->sourcePath));
}

$controllerNameForVariable = sprintf('component_%s', \count($componentParts));

Expand Down
30 changes: 13 additions & 17 deletions src/React/src/DependencyInjection/ReactExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
namespace Symfony\UX\React\DependencyInjection;

use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\AssetMapper\Compiler\AssetCompilerPathResolverTrait;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand Down Expand Up @@ -44,22 +43,19 @@ public function load(array $configs, ContainerBuilder $container)
->setPublic(false)
;

// on older versions, the absence of this trait will trigger an error if the service is loaded
if (trait_exists(AssetCompilerPathResolverTrait::class)) {
$container->setDefinition('react.asset_mapper.react_controller_loader_compiler', new Definition(ReactControllerLoaderAssetCompiler::class))
->setArguments([
$config['controllers_path'],
$config['name_glob'],
])
// run before the core JavaScript compiler
->addTag('asset_mapper.compiler', ['priority' => 100]);

$container->setDefinition('react.asset_mapper.replace_process_env_compiler', new Definition(ReactReplaceProcessEnvAssetCompiler::class))
->setArguments([
'%kernel.debug%',
])
->addTag('asset_mapper.compiler');
}
$container->setDefinition('react.asset_mapper.react_controller_loader_compiler', new Definition(ReactControllerLoaderAssetCompiler::class))
->setArguments([
$config['controllers_path'],
$config['name_glob'],
])
// run before the core JavaScript compiler
->addTag('asset_mapper.compiler', ['priority' => 100]);

$container->setDefinition('react.asset_mapper.replace_process_env_compiler', new Definition(ReactReplaceProcessEnvAssetCompiler::class))
->setArguments([
'%kernel.debug%',
])
->addTag('asset_mapper.compiler');
}

public function prepend(ContainerBuilder $container)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ public function testCompileDynamicallyAddsContents()
if (str_contains($sourcePath, 'MyReactController')) {
return new MappedAsset(
'MyReactController.js',
'/project/assets/react/controllers/MyReactController.js',
publicPathWithoutDigest: '/assets/react/controllers/MyReactController.js',
);
}

if (str_contains($sourcePath, 'DeeperReactController')) {
return new MappedAsset(
'subdir/DeeperReactController.js',
'/project/assets/react/controllers/subdir/DeeperReactController.js',
publicPathWithoutDigest: '/assets/react/controllers/subdir/DeeperReactController.js',
);
}
Expand All @@ -50,7 +52,7 @@ public function testCompileDynamicallyAddsContents()
['*.js']
);

$loaderAsset = new MappedAsset('loader.js', publicPathWithoutDigest: '/assets/symfony/ux-react/loader.js');
$loaderAsset = new MappedAsset('loader.js', '/project/assets/vendor/StimulusBundle/loader.js', publicPathWithoutDigest: '/assets/symfony/ux-react/loader.js');
$startingContents = file_get_contents(__DIR__.'/../../assets/dist/loader.js');

$compiledContents = $compiler->compile($startingContents, $loaderAsset, $assetMapper);
Expand Down
3 changes: 2 additions & 1 deletion src/StimulusBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/finder": "^5.4|^6.0|^7.0",
"symfony/http-kernel": "^5.4|^6.0|^7.0",
"twig/twig": "^2.15.3|^3.4.3"
"twig/twig": "^2.15.3|^3.4.3",
"symfony/deprecation-contracts": "^2.0|^3.0"
},
"require-dev": {
"symfony/asset-mapper": "^6.3|^7.0",
Expand Down
10 changes: 10 additions & 0 deletions src/StimulusBundle/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\UX\StimulusBundle\AssetMapper\AutoImportLocator;
use Symfony\UX\StimulusBundle\AssetMapper\ControllersMapGenerator;
use Symfony\UX\StimulusBundle\AssetMapper\StimulusLoaderJavaScriptCompiler;
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
Expand Down Expand Up @@ -64,6 +65,15 @@
service('stimulus.asset_mapper.ux_package_reader'),
abstract_arg('controller paths'),
abstract_arg('controllers_json_path'),
// @legacy - only allowing null for framework-bundle 6.3
service('stimulus.asset_mapper.auto_import_locator')->nullOnInvalid(),
])

// @legacy - is removed in 6.3
->set('stimulus.asset_mapper.auto_import_locator', AutoImportLocator::class)
->args([
service('asset_mapper.importmap.config_reader'),
service('asset_mapper'),
])

->set('stimulus.asset_mapper.loader_javascript_compiler', StimulusLoaderJavaScriptCompiler::class)
Expand Down
72 changes: 72 additions & 0 deletions src/StimulusBundle/src/AssetMapper/AutoImportLocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\StimulusBundle\AssetMapper;

use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\AssetMapper\ImportMap\ImportMapConfigReader;
use Symfony\Component\AssetMapper\MappedAsset;
use Symfony\UX\StimulusBundle\Ux\UxPackageMetadata;

/**
* Finds the MappedAsset for an "autoimport" string.
*/
class AutoImportLocator
{
public function __construct(
private ImportMapConfigReader $importMapConfigReader,
private AssetMapperInterface $assetMapper,
) {
}

// parts of this method are duplicated & adapted from UxControllersTwigRuntime
public function locateAutoImport(string $path, UxPackageMetadata $packageMetadata): MappedControllerAutoImport
{
// see if this is a mapped asset path
if ($asset = $this->assetMapper->getAsset($path)) {
return new MappedControllerAutoImport($asset->sourcePath, false);
}

$slashPosition = strpos($path, '/');
if (false === $slashPosition) {
throw new \LogicException(sprintf('The autoimport "%s" is not valid.', $path));
}

$parts = explode('/', ltrim($path, '@'));
if (2 > \count($parts)) {
throw new \LogicException(sprintf('The autoimport "%s" is not valid.', $path));
}
$package = implode('/', \array_slice($parts, 0, 2));
$file = implode('/', \array_slice($parts, 2));

if ($package === $packageMetadata->packageName) {
// this is a file local to the ux package
$filePath = $packageMetadata->packageDirectory.'/'.$file;
if (!is_file($filePath)) {
throw new \LogicException(sprintf('An "autoimport" in "controllers.json" refers to "%s". This path could not be found in the asset mapper and the file "%s" does not exist in the package path "%s". And so, the file cannot be loaded.', $path, $filePath, $packageMetadata->packageDirectory));
}

$asset = $this->assetMapper->getAssetFromSourcePath($filePath);
if (!$asset) {
throw new \LogicException(sprintf('An "autoimport" in "controllers.json" refers to "%s". This file was found, but the path is not in the asset mapper. And so, the file cannot be loaded. This is a misconfiguration with the bundle providing this.', $path));
}

return new MappedControllerAutoImport($asset->sourcePath, false);
}

$entry = $this->importMapConfigReader->findRootImportMapEntry($path);
if (!$entry) {
throw new \LogicException(sprintf('The autoimport "%s" could not be found in importmap.php. Try running "importmap:require %s".', $path, $path));
}

return new MappedControllerAutoImport($path, true);
}
}
35 changes: 33 additions & 2 deletions src/StimulusBundle/src/AssetMapper/ControllersMapGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
namespace Symfony\UX\StimulusBundle\AssetMapper;

use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\AssetMapper\ImportMap\ImportMapGenerator;
use Symfony\Component\Finder\Finder;
use Symfony\UX\StimulusBundle\Ux\UxPackageMetadata;
use Symfony\UX\StimulusBundle\Ux\UxPackageReader;

/**
Expand All @@ -31,6 +33,7 @@ public function __construct(
private UxPackageReader $uxPackageReader,
private array $controllerPaths,
private string $controllersJsonPath,
private ?AutoImportLocator $autoImportLocator = null,
) {
}

Expand Down Expand Up @@ -72,7 +75,8 @@ private function loadCustomControllers(): array
$name = str_replace(['_', '/'], ['-', '--'], $name);

$asset = $this->assetMapper->getAssetFromSourcePath($file->getRealPath());
$isLazy = preg_match('/\/\*\s*stimulusFetch:\s*\'lazy\'\s*\*\//i', $asset->content);
$content = $asset->content ?: file_get_contents($asset->sourcePath);
$isLazy = preg_match('/\/\*\s*stimulusFetch:\s*\'lazy\'\s*\*\//i', $content);

$controllersMap[$name] = new MappedControllerAsset($asset, $isLazy);
}
Expand Down Expand Up @@ -129,10 +133,37 @@ private function loadUxControllers(): array
throw new \RuntimeException(sprintf('Could not find an asset mapper path that points to the "%s" controller in package "%s", defined in controllers.json.', $controllerName, $packageMetadata->packageName));
}

$controllersMap[$controllerNormalizedName] = new MappedControllerAsset($asset, $lazy);
$autoImports = $this->collectAutoImports($localControllerConfig['autoimport'] ?? [], $packageMetadata);

$controllersMap[$controllerNormalizedName] = new MappedControllerAsset($asset, $lazy, $autoImports);
}
}

return $controllersMap;
}

/**
* @return MappedControllerAutoImport[]
*/
private function collectAutoImports(array $autoImports, UxPackageMetadata $currentPackageMetadata): array
{
// @legacy: Backwards compatibility with Symfony 6.3
if (!class_exists(ImportMapGenerator::class)) {
return [];
}
if (null === $this->autoImportLocator) {
throw new \InvalidArgumentException(sprintf('The "autoImportLocator" argument to "%s" is required when using AssetMapper 6.4', self::class));
}

$autoImportItems = [];
foreach ($autoImports as $path => $enabled) {
if (!$enabled) {
continue;
}

$autoImportItems[] = $this->autoImportLocator->locateAutoImport($path, $currentPackageMetadata);
}

return $autoImportItems;
}
}
4 changes: 4 additions & 0 deletions src/StimulusBundle/src/AssetMapper/MappedControllerAsset.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class MappedControllerAsset
public function __construct(
public MappedAsset $asset,
public bool $isLazy,
/**
* @var MappedControllerAutoImport[]
*/
public array $autoImports = [],
) {
}
}
26 changes: 26 additions & 0 deletions src/StimulusBundle/src/AssetMapper/MappedControllerAutoImport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\StimulusBundle\AssetMapper;

/**
* @experimental
*
* @author Ryan Weaver <[email protected]>
*/
class MappedControllerAutoImport
{
public function __construct(
public string $path,
public bool $isBareImport
) {
}
}
Loading