Auto-load all stories in storybook by storybook-loader
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 repeat 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:
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, you can access my github repo:
and give me a star if you feel it useful.
Have a nice storybook!