Skip to content
Open
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
3 changes: 3 additions & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
])
->methods(['POST'])
->controller('storybook.controller.render_story')
->add('storybook_preview', '/_storybook/preview')
->methods(['GET'])
->controller('storybook.controller.preview')
;
};
19 changes: 12 additions & 7 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,17 @@ const config: StorybookConfig = {

/**
* Symfony framework options.
*
* These options only applies to the dev environment.
*/
symfony: {
symfony: {
/**
* Mandatory, the absolute path of the Storybook cache path.
*
* @var string
*/
storybookCachePath: path.resolve(__dirname, '../var/cache/dev/storybook'),

/**
* Mandatory, the URL of the Symfony development server.
* Mandatory, the URL of the Symfony application server.
*
* @var string
*/
Expand All @@ -287,9 +292,9 @@ const config: StorybookConfig = {
* @var string[]
*/
additionalWatchPaths: [
'assets',
'var/tailwind/tailwind.built.css'
]
'/assets',
'/var/tailwind/tailwind.built.css'
],
}
},
},
Expand Down
3 changes: 3 additions & 0 deletions docs/static-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Storybook can be built in a static application, to be deployed on a simple web server. The build will contain all stories metadata and the JavaScript used to render the Storybook UI.

> ⚠️ In order to build Storybook, you need to have a running Symfony server.
> The Symfony server is used to generate "preview.html.twig" iframe.

## Build Storybook

To build Storybook, use:
Expand Down
24 changes: 12 additions & 12 deletions sandbox/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dotenv/config';
import type { StorybookConfig } from "@sensiolabs/storybook-symfony-webpack5";

