@pendalf/storypug
v1.0.0-rc.9
Published
A set of utilities to ease using pug templates in Storybook
Downloads
1
Readme
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 @pendalf/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 need 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
- Create a
.storybook/webpack.config.js
file in your project's root folder. - 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;
};
- 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 mixincontents
: an optional HTML string rendered at the mixin'sblock
...
: any other property will be available 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, Storypug provides a handy render helper.
// 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 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.
Usage with @storybook/addon-knobs
or storyArgs
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 });
};
The same story can be written using Storybook 6+ args:
// components/checkbox.stories.js
import { renderer } from 'storypug';
import Checkbox from './checkbox.pug';
const { html } = renderer();
const defaultProps = { value: 'on' };
export default {
title: 'Checkbox',
argTypes: {
checked: {
type: 'boolean',
name: 'Checked',
control: 'boolean'
}
}
};
export const Basic = ({ 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.