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
:
moduleexports = 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:
moduleexports = 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
:
moduleexports = 'storypug/preset';
Or with options:
moduleexports = name: 'storypug/preset' options: babel: true //use babel-loader loaderOptions: root: 'src/components' // use src components as the pug root inclusion path ;
Manual Setup
- Create a
.storybook/webpack.config.js
file in your project's root folder. - Add the module loader for
.pug
files:
moduleexports = async { configmodulerules; return config;};
- 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:
moduleexports = async { configmodulerules; 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 mixincontents
: an optional HTML string rendered at the mixin'sblock
...
: 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 ;; title: 'Example'; 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 ;};
The output of the default
story will be:
This Is An Intro Example body
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 stringel
: the wrapper DOM element itselfhtml()
: function returning the rendered mixin HTML as a stringouter()
: function returning the wrapper HTML as a stringfind(selector)
: a shortcut toel.querySelector(selector)
findAll(selector)
: a shortcut toel.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.
@storybook/addon-knobs
Usage with 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 ;;; const html = ;const defaultProps = value: 'on' ; title: 'Checkbox'; const Basic = { const checked = ; return ;};
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:
// ...; const globalStyle = backgroundColor: 'black'; const lightStyle = backgroundColor: 'white'; title: 'Checkbox' decorators: withStyle parameters: style: globalStyle ; const Basic = ; const Light = ; Lightstory = 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:
// ...;; // lightStyle is a unique class nameconst lightStyle = css` background-color: white;`; const redText = color: 'red'; title: 'Checkbox' decorators: withStyle; // ... const LightAndRed = ; LightAndRedstory = 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:
; fullscreenbaseClass = 'custom-fullscreen'; 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" /> // ... 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.