Is there any way to have the addon automatically add the local README.md? Given a consistent file structure, I was hoping to be able to just always load the README for a given story, without having to add the decorator to every single story.
For example, I use:
components/input
├── Input.vue
├── README.md
└── story.js
for every component.
To implement this we should patch webpack config.
The flow should be:
README.md files inside stories directoryinput){[componentName]: README} and pass it to global variable or to the process.env I would be happy for any help.
Work to do! Okay, I will keep this in mind. Thanks for the feedback. Not sure when / if I will get time to work on this, but I'll see what I can do.
Yes, it's possible: I have done it like this:
config.js
import React from 'react'
import path from 'path'
import { ThemeDecorator } from './decorators/index'
import {
configure,
storiesOf,
getStorybook,
addDecorator,
} from '@storybook/react'
import { withKnobs } from '@storybook/addon-knobs/react'
import { withReadme } from 'storybook-readme'
import { withOptions } from '@storybook/addon-options'
const getPackageName = filePath =>
path
.dirname(filePath)
.split(path.sep)
.reverse()[1]
const req = require.context(
'../../../components',
true,
/^((?!node_modules).)*\.example\.(js|tsx)$/
)
const readMeReq = require.context(
'../../../components',
true,
/^((?!node_modules).)*\README\.md$/
)
addDecorator(
withOptions({
name: 'Components',
})
)
configure(() => {
req.keys().forEach(pathToExample => {
const { name, Example, options = {} } = req(pathToExample)
const packageName = getPackageName(pathToExample)
const readmePath = readMeReq.keys().find(rm => rm.includes(packageName))
const readme = readMeReq(readmePath)
storiesOf(packageName, module)
.addDecorator(withKnobs)
.addDecorator(withReadme(readme))
.addDecorator(withOptions(options))
.addDecorator(ThemeDecorator)
.add(name, () => <Example />, { options })
})
}, module)
export { getStorybook }
HelloWorld.example.js:
import React from 'react'
import HelloWorld from 'path/to/hello-world'
export const name = 'Default'
export const Example = () => (
<HelloWorld />
)
We're planning on making this a feature of addon-notes in the future too, so "it just works".
Great setup you discovered there @kristof0425 !
@tuchk4 - Will this implementation mentioned above from @kristof0425 still work with v5? I am only getting it to work for one of my components?


import "./styles.css";
import React from "react";
import path from "path";
import { getStorybook, storiesOf, configure } from "@storybook/react";
import { withKnobs } from "@storybook/addon-knobs";
import { addReadme } from 'storybook-readme'
let getComponentName = filePath =>
path
.dirname(filePath)
.split(path.sep)
.reverse()[0];
let getPackageName = filePath =>
path
.dirname(filePath)
.split(path.sep)
.reverse()[1];
configure(() => {
// Automatically import all examples
const req = require.context(
"../packages",
true,
/^((?!node_modules).)*\.example\.js$/
);
const readMeReq = require.context(
"../packages",
true,
/^((?!node_modules).)*\.README\.md$/
)
req.keys().forEach(pathToExample => {
const { name, Example } = req(pathToExample);
const packageName = getPackageName(pathToExample)
const componentName = `${packageName}.${getComponentName(
pathToExample
)}`;
const readmePath = readMeReq.keys().find(rm => rm.includes(packageName))
const readme = readMeReq(readmePath)
console.log(readmePath)
storiesOf(componentName, module)
.addDecorator(withKnobs)
.addDecorator(addReadme)
.addParameters({
readme: {
content: '<!-- STORY --><!-- PROPS -->',
sidebar: readme,
},
})
.add(name, () => <Example dummy="test" />);
});
}, module);
export { getStorybook };
@jephjohnson yes, it should work
but maybe split into two lines. Not sure it will be parsed correctly )
readme: {
content: `
<!-- STORY -->
<!-- PROPS -->
`,
sidebar: readme,
},
})
@tuchk4 - Ah, stil no dice.

