Skip to content

Commit

Permalink
Fix component runtime for UX 2.22
Browse files Browse the repository at this point in the history
  • Loading branch information
squrious committed Jan 14, 2025
1 parent 8c0ca89 commit b83af48
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 83 deletions.
9 changes: 3 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,10 @@
"symfony/css-selector": "^6.4|^7.0",
"symfony/framework-bundle": "^6.4|^7.0",
"symfony/phpunit-bridge": "^6.4|^7.0",
"symfony/stimulus-bundle": "^2.21",
"symfony/ux-live-component": "^2.21",
"symfony/ux-twig-component": "^2.21",
"symfony/stimulus-bundle": "~2.22",
"symfony/ux-live-component": "~2.22",
"symfony/ux-twig-component": "~2.22",
"symfonycasts/sass-bundle": "^0.5.1",
"symfonycasts/tailwind-bundle": "^0.5.0"
},
"conflict": {
"symfony/ux-twig-component": "2.17"
}
}
32 changes: 32 additions & 0 deletions src/DependencyInjection/Compiler/StorybookRuntimeLoaderPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Storybook\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use function array_merge;

/**
* Prepends custom runtime loader in Storybook Twig environment.
*
* @author Nicolas Rigaud <[email protected]>
*/
final class StorybookRuntimeLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$twig = $container->getDefinition('storybook.twig');

$addRuntimeLoaderMethodCall = [
'addRuntimeLoader',
[new Reference('storybook.twig.runtime_loader')],
];

// Prepend the Storybook runtime loader to the default Twig ones
$twig->setMethodCalls(array_merge(
[$addRuntimeLoaderMethodCall],
$twig->getMethodCalls())
);
}
}
77 changes: 44 additions & 33 deletions src/DependencyInjection/StorybookExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
use Storybook\Exception\UnauthorizedStoryException;
use Storybook\Mock\ComponentProxyFactory;
use Storybook\StoryRenderer;
use Storybook\Twig\StorybookEnvironment;
use Storybook\Twig\StorybookEnvironmentConfigurator;
use Storybook\Twig\StorybookRuntimeLoader;
use Storybook\Twig\StoryExtension;
use Storybook\Twig\TwigComponentSubscriber;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
Expand All @@ -30,6 +30,7 @@
use Symfony\Component\DependencyInjection\Reference;
use Twig\Extension\SandboxExtension;
use Twig\Sandbox\SecurityPolicy;
use function array_merge_recursive;

