Newborn Programming Monsters
Miss any of our Open RFC calls?Watch the recordings here! »

storypug

1.0.0-rc.8 • Public • Published

Storypug

Storypug makes it easy and more straightforward to use pug mixins as components inside Storybook (and, incidentally, Jest).

In a nutshell Storypug let's you import pug mixins as functions and render them to HTML with options.

Installation

First of all setup Storybook for HTML following this guide.

Then you need to install both pug and pug-runtime alongside storypug:

npm i pug pug-runtime storypug -D

Note: If you're using ES6+ features inside pug templates (like const and () => {}) and want to target older browsers you need to install babel-loader as well.

npm i babel-loader -D

Code Requirements

In order for Storypug to work correctly you are required to define exactly one mixin per file.

Storybook configuration

As a preset

Storybook 5.3+

Add the following to .storybook/main.js:

module.exports = {
  addons: ['storypug'],
};

You can customize the preset with the following options:

name type default description
include string[] Include rule for /\.pug?\$/
babel boolean false Transpile the pug template with babel
babelLoaderOptions object (Optional) babel-loader custom options
loaderOptions object (Optional) Storypug loader options

Example:

module.exports = {
  addons: [
    {
      name: 'storypug',
      options: {
        babel: true, //use babel-loader
        loaderOptions: {
          root: 'src/components', // use src components as the pug root inclusion path
        },
      },
    },
  ],
};

Storybook <=5.2

Add the following to .storybook/preset.js:

module.exports = ['storypug/preset'];

Or with options:

module.exports = [
  {
    name: 'storypug/preset',
    options: {
      babel: true, //use babel-loader
      loaderOptions: {
        root: 'src/components', // use src components as the pug root inclusion path
      },
    },
  },
];

Manual Setup

  1. Create a .storybook/webpack.config.js file in your project's root folder.
  2. Add the module loader for .pug files:
module.exports = async ({ config }) => {
  config.module.rules.push({
    test: /\.pug$/,
    use: ['storypug/lib/webpack-loader.js'],
  });
  return config;
};
  1. If you're using ES6+ features and you target old browsers add babel-loader before the Storypug loader.
module.exports = async ({ config }) => {
  config.module.rules.push({
    test: /\.pug$/,
-   use: ['storypug/lib/webpack-loader.js'],
+   use: ['babel-loader', 'storypug/lib/webpack-loader.js'],
  });
  return config;
};

Loader Options

Storypug's webpack loader is a wrapper around pug-loader, and thus any option set in the configuration will be forwarded to it:

module.exports = async ({ config }) => {
  config.module.rules.push({
    test: /\.pug\$/,
    use: [
      {
        loader: 'storypug/lib/webpack-loader.js',
        options: {
          root: '/path/to/base-dir',
          // ^-- root directory used for absolute includes
        },
      },
    ],
  });
  return config;
};

Usage in Stories

Note: This documentation uses Storybook's Component Story Format, but the storiesOf API is supported as well.

Now that you have configured Storybook to handle .pug files, you can import them like JavaScript modules. The imported module will be a function accepting an object with the following properties:

  • props: An object passed as the first argument of the mixin
  • contents: an optional HTML string rendered at the mixin's block
  • ...: any other property will be avaiable as pug locals.

The function will return the rendered template as a string.

//- components/example.pug
 
mixin Example (props = {})
  - const intro = startCase(props.intro)
  .example
    p.example__intro= props.intro
    .example__body
      block
// components/example.stories.js
 
import startCase from 'lodash/startCase';
import Example from './example.pug';
 
export default {
  title: 'Example',
};
 
export const Basic = () => {
  // setup properties
  const props = { intro: 'This is an intro' };
  // this HTML will be rendered inside the mixin's block
  const contents = '<p>Example body</p>';
 
  return Example({ props, contents, startCase });
};

The output of the default story will be:

<div class="example">
  <p class="example__intro">This Is An Intro</p>
  <div class="example__body"><p>Example body</p></div>
</div>

Render Helpers

To ease the developer experience, and provide some useful defaults, Storypug provides a handy render helpers.

// components/example.stories.js
 
import startCase from 'lodash/startCase';
+ import { renderer } from 'storypug';
import Example from './example.pug';
 
+ // pass here shared locals like functions and variables
+ const { html } = renderer({ startCase });
 
export default {
  title: 'Example',
};
 
export const Basic = () => {
  // setup properties
  const props = { intro: 'This is an intro' };
  // this HTML will be rendered inside the mixin's block
  const contents = '<p>Example body</p>';
 
- return Example({ props, contents, startCase });
+ return html(Example, props, contents);
};

Render to a DOM Element

Storybook HTML accepts both strings and DOM elements. To render the template to a DOM element use the render helper instead of html:

// components/example.stories.js
 
import startCase from 'lodash/startCase';
import { renderer } from 'storypug';
import Example from './example.pug';
 
  // pass here shared locals like functions and variables
