Rendering Twig templates in Storybook

Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It’s open source and free.

Historically, it requires a frontend framework like React, Vue o…


This content originally appeared on DEV Community and was authored by Jérôme TAMARELLE

Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It’s open source and free.

Historically, it requires a frontend framework like React, Vue or Angular to render components. But I build websites with Twig, so I needed a way to render the templates in my backend application. The package @storybook/server is a solution for this use-case. It was released in March 2022 and is exactly what its name suggests: a server-side rendering solution for Storybook.

There are very few things to do to connect Storybook to a Symfony application, this is a step-by-step guide to get you started. I'm assuming that you already have a Symfony application with Twig installed, and that you're using webpack encore. But that's not a requirement, you can use any other frontend tool you like.

Creating the first component

In the websites I build, a components is a single Twig file that can be included in any other template of the project. This can also be a Twig Component if you are using Symfony UX. For this example, we'll create a simple button component, in a templates/components folder. This button can have a label, a size and can be primary or secondary.

{# templates/components/button.html.twig #}

<button
    type="button"
    class="{{ size }} {{ primary|default(false) ? 'primary' : 'secondary' }}"
    style="{{ style|default('')|escape('html_attr') }}"
>
    {{- label -}}
</button>

Our goal is to provide a preview of this component in storybook, with the possibility to change its properties.

Creating the story

In StorybookJS, stories are individual states of components that can be displayed in isolation for preview and testing. They are typically written in JavaScript when rendering is done on the client-side with a javascript function. For server-side rendering, stories can be written in JSON. An additional key parameters.server.id is required to specify the path to the preview.

// stories/button.json
{
  "title": "Components/Button",
  "parameters": {
    "server": { "id": "button" }
  },
  "args": { "label": "Button" },
  "argTypes": {
    "label": { "control": "text" },
    "primary": { "control": "boolean" }, 
    "backgroundColor": { "control": "color" },
    "size": {
      "control": { "type": "select", "options": ["small", "medium", "large"] }
    }
  },
  "stories": [
    {
      "name": "Primary",
      "args": { "primary": true }
    },
    {
      "name": "Secondary"
    },
    {
      "name": "Large",
      "args": { "size": "large" }
    }
  ]
}

Installing Storybook server

The @storybook/server documentation recommends using of npx to initialize the project with the server type:

npx sb init -t server

Symfony Webpack Encore uses Webpack5 while Storybook is uses Webpack4 by default, so we need to install the @storybook/builder-webpack5 package:

npm install @storybook/builder-webpack5

This will install npm packages and create a .storybook folder with main.js and preview.js.

The file .storybook/main.js defines the location of the stories and the addons to be used. We need to add the @storybook/server framework and the @storybook/builder-webpack5 builder because webpack encore uses webpack5.

// .storybook/main.js
module.exports = {
    "stories": [
        "stories/**/*.stories.mdx",
        "stories/**/*.stories.@(json)"
    ],
    "addons": [/* optional addons */],
    "framework": "@storybook/server",
    "core": {
        // Webpack Encore uses webpack5
        "builder": "@storybook/builder-webpack5"
    }
}

Last configuration step, we need to configure the URL where the application is served (by default symfony-cli serves on port 8000). The url is arbitrary, it can be anything you want, but it must match the route defined in the Symfony application. Storybook server will concatenate the url with the parameters.server.id value to get the full url to the component. It must be a valid absolute url.

// .storybook/preview.js
export const parameters = {
  server: {
    url: `https://localhost:8000/storybook/component`,
  },
};

Creating a generic Symfony controller

Back to the Symfony application, we need to create a controller behind the url defined in the Storybook configuration. It gets the id from the url and the args from the query string.

<?php # src/Controller/StorybookController.php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Annotation\Route;
use Twig\Environment;

#[AsController]
readonly class StorybookController
{
    public function __construct(private Environment $twig) {}

    #[Route(
        '/storybook/component/{id}',
        // The id can contain slashes, so we need to use a regex
        requirements: ['id' => '.+'],
    )]
    public function __invoke(Request $request, string $id): Response
    {
        // $id is the path to the Twig template in the storybook/ directory
        // Args are read from the query parameters and sent to the template
        $template = sprintf('storybook/%s.html.twig', $id);
        $context = ['args' => $request->query->all(), 'id' => $id];
        $content = $this->twig->render($template, $context);

        // During development, storybook is served from a different port than the Symfony app
        // You can use nelmio/cors-bundle to set the Access-Control-Allow-Origin header correctly
        $headers = ['Access-Control-Allow-Origin' => 'http://localhost:6006'];

        return new Response($content, Response::HTTP_OK, $headers);
    }
}

Creating a Twig template to render the component

We have created our component in templates/components/button.html.twig and we configured the stories in stories/button.json. Now we need to create a Twig template that will render the component in the context of Storybook. This template will be used by the controller we created in the previous step.

{# templates/storybook/button.html.twig #}

{{ include('components/button.html.twig', {
    label: args.label|default('Button'),
    size: args.size|default('medium'),
    primary: args.primary|default(false) == 'true',
    style: args.backgroundColor is defined ? 'background-color: #{args.backgroundColor}' : '',
}) }}

Running Storybook

Finally, we can run Storybook and see our component in action.

npm run storybook

> storybook
> start-storybook -p 6006
...
╭───────────────────────────────────────────────────╮
│                                                   │
│   Storybook 6.5 for Server started                │
│   4.56 s for preview                              │
│                                                   │
│    Local:            http://localhost:6006/       │
│    On your network:  http://192.168.1.11:6006/    │
│                                                   │
╰───────────────────────────────────────────────────╯

Time to play with the component and see how it behaves in different states.

Conclusion

This was my first experience with Storybook, and I'm very satisfied with the result. I hope this article helps you to get started and if you need to convince your frontend team that you don't need to switch to Node to use Storybook. Symfony is an amazing framework for building rich web applications and the need for a frontend framework to build the rich UI is being challenged by the Symfony UX initiative.

If you have any questions or feedback, feel free to comment below or reach out to me on Twitter.


This content originally appeared on DEV Community and was authored by Jérôme TAMARELLE


Print Share Comment Cite Upload Translate Updates
APA

Jérôme TAMARELLE | Sciencx (2023-03-11T21:54:59+00:00) Rendering Twig templates in Storybook. Retrieved from https://www.scien.cx/2023/03/11/rendering-twig-templates-in-storybook/

MLA
" » Rendering Twig templates in Storybook." Jérôme TAMARELLE | Sciencx - Saturday March 11, 2023, https://www.scien.cx/2023/03/11/rendering-twig-templates-in-storybook/
HARVARD
Jérôme TAMARELLE | Sciencx Saturday March 11, 2023 » Rendering Twig templates in Storybook., viewed ,<https://www.scien.cx/2023/03/11/rendering-twig-templates-in-storybook/>
VANCOUVER
Jérôme TAMARELLE | Sciencx - » Rendering Twig templates in Storybook. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/03/11/rendering-twig-templates-in-storybook/
CHICAGO
" » Rendering Twig templates in Storybook." Jérôme TAMARELLE | Sciencx - Accessed . https://www.scien.cx/2023/03/11/rendering-twig-templates-in-storybook/
IEEE
" » Rendering Twig templates in Storybook." Jérôme TAMARELLE | Sciencx [Online]. Available: https://www.scien.cx/2023/03/11/rendering-twig-templates-in-storybook/. [Accessed: ]
rf:citation
» Rendering Twig templates in Storybook | Jérôme TAMARELLE | Sciencx | https://www.scien.cx/2023/03/11/rendering-twig-templates-in-storybook/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.