/**
* @author Nicolas Rigaud <[email protected]>
Expand Down Expand Up @@ -70,6 +71,8 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance

$config = (new Processor())->processConfiguration($this, $configs);

$this->configureStorybookTwigEnvironment($container, $config);

// Proxy listener
$container->register('storybook.listener.proxy_request', ProxyRequestListener::class)
->addTag('kernel.event_subscriber');
Expand All @@ -82,6 +85,39 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance
;

// Story renderer
$container->register('storybook.story_renderer', StoryRenderer::class)
->setArgument(0, new Reference('storybook.twig'))
;

// Args processors
$container->register('storybook.args_processor', StorybookArgsProcessor::class);

// Proxy factory
$container->register('storybook.component_proxy_factory', ComponentProxyFactory::class)
->setArgument(0, new AbstractArgument(\sprintf('Provided in "%s".', ComponentMockPass::class)));

// Internal commands
$container->register('storybook.generate_preview_command', GeneratePreviewCommand::class)
->setArgument(0, new Reference('twig'))
->setArgument(1, new Reference('event_dispatcher'))
->addTag('console.command', ['name' => 'storybook:generate-preview'])
;

// Init command
$container->register('storybook.init_command', StorybookInitCommand::class)
->setArgument(0, $container->getParameter('kernel.project_dir'))
->addTag('console.command', ['name' => 'storybook:init']);

// Component subscriber
$container->register('storybook.twig.on_pre_render_listener', TwigComponentSubscriber::class)
->setArgument(0, new Reference('request_stack'))
->setArgument(1, new Reference('event_dispatcher'))
->addTag('kernel.event_subscriber');
}

private function configureStorybookTwigEnvironment(ContainerBuilder $container, array $config): void
{
// Sandbox
$defaultSandboxConfig = [
'allowedTags' => ['component'],
'allowedFunctions' => ['component'],
Expand All @@ -100,13 +136,16 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance
->setArgument(4, $sandboxConfig['allowedFunctions'])
;

// Storybook Twig extensions
// Storybook Twig environment
$container->setDefinition('storybook.twig', new ChildDefinition('twig'))
->setClass(StorybookEnvironment::class)
->addMethodCall('setComponentRuntime', [new Reference('storybook.twig.component_runtime')])
->setConfigurator([new Reference('storybook.twig.environment_configurator'), 'configure'])
;

$container->register('storybook.twig.runtime_loader', StorybookRuntimeLoader::class)
->addMethodCall('addRuntime', [new Reference('storybook.twig.component_runtime')])
;


$container->register('storybook.twig.extension.sandbox', SandboxExtension::class)
->setArgument(0, new Reference('storybook.twig.security_policy'))
->addTag('storybook.twig.extension')
Expand All @@ -123,42 +162,14 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance
->setArgument(2, $config['cache'] ?? false)
;

$container->setDefinition('storybook.twig.component_runtime', new ChildDefinition('.ux.twig_component.twig.component_runtime'))
$container->setDefinition('storybook.twig.component_runtime', new ChildDefinition('ux.twig_component.twig.component_runtime'))
->replaceArgument(0, new Reference('storybook.twig.component_renderer'))
;

$container->setDefinition('storybook.twig.component_renderer', new ChildDefinition('ux.twig_component.component_renderer'))
->replaceArgument(0, new Reference('storybook.twig'))
;

$container->register('storybook.story_renderer', StoryRenderer::class)
->setArgument(0, new Reference('storybook.twig'))
;

// Args processors
$container->register('storybook.args_processor', StorybookArgsProcessor::class);

// Proxy factory
$container->register('storybook.component_proxy_factory', ComponentProxyFactory::class)
->setArgument(0, new AbstractArgument(\sprintf('Provided in "%s".', ComponentMockPass::class)));

// Internal commands
$container->register('storybook.generate_preview_command', GeneratePreviewCommand::class)
->setArgument(0, new Reference('twig'))
->setArgument(1, new Reference('event_dispatcher'))
->addTag('console.command', ['name' => 'storybook:generate-preview'])
;

// Init command
$container->register('storybook.init_command', StorybookInitCommand::class)
->setArgument(0, $container->getParameter('kernel.project_dir'))
->addTag('console.command', ['name' => 'storybook:init']);

// Component subscriber
$container->register('storybook.twig.on_pre_render_listener', TwigComponentSubscriber::class)
->setArgument(0, new Reference('request_stack'))
->setArgument(1, new Reference('event_dispatcher'))
->addTag('kernel.event_subscriber');
}

public function getConfigTreeBuilder(): TreeBuilder
Expand Down
5 changes: 5 additions & 0 deletions src/StorybookBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use Storybook\DependencyInjection\Compiler\ArgsProcessorPass;
use Storybook\DependencyInjection\Compiler\ComponentMockPass;
use Storybook\DependencyInjection\Compiler\StorybookRuntimeLoaderPass;
use Storybook\DependencyInjection\StorybookExtension;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;
Expand All @@ -18,6 +20,9 @@ public function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new ArgsProcessorPass());
$container->addCompilerPass(new ComponentMockPass());

// Must be run AFTER ResolveChildDefinitionPass
$container->addCompilerPass(new StorybookRuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING);
}

public function getContainerExtension(): ?ExtensionInterface
Expand Down
44 changes: 0 additions & 44 deletions src/Twig/StorybookEnvironment.php

This file was deleted.

36 changes: 36 additions & 0 deletions src/Twig/StorybookRuntimeLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Storybook\Twig;

use InvalidArgumentException;
use Twig\RuntimeLoader\RuntimeLoaderInterface;

/**
* Runtime loader for custom runtimes that have to be used in a Storybook context.
*
* @author Nicolas Rigaud <[email protected]>
*
* @internal
*/
final class StorybookRuntimeLoader implements RuntimeLoaderInterface
{
/**
* @var array<string, object>
*/
private array $map = [];

public function load(string $class): ?object
{
return $this->map[$class] ?? null;
}

public function addRuntime(object $runtime): void
{
$class = $runtime::class;
if (isset($this->map[$class])) {
throw new InvalidArgumentException(sprintf('Runtime "%s" is already registered.', $class));
}

$this->map[$class] = $runtime;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Storybook\Tests\Unit\DependencyInjection\Compiler;

use PHPUnit\Framework\TestCase;
use Storybook\DependencyInjection\Compiler\StorybookRuntimeLoaderPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

final class StorybookRuntimeLoaderPassTest extends TestCase
{
public function testProcessAddsStorybookRuntimeLoaderBeforeTheOriginalOnes()
{
$container = new ContainerBuilder();
$twigDefinition = new Definition();
$container->setDefinition('storybook.twig', $twigDefinition);

$twigDefinition->addMethodCall(
'addRuntimeLoader',
[new Reference('native_loader')],
);

$pass = new StorybookRuntimeLoaderPass();
$pass->process($container);

$runtimeLoaderCalls = array_filter(
$twigDefinition->getMethodCalls(),
static fn (array $call) => 'addRuntimeLoader' === $call[0],
);

self::assertCount(2, $runtimeLoaderCalls);
self::assertEquals(
new Reference('storybook.twig.runtime_loader'),
$runtimeLoaderCalls[0][1][0],
);
}
}
37 changes: 37 additions & 0 deletions tests/Unit/Twig/StorybookRuntimeLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Storybook\Tests\Unit\Twig;

use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Storybook\Twig\StorybookRuntimeLoader;

final class StorybookRuntimeLoaderTest extends TestCase
{
public function testAddRuntime()
{
$runtimeLoader = new StorybookRuntimeLoader();

$runtime = new DummyRuntime();
$runtimeLoader->addRuntime($runtime);

self::assertSame($runtime, $runtimeLoader->load(DummyRuntime::class));
}

public function testAddingTheSameRuntimeMultipleTimesThrowsException()
{
$runtimeLoader = new StorybookRuntimeLoader();

$runtime = new DummyRuntime();
$runtimeLoader->addRuntime($runtime);

$sameRuntime = new DummyRuntime();

$this->expectException(InvalidArgumentException::class);
$runtimeLoader->addRuntime($sameRuntime);
}
}

final class DummyRuntime
{
}

0 comments on commit b83af48

Please sign in to comment.