Using Storybook in a Monorepo

kamranayub

Kamran Ayub

Posted on September 17, 2019

Using Storybook in a Monorepo

I'm currently working in a small monorepo and we have multiple packages set up for shared components, like this:

packages/
  atoms/
  molecules/
  organisms/
Enter fullscreen mode Exit fullscreen mode

Each package has React components under a src directory and since each directory is a package, they also contain node_modules (more on why that's important below).

packages/
  atoms/
    node_modules/
    src/
      components/
        Hello.js
        Hello.stories.js
    package.json
  molecules/
  organisms/
Enter fullscreen mode Exit fullscreen mode

This is different than other posts I've seen that use a root stories/ directory -- that is not how we'd like to set up our repo, we'd prefer stories to live right next to the components they describe.

Setting Up Storybook

You can follow the same steps on the getting started page for setting up Storybook in a monorepo.

Once it's done, you should have a new folder at the root:

.storybook/
  config.js
Enter fullscreen mode Exit fullscreen mode

If not, make sure that gets created. We need to make some changes to config.js to read stories within each package in the monorepo.

Configuring Storybook for a Monorepo

The key issue I ran into that prompted me to write about this is that we need to import stories using the Webpack context:

require.context('../packages', true, /stories.jsx?$/);
Enter fullscreen mode Exit fullscreen mode

This prompts Webpack to scan a src directory for paths containing stories.js or stories.jsx anywhere in the string.

Since Webpack statically analyzes this code it means we cannot dynamically read the file system and iterate through each package directory (I tried that πŸ˜”).

There's another problem. Remember I mentioned each package directory has node_modules which means that the following paths will match the regular expression:

./atoms/node_modules/@storybook__react/src/stories/blah.stories.js
./molecules/lcov-report/_html/src/components/Hello.stories.js
Enter fullscreen mode Exit fullscreen mode

And you can imagine more paths like that can match. Webpack will attempt to import these files into the bundle it generates but these will most likely cause build errors. That isn't what we want! We need to exclude everything but the src directory in a package when matching files.

The solution, as it turns out is fairly straightforward, since we have a convention where each package has components within a src directory, we really just need to match that inner src after the package name, so we can write a regular expression like this:

require.context('../packages', true, /^\.\/[^\/]+\/src\/.*stories\.jsx?$/);
Enter fullscreen mode Exit fullscreen mode

Let's break it down:

^               # match beginning of path
\.\/            # the path will begin with a "./", like ./atoms
[^\/]+          # get the first path segment (match characters up to first /)
\/src\/         # ensure we match under the `src` directory
.*              # match any character
stories\.jsx?   # match anything with "stories.js(x)" in it
$               # match end of string
Enter fullscreen mode Exit fullscreen mode

Here's an example using my favorite regex tool RegExr on which paths match and which don't:

Regular expression match example

Handy!

The Final Config

Here's our final config.js:

import { configure } from '@storybook/react';

function loadStories() {
  const req = require.context('../packages', true, /^\.\/[^\/]+\/src\/.*stories\.jsx?$/);
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);
Enter fullscreen mode Exit fullscreen mode

I hope this helps someone else and saves them the few hours I spent performing the correct rites to get this to work!


If you thought this was helpful, you can follow me or subscribe to my blog!

Originally posted at Kamranicus

πŸ’– πŸ’ͺ πŸ™… 🚩
kamranayub
Kamran Ayub

Posted on September 17, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related