- const { html } = renderer({ startCase });
+ const { render } = renderer({ startCase });
 
export default {
  title: 'Example',
};
 
export const Basic = () => {
  // setup properties
  const props = { intro: 'This is an intro' };
  // this HTML will be rendered inside the mixin's block
  const contents = '<p>Example body</p>';
 
- return html(Example, props, contents);
+ const wrapper = render(Example, props, contents);
+ return wrapper.$root;
};

The wrapper object returned by render has the following properties:

  • $root: a reference to the rendered mixin root element
  • $raw: the rendered mixin HTML as a string
  • el: the wrapper DOM element itself
  • html(): function returning the rendered mixin HTML as a string
  • outer(): function returning the wrapper HTML as a string
  • find(selector): a shortcut to el.querySelector(selector)
  • findAll(selector): a shortcut to el.querySelectorAll(selector)

Note that $raw differs from html() in that the former is a reference to the HTML generated at render time while the latter will reflect any manipulation applied after rendering.

Usage with @storybook/addon-knobs

Another benefit of Storypug is the ability to use addons like @storybook/addon-knobs with ease.

//- components/checkbox.pug
 
mixin Checkbox(props={})
  input.checkbox(type="checkbox" value=props.value checked=!!props.checked)
// components/checkbox.stories.js
 
import { boolean } from '@storybook/addon-knobs';
import { renderer } from 'storypug';
import Checkbox from './checkbox.pug';
 
const { html } = renderer();
const defaultProps = { value: 'on' };
 
export default {
  title: 'Checkbox',
};
 
export const Basic = () => {
  const checked = boolean('Checked', false);
 
  return html(Checkbox, { ...defaultProps, checked });
};

Decorators

Storypug provides some useful decorators as well:

withStyle

This decorator will wrap the rendered HTML in a DOM element with custom styles. The decorator can be configured by setting the style parameter:

// ...
import { withStyle } from 'storypug';
 
const globalStyle = {
  backgroundColor: 'black',
};
 
const lightStyle = {
  backgroundColor: 'white',
};
 
export default {
  title: 'Checkbox',
  decorators: [withStyle],
  parameters: {
    style: globalStyle,
  },
};
 
export const Basic = () => html(Checkbox);
 
export const Light = () => html(Checkbox);
 
Light.story = {
  parameters: [{ style: lightStyle }],
};

In the above example the default story will wrap the checkbox in a container with black background, while the light story will have it white.

To skip the decorator in a story set the style parameter to false.

Note: the style parameter accepts style objects, class names or an array of those. This makes it easy to use packages like emotion for styling:

// ...
import { withStyle } from 'storypug';
import { css } from 'emotion';
 
// lightStyle is a unique class name
const lightStyle = css`
  background-color: white;
`;
 
const redText = {
  color: 'red',
};
 
export default {
  title: 'Checkbox',
  decorators: [withStyle],
};
 
// ...
 
export const LightAndRed = () => html(Checkbox);
 
LightAndRed.story = {
  parameters: {
    style: [lightStyle, redText],
  },
};

fullscreen

Works like withStyle but will add a fullscreen class name by default to the wrapper. This decorator is useful if you need to reset any default spacing added to the storybook's preview panel. Like withStyle accepts additional classes and styles via the fullscreen parameter.

If you want to rename the default fullscreen class name to something else you can do so by setting the fullscreen.baseClass property:

import { fullscreen } from 'storypug';
 
fullscreen.baseClass = 'custom-fullscreen';
 
export default {
  title: 'Story name',
  decorators: [fullscreen],
  parameters: { fullscreen: 'additional-class' },
};

To skip the decorator in a story set the fullscreen parameter to false.

withWrap

Works like withStyle but lets you define the tag name of the wrapper element (defaults to div):

// wrap the story with <section class="my-class" />
 
// ...
export default {
  title: 'Story name',
  decorators: [withWrap],
  parameters: { wrap: { style: 'my-class', tag: 'section' } },
};

To skip the decorator in a story set the wrap parameter to false.

Usage with Jest

Storypug lets you use the same patterns you're using in stories to render pug templates inside Jest.

This feature can be useful paired with snapshot testing and DOM testing

Configuration

Add a transform for pug files in your jest.config.js file:

module.exports = {
  // ...
  transform: {
+    '\\.pug$': 'storypug/lib/pug-jest.js',
  },
};

You can customize the pug settings by using jest.globals:

module.exports = {
  // ...
+ globals: {
+   'pug-jest': {
+     basedir: '<rootDir>',
+   },
+ },
  transform: {
    '\\.pug$': 'storypug/lib/pug-jest.js',
  },
};

Writing tests

Once configured, you can import your templates and use the render helpers as described in the Storybook section.

Install

npm i storypug

DownloadsWeekly Downloads

94

Version

1.0.0-rc.8

License

MIT

Unpacked Size

35.9 kB

Total Files

13

Last publish

Collaborators

  • avatar