npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@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

  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 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 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 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.