React Styleguidist

Developer guide

For basics see How to contribute.

Styleguidist isn’t an ordinary single page app and some design decisions may look confusing to an outsider. In this guide we’ll explain these decisions to un-confuse potential contributors.

The main thing is that we’re running two apps at the same time: user’s components and Styleguidist UI. They share a webpack configuration and have styles in the same scope (there’s only one scope in CSS). And we can control only one of these two apps: Styleguidist UI. That puts us under some restrictions:

  • Our styles should not affect user component styles.
  • User styles (especially global like Bootstrap) should not affect Styleguidist UI.
  • body styles (like font-family) should affect user components as the user expects but not Styleguidist UI.

How it works

Styleguidist uses react-docgen to parse source files (not transpiled). react-docgen finds exported React components and generates documentation based on PropTypes or Flow annotations.

Styleguidist uses Markdown for documentation: each JavaScript code block is rendered as an interactive playground with CodeMirror. To do that we extract all these code blocks using Remark.

Webpack loaders (see below) generate JavaScript modules with all user components, their documentation and examples and pass that to a React app which renders a style guide.

Webpack loaders and webpack configuration

We use webpack loaders to hot reload the style guide on changes in user components, styles and Markdown documentation. We have three loaders (loaders folder):

  • styleguide-loader: loads components and sections;
  • props-loaders: loads props documentation using react-docgen;
  • examples-loader: loads examples from Markdown files;

There are two more loaders —css-loader and styles-loader but they are just one-line aliases to corresponding webpack loaders. We don’t want to rely on webpack loader resolver because its behavior can be changed by user’s webpack config (Create React App does that for example). This way we can bypass webpack resolver and use Node.js resolver instead. These loaders are used like this:

require('!!../../../loaders/style-loader!../../../loaders/css-loader!codemirror/lib/codemirror.css');

!! prefix tells webpack not to use any other loaders that may be listed in a webpack configuration to load this module. This ensures that user’s webpack configuration won’t affect Styleguidist.

Styleguidist tries to load and reuse user’s webpack config (webpack.config.js in project root folder). It works most of the time but has some restrictions: Styleguidist ignores some fields and plugins because they are already included (like webpack.HotModuleReplacementPlugin), don’t make sense for a style guide (like output) or may break Styleguidist (like entry).

We’re trying to keep Styleguidist’s own webpack config minimal to reduce clashes with user’s configuration.

React components

Most of StyleGuidist UI components consist of two parts: Foo/Foo.js that contains all logic and Foo/FooRenderer.js that contains all markup and styles. This allows users to customize rendering by overriding *Renderer component using webpack aliases (or styleguideComponents config option):

// styleguide.config.js
const path = require('path');
module.exports = {
  webpackConfig: {
    resolve: {
      alias: {
        'rsg-components/Wrapper': path.join(__dirname, 'lib/styleguide/Wrapper')
      }
    }
  }
};

All Styleguidist components should be imported like this: import Foo from 'rsg-components/Foo' to make aliases work.

Each component folder usually has several files:

  • Foo/Foo.js (optional for simple components);
  • Foo/FooRenderer.js;
  • Foo/Foo.spec.js — tests;
  • Foo/index.js — reexport of Foo.js or FooRenderer.js.

Styles

For styles we use JSS, it allows users to customize their style guide and allows us to ensure styles isolations (thanks to jss-isolate). No user styles should affect Styleguidist UI and no Styleguidist styles should affect user components.

Use classnames to merge several class names or for conditional class names, import it as cx (import cx from 'classnames').

We use Styled higher-order component to allow theming (see theme and style style guide config options). Use it like this:

import React from 'react';
import Styled from 'rsg-components/Styled';

export const styles = ({ fontFamily, fontSize, color }) => ({
  button: {
    fontSize: fontSize.base,
    fontFamily: fontFamily.base,
    color: color.light,
    '&:hover, &:active': {
      isolate: false,
      color: color.lightest,
    },
  },
});

export function ExamplePlaceholderRenderer({ classes }) {
  return (
    <button className={classes.button}>
      I am a styled button
    </button>
  );
}

Check available theme variables in src/styles/theme.js.

Because of isolation and theming you need to explicitly declare fontFamily, fontSize and color. Add isolate: false to your hover styles, otherwise you’ll have to repeat base non-hover styles.

Testing

We’re using Jest with Enzyme for testing. Put your component tests into Component.spec.js file in the same folder and all other tests into __tests__/filename.spec.js.

To test particular class names use classes function (available in the global namespace in tests):

import { TabButtonRenderer, styles } from './TabButtonRenderer';

const props = {
  classes: classes(styles),
};

it('should render active styles', () => {
  const actual = shallow(<TabButtonRenderer {...props} active>pizza</TabButtonRenderer>);
  expect(actual).toMatchSnapshot();
});