Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to automatically add README's based on the module path? #83

Closed
robcresswell opened this issue Aug 9, 2018 · 21 comments
Closed

Comments

@robcresswell
Copy link
Contributor

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.

@tuchk4
Copy link
Owner

tuchk4 commented Aug 9, 2018

To implement this we should patch webpack config.
The flow should be:

  • Find all README.md files inside stories directory
  • According to readme file path - pick component name. (In your example its input)
  • Generate Map {[componentName]: README} and pass it to global variable or to the process.env
  • Then, when addon renders REAMDE - check collected files and render it if exists.

I would be happy for any help.

@robcresswell
Copy link
Contributor Author

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.

@kristofdombi
Copy link

kristofdombi commented Jan 18, 2019

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 />
)

@ndelangen
Copy link
Collaborator

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 !

@jephjohnson
Copy link

jephjohnson commented Mar 16, 2019

@tuchk4 - Will this implementation mentioned above from @kristof0425 still work with v5? I am only getting it to work for one of my components?
Screen Shot 2019-03-16 at 6 13 58 PM

Screen Shot 2019-03-16 at 6 16 15 PM

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 };

@tuchk4
Copy link
Owner

tuchk4 commented Mar 16, 2019

@jephjohnson yes, it should work
but maybe split into two lines. Not sure it will be parsed correctly )

   readme: {
          content: `
<!-- STORY -->
<!-- PROPS -->
`,
          sidebar: readme,
        },
      })

@jephjohnson
Copy link

@tuchk4 - Ah, stil no dice.

Screen Shot 2019-03-16 at 7 02 53 PM

@tuchk4
Copy link
Owner

tuchk4 commented Mar 17, 2019

@jephjohnson

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;

@tuchk4
Copy link
Owner

tuchk4 commented Mar 17, 2019

image

@jephjohnson
Copy link

jephjohnson commented Mar 17, 2019

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

@tuchk4
Copy link
Owner

tuchk4 commented Mar 17, 2019

Thinking that I should to add @kristof0425's solution to README and close this issue

@kristofdombi
Copy link

That would be awesome! 🎉 @tuchk4 Also thanks for the v5 support!

@tuchk4
Copy link
Owner

tuchk4 commented May 7, 2019

@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 - #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 storybookjs/storybook#4479

Do we need to install additional babel plugins to use your solution?

@kristofdombi
Copy link

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)
  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. 🙂

@jephjohnson
Copy link

@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"
  }
}

@ndelangen
Copy link
Collaborator

Hey @tuchk4 This would become a much easier process once storybook config has changed to monoconfig:

storybookjs/storybook#6806

@tuchk4
Copy link
Owner

tuchk4 commented May 29, 2019

@ndelangen nice new feature! Seems it will be possible to implement awesome new features much easier :)

@fgaleano
Copy link

@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!

@jephjohnson
Copy link

@kristof0425 @tuchk4 - Any thoughts on the repo above? Thanks, Jeph

@tuchk4
Copy link
Owner

tuchk4 commented Jun 7, 2019

@jephjohnson sorry for the delay, had hard working days.
going to back to storybook-readme in nearest days.

@jephjohnson
Copy link

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants