question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

storybooks and styled-components with new config system

See original GitHub issue

There is no documentation how to integrate storybook and styled components on the new config system

previously I had a .storybook/config.js:

import { configure } from "@storybook/react"
import { action } from "@storybook/addon-actions"
import { addDecorator } from '@storybook/react';
import React from 'react';
import GlobalStyle from '../src/components/GlobalStyle';

function withGlobalStyles(storyFn) {
  return (
    <React.Fragment>
      <GlobalStyle />
      {storyFn()}
    </React.Fragment>
  );
}

addDecorator(withGlobalStyles);

// automatically import all files ending in *.stories.js
configure(require.context("../src", true, /\.stories\.js$/), module)
// Gatsby's Link overrides:
// Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here
global.___loader = {
  enqueue: () => {},
  hovering: () => {},
}
// Gatsby internal mocking to prevent unnecessary errors in storybook testing environment
global.__PATH_PREFIX__ = ""
// This is to utilized to override the window.___navigate method Gatsby defines and uses to report what path a Link would be taking us to if it wasn't inside a storybook
window.___navigate = pathname => {
  action("NavigateTo:")(pathname)
}

How do this translates to a .storybook/main.js config?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:20 (2 by maintainers)

github_iconTop GitHub Comments

8reactions
jonniebigodescommented, Jan 16, 2020

@cristianaortiga i’m going to detail the steps i took to triage this and make both frameworks work together, with the added bonus of using Styled Components. The reproduction used is a “mashed redux”(pardon the bad pun) version of the LearnStorybook tutorial and the Gatsby documentation on how to use Styled Components. Here we go.

  • Created a new Gatsby website based on the hello world starter.

  • Installed the necessary packages for Styled components, more specifically gatsby-plugin-styled-components, styled-components babel-plugin-styled-components.

  • Added Storybook to the site, through running npx -p @storybook/cli sb init

  • After the installation process finished i added some extra Storybook packages, more specifically @storybook/addon-a11y, @storybook/addon-viewport and @storybook/addon-knobs.

  • Updated the configuration file(.storybook/main.js) in order for make Storybook look inside the components folder as oposed to the default config, which will introspect all of the src folder. I did this for one key thing, Gatsby treats the pages folder as a special folder. And if you write a story somewhere and run Storybook in development mode for instance, Storybook will fail and you’ll get accosted by some weird error messages, probably something like the following:

