Fractal is a tool to generate component libraries or styleguides. We want to integrate it into our workflow of handling frontend code in Symfony apps, which can be handled by Symfony's extension of Webpack, Encore Let's get started by creating a project and installing Encore.

composer create-project symfony/website-skeleton symfony-demo-fractal-encore
cd symfony-demo-fractal-encore
git init
yarn install

With the last command, we already installed the packages that Encore added for our frontend tooling. Symfony's Webpack configuration is using an app.js and app.css file inside an assets/ subfolder.

Setup Fractal

We continue with adding Fractal. Fractal uses handlebars templating by default. As Symfony's template language is Twig, I am replacing Fractal's default handlebars templating with a Twig clone written in Javascript.

yarn add --dev @frctl/fractal @frctl/twig fractal-webpack-plugin

Create a fractal.config.js with the recommended settings and set the static.path to our public directory.

'use strict';

/* Create a new Fractal instance and export it for use elsewhere if required */
const fractal = module.exports = require('@frctl/fractal').create();

/* Set the title of the project */
fractal.set('project.title', 'FooCorp Component Library');

/* Tell Fractal where the components will live */
fractal.components.set('path', __dirname + '/src/components');

/* Tell Fractal where the documentation pages will live */
fractal.docs.set('path', __dirname + '/src/docs');

// Tell the Fractal web preview plugin where to look for static assets.
fractal.web.set('static.path', __dirname + '/public');

By using public as static path, the files generated by Webpack Encore will be available with the same path, because Webpack Encore defaults to public/build as output path.

We now need to add the Twig adapter and introduce a customized asset function to load the correct assets from our manifest.json.

// Use twig adapter
const utils = require('@frctl/fractal').utils;
const twigAdapter = require('@frctl/twig')({
  importContext: true,
  functions: {
    asset: function(path) {
      path = utils.relUrlPath(path, '/', fractal.web.get('builder.urls'));
      let env = this.context._env;
      if (!env || env.server) {
        const manifest = require(fractal.web.get('static.path') + '/build/manifest.json');
        return manifest[manifestPath];
      }

      const manifest = require(fractal.web.get('builder.dest') + '/build/manifest.json');
      return manifest[manifestPath];
    }
  }
});

fractal.components.engine(twigAdapter);
fractal.components.set('ext', '.html.twig');

We add a function that helps the Adapter discover our assets generated with Encore versioning in the manifest.json file. It will differentiate between server (used in dev) and builder. To make this work, we must specify a static.path and builder.dest respectively. We already added static.path, so only the builder.dest remains.

This is the path where Fractal will be built into as static html in production. Sadly, it can not be a parent of static.path, as that is copied into it.

// Tell Fractal where to build
fractal.web.set('builder.dest', __dirname + '/styleguide');

Fractal will not include our assets correctly by default. We can tell it to load our stylesheet and scripts, but this will not work with versioned files. We will copy templates/base.html.twig into our components and update it to make it work with Fractal.

cp templates/base.html.twig src/components/_preview.html.twig

note the underscore: this will hide it from the component library

We don't want to specify a block in all our components, which is usually required by Twig in sub-templates. Fractal allows specifying a preview template and provides a yield variable with the components' content, which we can use instead.

We also add links to the assets.

// src/components/_preview.html.twig
\{% block stylesheets %}
+    <link rel="stylesheet" type="text/css" href="\{{ asset('/build/app.css') }}" />
\{% endblock %}
…
- \{% block body %}{% endblock %}
+ \{% block body %}{{ yield | raw }}{% endblock %}
\{% block javascripts %}
+    <script src="\{{ asset('/build/runtime.js') }}"></script>
+    <script src="\{{ asset('/build/app.js') }}"></script>
{% endblock %}

Finally, set this file as custom preview in fractal.config.js.

+ // use a custom preview
+ fractal.components.set('default.preview', __dirname + '/src/components/_preview.html.twig');

Notice how we put Fractal's yield output in a block? If we generate templates for pages, we have to be able to extend again. We will probably extend from base.html.twig, so copy this:

cp templates/base.html.twig src/components/base.html.twig

Fractal will render this into our preview template, so make sure the preview plays nicely with our base template. If you don't want the base template to appear as a component in Fractal, add a base.config.yml file and hide it.

Configure Webpack Encore

We now need to tweak our existing Webpack configuration to include Fractal. Open webpack.config.js and add the Fractal plugin.

var Encore = require('@symfony/webpack-encore');
+const FractalWebpackPlugin = require('fractal-webpack-plugin');
…
Encore
…
+    .addPlugin(
+        new FractalWebpackPlugin({
+        mode: Encore.isProduction() ? 'build' : 'server',
+        configPath: './fractal.config.js',
+        })
    );
;

module.exports = Encore.getWebpackConfig();

Create a first component

Following the example from Fractal's guide, we create an alert example.

/* src/components/alert.css */
.alert {
    background: red;
    color: white;
    padding: 1rem;
}


<!-- src/components/alert.html.twig -->
<div class="alert">This is an alert!</div>

/* src/components/alert.js */
console.log('ALERT');

To include it in the CSS generated by Webpack Encore, we must import it in assets/app.js.

import '../css/app.css';
+ import '../../src/components/alert.css'
+ import '../../src/components/alert.js';

I'd recommend to create a main file for all components in src/components and include this from app.js, but that is up to you.

We can now fire up a Fractal dev server by running Encore in dev mode.

yarn dev

Integration?

Fractal gives us a nice overview of our components. We can develop them in the languages that are used eventually. Webpack handles the frontend code perfectly, we only need to find a way to get our components into our templates/ directory.

I'll write about one approach in my next post.