const config: StorybookConfig = {
Expand Down Expand Up @@ -57,18 +58,17 @@ const config: StorybookConfig = {
options: {
// 👇 Here configure the framework
symfony:
process.env.NODE_ENV === 'development'
? {
server: 'http://localhost:8000',
proxyPaths: [
'/assets',
'/_components',
],
additionalWatchPaths: [
'assets',
]
}
: {}
{
storybookCachePath: `var/cache/${process.env.APP_ENV}/storybook`,
server: 'http://localhost:8000',
proxyPaths: [
'/assets',
'/_components',
],
additionalWatchPaths: [
'/assets',
]
}
},
},
previewAnnotations: ['./templates/components/Storybook', './template-stories/lib/preview-api/preview.ts', './template-stories/addons/toolbars/preview.ts'],
Expand Down
1 change: 1 addition & 0 deletions sandbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@storybook/global": "5.0.0",
"@storybook/test": "^8.1.3",
"@storybook/test-runner": "^0.18.2",
"dotenv": "^16.4.7",
"esbuild-loader": "^4.1.0",
"typescript": "^5.4.2",
"wait-on": "^7.2.0",
Expand Down
90 changes: 90 additions & 0 deletions src/CacheWarmer/StorybookCacheWarmer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Storybook\CacheWarmer;

use Symfony\Component\Config\ConfigCacheFactory;
use Symfony\Component\Config\ConfigCacheFactoryInterface;
use Symfony\Component\Config\ConfigCacheInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;

class StorybookCacheWarmer implements CacheWarmerInterface
{
private ConfigCacheFactory $configCacheFactory;

public function __construct(
private readonly ?string $cacheDir,
private readonly bool $debug,
private readonly string $projectDir,
private readonly array $twigConfig,
private readonly array $twigComponentConfig,
) {
}

public function isOptional(): bool
{
return true;
}

public function warmUp(string $cacheDir, ?string $buildDir = null): array
{
$this->getConfigCacheFactory()->cache(
$this->cacheDir.'/symfony_parameters.json',
function (ConfigCacheInterface $cache) {
$this->generateSymfonyParameters($cache);
}
);

return [];
}

/**
* Provides the ConfigCache factory implementation, falling back to a
* default implementation if necessary.
*/
private function getConfigCacheFactory(): ConfigCacheFactoryInterface
{
$this->configCacheFactory ??= new ConfigCacheFactory($this->debug);

return $this->configCacheFactory;
}

private function generateSymfonyParameters(ConfigCacheInterface $cache): void
{
$parameters = [
'twig_config' => [
'default_path' => $this->twigConfig['default_path'],
'paths' => $this->twigConfig['paths'],
],
'twig_component_config' => [
'anonymous_template_directory' => $this->twigComponentConfig['anonymous_template_directory'],
'defaults' => $this->twigComponentConfig['defaults'],
],
];

$cache->write(json_encode($this->stripProjectDirectory($parameters), JSON_PRETTY_PRINT));
}

private function stripProjectDirectory(array $array): array
{
$sanitizedArray = [];
foreach ($array as $key => $value) {
if (is_string($value) && str_starts_with($value, $this->projectDir)) {
$value = str_replace($this->projectDir, '', $value);
}

if (is_string($key) && str_starts_with($key, $this->projectDir)) {
$key = str_replace($this->projectDir, '', $key);
$sanitizedArray[$key] = $value;
} elseif (is_array($value)) {
$sanitizedArray[$key] = $this->stripProjectDirectory($value);
} else {
$sanitizedArray[$key] = $value;
}

}

return $sanitizedArray;
}
}
38 changes: 0 additions & 38 deletions src/Command/GeneratePreviewCommand.php

This file was deleted.

5 changes: 3 additions & 2 deletions src/Command/StorybookInitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ private function setupStorybookConfig(): void
options: {
// 👇 Here configure the framework
symfony: {
storybookCachePath: path.resolve(__dirname, `../var/cache/\${process.env.APP_ENV}/storybook`),
server: 'https://localhost',
proxyPaths: [
'/assets',
Expand All @@ -160,12 +161,12 @@ private function setupStorybookConfig(): void
$mainFile .= <<<TS
],
additionalWatchPaths: [
'assets',
'/assets',

TS;
if ($this->isTailwindInstalled()) {
$mainFile .= <<<TS
'var/tailwind/tailwind.built.css',
'/var/tailwind/tailwind.built.css',

TS;
}
Expand Down
26 changes: 26 additions & 0 deletions src/Controller/StorybookPreviewController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Storybook\Controller;

use Storybook\Event\GeneratePreviewEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;

final class StorybookPreviewController
{
public function __construct(
private readonly Environment $twig,
private readonly EventDispatcherInterface $eventDispatcher
) {
}

public function __invoke(): Response
{
$this->eventDispatcher->dispatch(new GeneratePreviewEvent());

$content = $this->twig->render('@Storybook/preview.html.twig');

return new Response($content);
}
Comment on lines +18 to +25
Copy link
Author

@jon-ht jon-ht Feb 15, 2025

Choose a reason for hiding this comment

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

Same process as storybook:generate-preview. This command should be removed before merging

}
50 changes: 50 additions & 0 deletions src/DependencyInjection/Compiler/CacheWarmerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Storybook\DependencyInjection\Compiler;

use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;

class CacheWarmerPass implements CompilerPassInterface
{
private ?array $extensionConfig = null;

public function process(ContainerBuilder $container): void
{
$cacheWarmerDefinition = $container->getDefinition('storybook.cache_warmer');

$cacheWarmerDefinition
->setArgument(3, $this->getConfig($container, $container->getExtension('twig')))
->setArgument(4, $this->getConfig($container, $container->getExtension('twig_component')))
;
}

private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container)
{
$extensionAlias = $extension->getAlias();

if (isset($this->extensionConfig[$extensionAlias])) {
return $this->extensionConfig[$extensionAlias];
}

$configs = $container->getExtensionConfig($extensionAlias);

$configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($configs, $container);

return $this->extensionConfig[$extensionAlias] = (new Processor())->processConfiguration($configuration, $configs);
}

private function getConfig(ContainerBuilder $container, ExtensionInterface $extension): array
{
return $container->resolveEnvPlaceholders(
$container->getParameterBag()->resolveValue(
$this->getConfigForExtension($extension, $container)
), true
);
}
Comment on lines +27 to +49
Copy link
Author

@jon-ht jon-ht Feb 8, 2025

Choose a reason for hiding this comment

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

This process is based on debug:config command.

I'm not really sure about the implementation. It gives me expected output, but there could be other ways to achieve this. Let me know if it could be improved

}
26 changes: 18 additions & 8 deletions src/DependencyInjection/StorybookExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
use Storybook\ArgsProcessor\StorybookArgsProcessor;
use Storybook\Attributes\AsArgsProcessor;
use Storybook\Attributes\AsComponentMock;
use Storybook\Command\GeneratePreviewCommand;
use Storybook\CacheWarmer\StorybookCacheWarmer;
use Storybook\Command\StorybookInitCommand;
use Storybook\Controller\StorybookController;
use Storybook\Controller\StorybookPreviewController;
use Storybook\DependencyInjection\Compiler\CacheWarmerPass;
use Storybook\DependencyInjection\Compiler\ComponentMockPass;
use Storybook\EventListener\ProxyRequestListener;
use Storybook\Exception\UnauthorizedStoryException;
Expand Down Expand Up @@ -81,6 +83,13 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance
->addTag('controller.service_arguments')
;

// Preview controller
$container->register('storybook.controller.preview', StorybookPreviewController::class)
->setArgument(0, new Reference('twig'))
->setArgument(1, new Reference('event_dispatcher'))
->addTag('controller.service_arguments')
;

// Story renderer
$defaultSandboxConfig = [
'allowedTags' => ['component'],
Expand Down Expand Up @@ -142,13 +151,6 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance
$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'))
Expand All @@ -159,6 +161,14 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance
->setArgument(0, new Reference('request_stack'))
->setArgument(1, new Reference('event_dispatcher'))
->addTag('kernel.event_subscriber');

$container->register('storybook.cache_warmer', StorybookCacheWarmer::class)
->setArgument(0, $container->getParameter('kernel.cache_dir').'/storybook')
->setArgument(1, $container->getParameter('kernel.debug'))
->setArgument(2, $container->getParameter('kernel.project_dir'))
->setArgument(3, new AbstractArgument(\sprintf('Provided in "%s".', CacheWarmerPass::class)))
->setArgument(4, new AbstractArgument(\sprintf('Provided in "%s".', CacheWarmerPass::class)))
->addTag('kernel.cache_warmer');
}

public function getConfigTreeBuilder(): TreeBuilder
Expand Down
Loading