ERR! Module not found: Error: Can't resolve '..\..\public\static\d\3962212716.json' in '\test_cristianaortiga\test_storybook_gatsby_5_3\src\components'
(node:12120) UnhandledPromiseRejectionWarning: ModuleNotFoundError: Module not found: Error: Can't resolve '..\..\public\static\d\3962212716.json' in '\test_cristianaortiga\test_storybook_gatsby_5_3\src\components'
    at factory.create (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\@storybook\core\node_modules\webpack\lib\Compilation.js:925:10)
    at factory (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\@storybook\core\node_modules\webpack\lib\NormalModuleFactory.js:401:22)
    at resolver (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\@storybook\core\node_modules\webpack\lib\NormalModuleFactory.js:130:21)
    at asyncLib.parallel (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\@storybook\core\node_modules\webpack\lib\NormalModuleFactory.js:224:22)
    at \test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\neo-async\async.js:2830:7
    at \test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\neo-async\async.js:6877:13
    at normalResolver.resolve (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\@storybook\core\node_modules\webpack\lib\NormalModuleFactory.js:214:25)
    at doResolve (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\enhanced-resolve\lib\Resolver.js:213:14)
    at hook.callAsync (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\enhanced-resolve\lib\Resolver.js:285:5)
    at _fn0 (eval at create (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:15:1)
    at resolver.doResolve (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\enhanced-resolve\lib\UnsafeCachePlugin.js:44:7)
    at hook.callAsync (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\enhanced-resolve\lib\Resolver.js:285:5)
    at _fn0 (eval at create (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:15:1)
    at hook.callAsync (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\enhanced-resolve\lib\Resolver.js:285:5)
    at _fn0 (eval at create (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:27:1)
    at resolver.doResolve (\test_cristianaortiga\test_storybook_gatsby_5_3\node_modules\enhanced-resolve\lib\DescriptionFilePlugin.js:67:43)

With that my .storybook/main.js now looks like:

// .storybook/main.js
module.exports = {
  //stories: ["../stories/**/*.stories.js"],
  stories: ["../src/components/**/*.stories.js"], // introspects the `components` folder
  // registers the installed addons(registration order matters)
  addons: [
    "@storybook/addon-a11y",
    "@storybook/addon-actions",
    "@storybook/addon-links",
    "@storybook/addon-viewport",
    "@storybook/addon-knobs",
  ],
}
  • Onto the key thing that triggered the issue. Before 5.3 the configuration for Storybook was a bit clunky and all over the place, now with this it becomes more streamlined (in my personal view). What this means is that almost all of the configuration will be present in the main.js file. Probably 90% of the time you wont’ need nothing more, but there are some cases like this one that will require some extra work. What this means is that everything that affects the way the component stories “look and feel” on a global scale, or if some addons are needed, then that configuration should be in a file called preview.js, more specifically adding system wide decorators, arguments or use a global css, or in this particular case use Styled components. With that out of the way i created a preview.js inside the .storybook folder with the following:
// .storybook/preview.js
import React from 'react';
import { addDecorator, addParameters } from '@storybook/react';
import { withA11y } from '@storybook/addon-a11y';
import { action } from '@storybook/addon-actions';

// simple layout component that generates a mocked Global Style in Styled components
import GlobalStyle from '../src/components/GlobalStyle'

// system wide decorator that all of the stories will use
addParameters({
  options: { panelPosition: 'bottom' },
  viewport: {
    viewports: [
      {
        name: 'Testing breakpoint',
        styles: {
          width: `600px`,
          height: '768px',
        },
      },
    ],
  },
});

// Gatsby's Link overrides:
// Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here
global.___loader = {
  enqueue: () => {},
  hovering: () => {},
};

// Gatsby internal mocking to prevent unnecessary errors in storybook testing environment
global.__PATH_PREFIX__ = '';

// This is to utilized to override the window.___navigate method Gatsby defines and uses to report what path a Link would be taking us to if it wasn't inside a storybook
window.___navigate = pathname => {
  action('NavigateTo:')(pathname);
};

// system wide decorator to allow the addon to be used
addDecorator(withA11y);
//
// system wide decorator that will inject the global style component into Storybook 
// the story function in conjunction with the children prop will make so that all the "descendants"
// will inherit the styles
addDecorator(story => (
  <>
    <GlobalStyle />
    {story()}
  </>
));
  • As Gatsby operates on one side of the spectrum (server side rendering), some special configuration is required, which means that you’ll need to create a new file inside your .storybook folder called webpack.config.js (using this will change Storybook to full control mode, meaning that you’re responsible for controlling what is built and configured) with the following content:
// .storybook/webpack.config.js
module.exports = ({ config }) => {
    // set the NODE_ENV to 'production' by default, to allow babel-plugin-remove-graphql-queries to remove static queries
    process.env.NODE_ENV = 'production';
  
    // Transpile Gatsby module because Gatsby includes un-transpiled ES6 code.
    config.module.rules[0].exclude = [/node_modules\/(?!(gatsby)\/)/];
  
    // use installed babel-loader which is v8.0-beta (which is meant to work with @babel/core@7)
    config.module.rules[0].use[0].loader = require.resolve('babel-loader');
  
    // use @babel/preset-react for JSX and env (instead of staged presets)
    config.module.rules[0].use[0].options.presets = [
      require.resolve('@babel/preset-react'),
      require.resolve('@babel/preset-env'),
    ];
  
    config.module.rules[0].use[0].options.plugins = [
      // use @babel/plugin-proposal-class-properties for class arrow functions
      require.resolve('@babel/plugin-proposal-class-properties'),
      // use babel-plugin-remove-graphql-queries to remove static queries from components when rendering in storybook
      require.resolve('babel-plugin-remove-graphql-queries'),
    ];
  
    // Prefer Gatsby ES6 entrypoint (module) over commonjs (main) entrypoint
    config.resolve.mainFields = ['browser', 'module', 'main'];
  
    return config;
  };

With that the config part is over, onto the components and stories.

  • Created the src\components folder and inside it i added some components:
  • GlobalStyle.js that will be used by both Gatsby and Storybook with the following inside it:
import React from "react"
import { createGlobalStyle } from "styled-components"
const GlobalStyle = createGlobalStyle`
  body {
    color: ${props => (props.theme === "purple" ? "purple" : "white")};
    margin: 3rem auto;
    max-width: 600px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
`
export default ({ children }) => (
  <React.Fragment>
    <GlobalStyle theme="purple" />
    {children}
  </React.Fragment>
)
  • Task.js with the following inside:
import React from "react"
import styled, { css } from "styled-components"

const TaskWrapper = styled.div`
  font-size: 14px;
  line-height: 20px;
  display: flex;
  flex-wrap: wrap;
  height: 3rem;
  width: 100%;
  ${props =>
    props.disabled &&
    css`
      background: #aaa;
    `}
  transition: all ease-out 150ms;
`

const TaskInput = styled.input`
  width: 100%;
  text-overflow: ellipsis;
  ${props =>
    props.disabled &&
    css`
      color: #aaa;
    `}
`

const Task = ({ taskInfo }) => (
  <TaskWrapper>
    <TaskInput value={taskInfo.node.title} readonly placeholder="Input title" />
  </TaskWrapper>
)
export default Task
  • Tasks.js which will be basically a composite component of Task with the following:
import React from "react"
import styled from "styled-components"
import Task from './Task'
const TasksWrapper = styled.div`
  font-size: 14px;
  line-height: 20px;
  display: flex;
  flex-wrap: wrap;
  height: 3rem;
  width: 100%;
`
const Tasks = ({taskList }) => {
  if (taskList.length === 0) {
    return (
      <TasksWrapper>
        <h1>Good news, you have no tasks! All good</h1>
      </TasksWrapper>
    )
  }
  return (
    <TasksWrapper>
      {taskList.map(task => (
        <Task taskInfo={task} key={task.node.id}/>
      ))}
    </TasksWrapper>
  )
}
export default Tasks
  • User.js with the following:
import React from "react"
import styled from "styled-components"
const UserWrapper = styled.div`
  display: flex;
  align-items: center;
  margin: 0 auto 12px auto;
  &:last-child {
    margin-bottom: 0;
  }
`
const Avatar = styled.img`
  flex: 0 0 96px;
  width: 96px;
  height: 96px;
  margin: 0;
`
const Description = styled.div`
  flex: 1;
  margin-left: 18px;
  padding: 12px;
`
const Username = styled.h2`
  margin: 0 0 12px 0;
  padding: 0;
`
const Excerpt = styled.p`
  margin: 0;
`
const User = ({userInfo}) => (
  <UserWrapper>
    <Avatar src={userInfo.avatar} alt="" />
    <Description>
      <Username>{userInfo.username}</Username>
      <Excerpt>{userInfo.excerpt}</Excerpt>
    </Description>
  </UserWrapper>
)
export default User
  • IndexScreen.js with the following inside:
import React from "react"
import Tasks from "./Tasks"
import User from './User'
import { useStaticQuery, graphql } from "gatsby"

const PureIndexScreen = ({ data }) => {
  
  const {allTasksJson}=data
  const {edges}= allTasksJson
  return <Tasks taskList={edges}/>
}
const IndexScreen = props => {
  const AllTasksDataResult = useStaticQuery(graphql`
    query fetchAllTasks {
      allTasksJson {
        edges {
          node {
            id
            title
            state
            updated_at
          }
        }
      }
    }
  `)

  return (
      <div>
          <h1>a minimal task list with gatsby and Storybook</h1>
          <User userInfo={props.userData}/>
          <PureIndexScreen {...props} data={AllTasksDataResult} />
      </div>
  )
}
export default IndexScreen

This is a special component, for two reasons, one, it contains a GraphQL query and you’ll see shortly how you can handle it inside Storybook and two it will “act” as traditional Gatsby page, to avoid having to deal with error i mentioned above.

We have the components and Storybook configured, onto the stories.

I created a Storybook story for each component, so that you could see it all in effect.

  • Starting with User.stories.js:
import React from "react"
import User from "./User"
import { withKnobs, object } from "@storybook/addon-knobs"

/**
 * standard Storybook CSF configuration
 * component will relate to which component to be used
 * excludeStories is used tell Storybook that everything that ends with Data is to be ignored or otherwise would be turned into a Story and generate some errors
 * decorators is where all the decorators that belong to the component's story should be placed.
 * title is self explanatory
 */
export default {
  component: User,
  excludeStories: /.*Data$/,
  decorators: [withKnobs],
  title: "Simple Component|User",
}
// dummy data to be used by the component
// notice the usage of export, which means that this data can be used in other stories/tests 
//(do not use it inside a component or Gatsby will break the build)
export const UserInformationData = {
  username: "Jane Doe",
  avatar:
    "https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg",
  excerpt:
    "I'm Jane Doe. Lorem ipsum dolor sit amet, consectetur adipisicing elit.",
}

// The default story of the component (note the usage of Default), this is a trick to bypass some es6 syntax
// as default is a reserved word
// This story will use the knobs addon that will allow you to modify the data while Storybook is running
export const Default = () => (
  <User
    userInfo={object("userInfo", {
      ...UserInformationData,
    })}
  />
)
// Another simple story to check the behaviour of the component
export const AnotherUser = () => (
  <User
    userInfo={{
      ...UserInformationData,
      username: "Another random user",
      excerpt: "Random description",
    }}
  />
)
  • Task.stories.js contains the following:
import React from "react"
import Task from "./Task"
import { withKnobs, object } from "@storybook/addon-knobs"
/**
 * standard Storybook CSF configuration
 * component will relate to which component to be used
 * excludeStories is used tell Storybook that everything that ends with Data is to be ignored or otherwise would be turned into a Story and generate some errors
 * decorators is where all the decorators that belong to the component's story should be placed.
 * title is self explanatory
 */

export default {
  component:Task,
  excludeStories: /.*Data$/,
  decorators: [withKnobs],
  title: "Simple Component|Task",
}
// dummy data to be used by the component
// notice the usage of export, which means that this data can be used in other stories/tests 
//(do not use it inside a component or Gatsby will break the build)
export const taskData = {
  node: {
    id: "1",
    title: "Dummy Task",
    state: "Task_INBOX",
    updated_at: "2020-01-01",
  },
}
// The default story of the component (note the usage of Default), this is a trick to bypass some es6 syntax
// as default is a reserved word
// This story will use the knobs addon that will allow you to modify the data while Storybook is running
export const Default = () => (
  <Task taskInfo={object("taskInfo", { ...taskData })} />
)
  • Tasks.stories.js contains the following:
import React from "react"
import Tasks from "./Tasks"
import { taskData } from "./Task.stories"
/**
 * standard Storybook CSF configuration
 * component will relate to which component to be used
 * excludeStories is used tell Storybook that everything that ends with Data is to be ignored or otherwise would be turned into a Story and generate some errors
 * title is self explanatory
 */
export default {
  component: Tasks,
  excludeStories: /.*Data$/,
  title: "Composite Component|Tasks",
}
// creates a dummy array of data to be used by the component
// Notice that the array will "spread" the task information inside the other story
export const defaultTasksData = [
  {
    
    node: {
      ...taskData.node,
      id: "1",
      title: "Task 1",
    },
  },
  {
    node: {
      ...taskData.node,
      id: "2",
      title: "Task 2",
    },
  },
  {
   
    node: {
      ...taskData.node,
      id: "3",
      title: "Task 3",
    },
  },
  {
    node: {
      ...taskData.node,
      id: "4",
      title: "Task 4",
    },
  },
  {
  
    node: {
      ...taskData.node,
      id: "5",
      title: "Task 5",
    },
  },
  {
    
    node: {
      ...taskData.node,
      id: "6",
      title: "Task 6",
    },
  },
]
// The default story of the component (note the usage of Default), this is a trick to bypass some es6 syntax
// as default is a reserved word
export const Default = () => <Tasks taskList={defaultTasksData} />

// A simple story for the component mocking a empty set of tasks
export const EmptyTasks = () => <Tasks taskList={[]} />

  • And finally IndexScreen.stories.js
import React from "react"
import IndexScreen from "./IndexScreen"
import { defaultTasksData } from './Tasks.stories'
import { UserInformationData } from './User.stories'

// dummy data to be used by the component
// notice the usage of export, which means that this data can be used in other stories/tests 
//(do not use it inside a component or Gatsby will break the build)
export default {
  component: IndexScreen,
  excludeStories: /.*Data$/,
  title: "Screen Component|index",
}
// the default story for the screen
// it will use the information that is present in the other stories (user.stories.js and tasks.stories.js)
export const Default=()=><IndexScreen userData={UserInformationData} {...defaultTasksData}/>

After all of this, issuing yarn storybook (i use yarn, if you use npm, you’ll need to use npm run storybook) to start Storybook in development mode and waiting for the build to complete, i’m presented with this:

ERROR in ./src/components/IndexScreen.js
Module not found: Error: Can't resolve '..\..\public\static\d\3962212716.json' in '\test_cristianaortiga\test_storybook_gatsby_5_3\src\components'
 @ ./src/components/IndexScreen.js 3:0-73 18:27-42
 @ ./src/components/IndexScreen.stories.js
 @ ./src/components sync ^\.\/(?:(?:(?!\.)(?:(?:(?!(?:|[\\\/])\.).)*?)[\\\/])?(?!\.)(?=.)[^\\\/]*?\.stories\.js[\\\/]?)$
 @ ./.storybook/generated-entry.js
 @ multi ./node_modules/@storybook/core/dist/server/common/polyfills.js ./node_modules/@storybook/core/dist/server/preview/globals.js ./.storybook/preview.js ./node_modules/@storybook/addon-knobs/dist/preset/addDecorator.js ./.storybook/generated-entry.js ./node_modules/webpack-hot-middleware/client.js?reload=true&quiet=true

Should you encounter this issue in your side, this is due to the fact that the IndexScreen component uses a GraphQL query and is expecting something to be present that is not, the data relevant to the component does not exist anywhere. The trick to bypass this is the following, run yarn develop, (once again i’m using yarn, if you use npm, adjust accordingly). to generate a Gatsby development build and with that create the piece of data that is required and then run Storybook with yarn storybook.

After that, opening up http://localhost:6006 in your browser you should be presented with:

cristina_storybook_1

In order to make this reproduction complete i added the serve package to my Gatsby site to see how Storybook would behave if i issued a Storybook production build. With that i changed my scripts aswell to incorporate this change and now it looks like

{
.....
"scripts":{
  "build-storybook": "build-storybook",
   "preview-storybook":"serve storybook-static"
  }
}

Issued yarn build-storybook it ran to completion and then yarn preview-storybook and opening up http://localhost:5000 i’m presented with mocked production deployment version of Storybook.

The whole code behind this comment is in this repo, i would sugest you go over it at your own pace and tinker with it, before deep diving into the pr.

A nice touch to include in the documentation update would be mentioning the fix to the error stated above. I think that could help out not only the Gatsby community but also the Storybook community.

I’ll be on the lookout for the pr and continue to help you with it. And i really hope this goes through and you get your much deserved swag.

Finally, sorry for the extremely long post, but i intended to provide you a full blown implementation that you could use as a baseline to when you update the documentation, also don’t forget to mention this issue when you submit the pr.

0reactions
bsgreenbcommented, Jul 20, 2020

Update: I got it even simpler, zero config typescript, no preset, with Storyook 6 (on next branch)

main.ts:

export default {
  stories: [
    "../src/ui/**/*.stories.ts",
    "../src/ui/**/*.stories.tsx",
    "../src/ui/**/*.stories.mdx",
  ],
  addons: [
    {
      name: "@storybook/addon-docs",
      options: {
        configureJSX: true,
      },
    },
    "@storybook/addon-a11y",
    "@storybook/addon-actions",
    "@storybook/addon-controls",
    "@storybook/addon-links",
    "@storybook/addon-storysource",
    "@storybook/addon-toolbars",
    "@storybook/addon-viewport",
    "@storybook/addon-backgrounds/register",
  ],
  babel: {
    plugins: [
      "@babel/plugin-proposal-optional-chaining",
      "@babel/plugin-proposal-nullish-coalescing-operator",
      "@babel/plugin-proposal-class-properties",
      "babel-plugin-remove-graphql-queries",
      [
        "styled-jsx/babel",
        { optimizeForSpeed: true, plugins: ["styled-jsx-plugin-postcss"] },
      ],
    ],
  },
};
Read more comments on GitHub >

github_iconTop Results From Across the Web

Build UI components - Storybook Tutorials
Learn how to develop UIs with components and design systems. Our in-depth frontend guides are created by Storybook maintainers and peer-reviewed by the...
Read more >
Setting up a Design System in Storybook with React, Styled ...
To use Tailwind together with Styled-Components, we will need to install ... On the root directory, add a new file called postcss.config.js ...
Read more >
Default styles in Storybook with Styled Components
Recently I was building a Storybook for my new project. ... import { createGlobalStyle } from "styled-components"; import reboot from ...
Read more >
Starting with Gatsby + Styled Components + Storybook
Step 1: Installation · Step 2. Create new component and stories · Step 3. How to set global styles in Storybook environment ·...
Read more >
The Ultimate Guide to Storybook for React Applications - PART II
When you installed Storybook, a new file called main.js was added to your ... We'll build the component in React with Styled Components, ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found