February 26, 2019 • ☕️ 4 min read

Same article in Medium: https://medium.com/@thundermiracle/auto-load-all-stories-in-storybook-by-storybook-loader-39f4a5b78d03

It is always said that documentation is as important as source code’s quality.

Without neat and tidy documentations, we’ll even forget how/why/what about the code we wrote couple of months before.

As components library grows bigger and bigger, storybook (https://storybook.js.org/) came out to take over the documentation problem. storybook is not new to react engineer. It’s easy enough to create a document site with storybook, and it successfully gained more than 30,000 stars and also being supported well by the community.

It seems that docz (https://www.docz.site/) and docusaurus (https://docusaurus.io/) are growing rapidly, but storybook is still one of most competive documentation tools as the big number of community.

By the way, storybook is implementing a new addon to support mdx (https://github.com/storybooks/storybook/issues/4341) as well. It’s really a good news.

Let’s write a story

create a TextField.stories.js:

TextField.stories.js
Copy
import React from 'react';
import { storiesOf } from '@storybook/react';
import WithLabel from './TextField/WithLabel';
import NumberOnly from './TextField/NumberOnly';
import Outline from './TextField/Outline';

storiesOf('TextField', module)
  .add('WithLabel', () => <WithLabel />)
  .add('NumberOnly', () => <NumberOnly />)
  .add('Outline', () => <Outline />);

The problem is that you have to add('NewPattern', () => <NewPattern />) to xxx.stories.js once a new pattern appears.

And of course, if you have multiple components and multiple patterns, this repeated work will become little annoying.

Is there a way to solve this meanless repeat work?

Yes. Webpack’s require.context (https://webpack.js.org/guides/dependency-management/#requirecontext) is the magic sugar.

require.context can read all files under a folder before compiling.

So TextField.stories.js will look like this

TextField.stories.js
Copy
import React from 'react';
import path from 'path';
import { storiesOf } from '@storybook/react';
const req = require.context('./TextField', true, /.js$/);
const stories = storiesOf('TextField', module);

req.keys().forEach((fileName) => {
  const Component = req(fileName);
  stories.add(path.basename(fileName, '.js'), () => <Component />);
});

And after creating this file, you can concentrate on adding new patterns to TextField folder.

Multiple patterns problem is solved , but not the multiple components problem. You still have to create Button.stories.js, Label.stories.js … one folder one file.

What’s the solution?

Well, maybe you think, just make a folder name array, maintain the array and iterate it when using:

index.stories.js
Copy
import React from 'react';
import path from 'path';
import { storiesOf } from '@storybook/react';

const folders = [  'TextField',  'Button',  'Label',]
folders.forEach((folderName) => {  const req = require.context(`./${folderName}`, true, /.js$/);
  const stories = storiesOf(folderName, module);
  req.keys().forEach((fileName) => {
    const Component = req(fileName);
    stories.add(path.basename(fileName, '.js'), () => <Component />);
  });
});

But NO! It doesn’t work as you expected.

folderName is a variable, which is decided in compile, but require.context need that folderName before it’s been compiled.

Yes, you CANNOT pass a variable to require.context to relief the duplicated work.

However, you can get all files using require.context and parse them by yourself:

index.stories.js
Copy
import React from 'react';
import path from 'path';
import { storiesOf } from '@storybook/react';

const reqAll = require.context('./', true, /.js$/);const folderSubkeysMap = getFoldersWithFileKey(reqAll);
folderSubkeysMap.forEach(([folderName, keys]) => {
  const stories = storiesOf(folderName, module);
  keys.forEach((fileName) => {
    const Component = reqAll(fileName);
    stories.add(path.basename(fileName, '.js'), () => <Component />);
  });
});

And this time, you’re really free.

To simplify these parsing steps, I wrote a library called

storybook-loader

By using this library, you can achieve the same goal simply like this:

index.stories.js
Copy
import { loadJSStories } from 'storybook-loader';

const req = require.context('./');
loadJSStories(req);

And of course, you can load markdown files by using loadMDStories API:

index.stories.js
Copy
import { loadMDStories } from 'storybook-loader';
import { doc } from 'storybook-readme';

const req = require.context('./');
const options = {
  // decorate md's content
  contentFuncList: [
    doc,
  ],
}
loadMDStories(req, options);

You maybe not only want to display components, but also need to show markdown notes in the panel by storybook addon-notes.

You can use loadJSWithNotesStories. It’ll try to find the markdown file in the same folder which has the same name and add it to storybook’s notes section.

index.stories.js
Copy
import { loadJSWithNotesStories } from 'storybook-loader';

const req = require.context('./');
const options = {
  // [optional] you can also addDecorator(withNotes) in config.js
  storySubFuncList: [
    [
      'addDecorator',
      [withNotes],
    ],
  ],
};
loadJSWithNotesStories(reqList, options);

For more details, access the github repo and give me a star if you feel it useful.

https://github.com/thundermiracle/storybook-loader

Have a nice storybook!


Relative Posts

gatsby develop not working after upgrade

May 10, 2020

ThunderMiracle

Blog part of ThunderMiracle.com