@jephjohnson
addDecorator(addReadme)Here is working example:
import React from 'react';
import path from 'path';
import {
getStorybook,
storiesOf,
addDecorator,
configure,
} from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs';
import { addReadme } from 'storybook-readme';
// register decorator
addDecorator(addReadme);
let getComponentName = filePath =>
path
.dirname(filePath)
.split(path.sep)
.reverse()[0];
let getPackageName = filePath =>
path
.dirname(filePath)
.split(path.sep)
.reverse()[1];
configure(() => {
// Automatically import all examples
const req = require.context(
'../auto',
true,
/^((?!node_modules).)*\.example\.js$/,
);
const readMeReq = require.context(
'../auto',
true,
/^((?!node_modules).)*\.README\.md$/,
);
req.keys().forEach(pathToExample => {
const { name, Example } = req(pathToExample);
const packageName = getPackageName(pathToExample);
const componentName = `${packageName}.${getComponentName(pathToExample)}`;
const readmePath = readMeReq.keys().find(rm => rm.includes(packageName));
const readme = readMeReq(readmePath);
storiesOf(componentName, module)
.addParameters({
readme: {
content: '<!-- STORY --><!-- PROPS -->',
sidebar: readme,
},
})
.add(name, () => <Example dummy="test" />);
});
}, module);
export { getStorybook };
Button.example.js
import React from 'react';
import Button from './';
export const name = 'Default';
export const Example = () => <Button label="Hi" />;
// copy proptypes so <!-- PROPS --> will work
Example.propTypes = Button.propTypes;

