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:
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
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:
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:
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:
import { loadJSStories } from 'storybook-loader';
const req = require.context('./');
loadJSStories(req);
And of course, you can load markdown files by using loadMDStories
API:
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.
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!