The decorator was in there. I’m thinking it’s related to my es lint folder. Still having the same issue of it not iterating through all the readmes
Thinking that I should to add @kristof0425's solution to README and close this issue
That would be awesome! 🎉 @tuchk4 Also thanks for the v5 support!
@kristof0425 Need you advice :)
Also ping @jephjohnson as I know you use such way to load stories with docs.
I would like to add to docs how to automatically add README based on the story path.
Here is PR - https://github.com/tuchk4/storybook-readme/pull/166
When tests faced the problem:
require.context is not a function
In your example this function is used to get all stories and README files by specific pattern.
Found this issue https://github.com/storybooks/storybook/issues/4479
Do we need to install additional babel plugins to use your solution?
Currently, this is the setup we use for Storybook:
| Package | Version |
| -------- | -------- |
| webpack | v4.28.4 |
| @storybook/react | v5.0.5 |
| storybook-readme | v5.0.1 |
| babel-loader | v8.0.0 |
Webpack config
const themeOverride = require('../webpack.config.theme.override')
process.env.NODE_ENV = 'development'
module.exports = {
resolve: {
extensions: ['.mjs', '.ts', '.tsx', '.js'],
},
module: {
rules: [
{
test: /\.(js|ts|tsx)$/,
use: ['babel-loader'],
},
{
test: /\.svg$/,
loader: 'file-loader',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.less$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'less-loader',
options: {
modifyVars: themeOverride,
javascriptEnabled: true,
},
},
],
},
],
},
devtool: 'source-map',
}
Storybook config
import React from 'react'
import path from 'path'
import { ThemeDecorator } from './decorators/index'
import {
configure,
storiesOf,
getStorybook,
addParameters,
} from '@storybook/react'
import { withKnobs } from '@storybook/addon-knobs/react'
import { addReadme } from 'storybook-readme'
const getPackageName = filePath =>
path
.dirname(filePath)
.split(path.sep)
.reverse()[1]
const getPathToExample = path => {
let hasReachedExamples = false
return path
.split('/')
.filter((folder, i) => {
// first two folders are common
if (folder === 'examples') hasReachedExamples = true
if (i < 2 || hasReachedExamples || folder.includes('.tsx')) return false
else return true
})
.join('/')
.replace(/.example.(js|tsx)/, '')
}
const req = require.context(
'../../../components',
true,
/^((?!node_modules).)*\.example\.(js|tsx)$/
)
const readMeReq = require.context(
'../../../components',
true,
/^((?!node_modules).)*\README\.md$/
)
addParameters({
name: 'MK-Components',
})
configure(() => {
req.keys().forEach(pathToExample => {
const { name, Example, options = {} } = req(pathToExample)
const packageName = getPackageName(pathToExample)
const readmePath = readMeReq.keys().find(rm => rm.includes(packageName))
const readme = readMeReq(readmePath)
const path = getPathToExample(pathToExample)
storiesOf(path, module)
.addDecorator(
withKnobs({
escapeHTML: false,
})
)
.addDecorator(addReadme)
.addParameters({
readme: {
sidebar: readme,
},
})
.addParameters(options)
.addDecorator(ThemeDecorator)
.add(name, () => <Example />, { options })
})
}, module)
export { getStorybook }
Babel config (I'm not sure whether it's relevant to this problem)
js
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-flow',
'@babel/typescript',
],
plugins: [
'@babel/plugin-transform-async-to-generator',
[
'import',
{
libraryName: 'antd',
style: true,
},
],
'react-hot-loader/babel',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-import-meta',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-json-strings',
[
'@babel/plugin-proposal-decorators',
{
legacy: true,
},
],
'@babel/plugin-proposal-function-sent',
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-proposal-numeric-separator',
'@babel/plugin-proposal-throw-expressions',
'@babel/plugin-proposal-export-default-from',
'@babel/plugin-proposal-logical-assignment-operators',
'@babel/plugin-proposal-optional-chaining',
[
'@babel/plugin-proposal-pipeline-operator',
{
proposal: 'minimal',
},
],
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-do-expressions',
'@babel/plugin-proposal-function-bind',
],
env: {
test: {
plugins: [
'emotion',
[
'@babel/plugin-transform-modules-commonjs',
{
loose: true,
},
],
],
},
development: {
plugins: [
['emotion', { sourceMap: true, autoLabel: true }],
'@babel/plugin-transform-modules-commonjs',
'babel-plugin-dynamic-import-node',
],
},
production: {
plugins: [
['emotion', { hoist: true }],
'@babel/plugin-transform-modules-commonjs',
],
},
},
}
@tuchk4 If you could share with me how you test the package, I can dig into the problem myself as well. 🙂
@kristof0425 @tuchk4 - Hey guys, sorry for the delay...Any new feedback on this? Still haven't got it resolved on my end. Here is the pakage.json.
{
"name": "@demo",
"private": true,
"license": "ISC",
"scripts": {
"start": "start-storybook -p 9001 -c .storybook",
"test": "jest -c jest.config.unit.js",
"test:watch": "jest -c jest.config.unit.js --watch",
"test:visual": "jest -c jest.config.visual-regression.js",
"precommit": "pretty-quick --staged",
"lint": "eslint -c .eslintrc.json 'packages/**/*.js'",
"build-storybook": "build-storybook -c .storybook -o .out"
},
"devDependencies": {
"0": "^0.0.0",
"@babel/cli": "^7.2.3",
"@babel/core": "^7.3.4",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.3.4",
"@babel/plugin-proposal-object-rest-spread": "^7.3.4",
"@babel/plugin-transform-object-assign": "^7.2.0",
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/polyfill": "^7.4.3",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@storybook/addon-a11y": "^5.0.6",
"@storybook/addon-actions": "^5.0.1",
"@storybook/addon-knobs": "^5.0.1",
"@storybook/addon-storysource": "^5.0.1",
"@storybook/react": "^5.0.6",
"autoprefixer": "^9.2.1",
"babel-eslint": "10.0.1",
"babel-jest": "^24.5.0",
"babel-loader": "^8.0.5",
"eslint": "^5.15.3",
"eslint-config-prettier": "^4.1.0",
"eslint-config-react-app": "^3.0.4",
"eslint-loader": "2.1.1",
"eslint-plugin-flowtype": "3.0.0",
"eslint-plugin-import": "2.14.0",
"eslint-plugin-jsx-a11y": "6.1.2",
"eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-react": "7.11.1",
"gzip-size": "^5.0.0",
"html-webpack-plugin": "^3.2.0",
"husky": "^1.1.2",
"jest": "24.4.0",
"jest-dom": "^3.1.3",
"jest-puppeteer-react": "^4.6.1",
"prettier": "^1.14.3",
"pretty-bytes": "^5.1.0",
"pretty-quick": "^1.8.0",
"puppeteer": "^1.14.0",
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react-testing-library": "^6.0.0",
"rollup": "^0.66.6",
"rollup-plugin-babel": "^4.0.3",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-uglify": "^6.0.0",
"storybook-readme": "^5.0.2",
"user-event": "^1.4.7"
},
"prettier": {},
"husky": {
"hooks": {
"pre-push": "yarn run lint"
}
},
"dependencies": {
"classnames": "^2.2.6",
"prop-types": "^15.7.2"
}
}
Hey @tuchk4 This would become a much easier process once storybook config has changed to monoconfig:
@ndelangen nice new feature! Seems it will be possible to implement awesome new features much easier :)
@tuchk4 I put together a repo that demonstrates the error @jephjohnson has been describing.
https://github.com/fgaleano/readme
Just npm install on root and then npm start.
The main problem is that prop's type, description and defaultValue are not picked up even though they are present in the component's props definition.
You can see Storybook's configuration in .storybook/config.js and the component props at packages/core/src/button/index.js. Then all of the examples have the same configuration as far as exporting props.
Let us know if you can reproduce the error as I'm describing it.
Thank you!
@kristof0425 @tuchk4 - Any thoughts on the repo above? Thanks, Jeph
@jephjohnson sorry for the delay, had hard working days.
going to back to storybook-readme in nearest days.
@jephjohnson sorry for the delay, had hard working days.
going to back to storybook-readme in nearest days.
Hi @tuchk4 - Just touching base on this. Anything new? Thanks, J
Most helpful comment
We're planning on making this a feature of addon-notes in the future too, so "it just works".
Great setup you discovered there @kristof0425 !