storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.39k stars 9.27k forks source link

Unable to embed mdx/md documents within mdx doc page stories #7644

Closed rkara closed 4 years ago

rkara commented 5 years ago

Describe the bug An error is received when attempting to embed markdown documents within a doc page story (mdx file). Error states Failed to execute 'createElement' on 'Document': The tag name provided ('# Markdown File....

To Reproduce Steps to reproduce the behavior:

  1. Use below code along with a markdown file within story
  2. Navigate to story
  3. Open docs page
  4. View error

Expected behavior I would expect the content of the markdown file to be embedded within the mdx file content.

Screenshots If applicable, add screenshots to help explain your problem. image

Code snippets Sample Content of my mdx file


import { moduleMetadata } from '@storybook/angular';
import { Story, Meta } from '@storybook/addon-docs/blocks';
import { MatCardModule } from '@angular/material/card';

import Overview from './docs/overview.md';

<Meta
  title="Experience/Components"
  decorators={
    [
      moduleMetadata(
        {
          imports: [
            MatCardModule,
          ],
        }
      ),
    ]
    }
/>

<Overview />

<Story name="Card">
  {{
    template: `
      <div style="padding: 10px">
        <mat-card>Simple card</mat-card>
      </div>
      `,
  }}
</Story>

Sample Content of my md file


# Markdown File

This is a markdown file...

System:


Environment Info:
  System:
    OS: Windows 10
    CPU: (8) x64 Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
  Binaries:
    Node: 10.15.1 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.16.0 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
    npm: 6.9.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: 41.16299.1004.0

    "@storybook/addon-a11y": "5.2.0-beta.18",
    "@storybook/addon-actions": "5.2.0-beta.18",
    "@storybook/addon-docs": "5.2.0-beta.18",
    "@storybook/addon-knobs": "5.2.0-beta.18",
    "@storybook/addon-links": "5.2.0-beta.18",
    "@storybook/addon-notes": "5.2.0-beta.18",
    "@storybook/addon-viewport": "5.2.0-beta.18",
    "@storybook/addons": "5.2.0-beta.18",
    "@storybook/angular": "5.2.0-beta.18",
shilman commented 5 years ago

@rkara Thanks for filing this. As you pointed out in Discord, transclusion is supported by MDX but it's not currently supported by Storybook. I'll see what I can do to make this work. It's a little complicated because Storybook already has a defined behavior for .md specifically and I think it's inconsistent with MDX. However, transcluding .mdx should be possible. 😭

stale[bot] commented 5 years ago

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

darondel commented 5 years ago

Hey! In my project, we use StencilJS to develop Web Components and generate associated readme files (with the .md format). Would be nice to have this feature. πŸ‘

atanasster commented 5 years ago

Hi @darondel , having a few flights to catch up and not sure about a final solution, but here is something that you can try in the meantime:

import { Description } from '@storybook/addon-docs/blocks';
import readme from '../../README.md';

export const plainMD = () => {
  return <Description markdown={readme} />;
};
atanasster commented 5 years ago

another workaround that will just render the markdown, it will not keep the storybooks styling for h1 etc elements.

import Markdown from 'markdown-to-jsx';
import readme from '../../README.md';
....
export const markdownToJsx = () => <Markdown>{readme}</Markdown>;
atanasster commented 5 years ago

Hi, we further researched the md transclusion and here is what we found:

  1. It works for .mdx files:

    import Readme from 'readme.mdx`
    <Readme />
  2. It does not currently work for .md files, because we use by default the raw-loader and really not sure we should break behavior. So this doesnt work:

    import Readme from 'readme.md`
    <Readme />
  3. There is an easy workaround for us to add a Markdown docs block

    
    import { Meta, Markdown } from '@storybook/addon-docs/blocks';
    import Readme from 'readme.md`
{Readme}

4. My PR #8007 fix for the transclusion in csf files works:

import Readme from '../../README.md';

export const plainMD = () => ;



what do you think - renaming .md to .mdx or using a `<Markdown>` sounds sufficient?
darondel commented 5 years ago

Hey!

In my case, StencilJS generates a readme using the MD format. As far as I know, we cannot use another extension for this behavior. The workarounds that you propose are completely suitable for me, either:

In the long run, I think that supporting the import of MD files into MDX would be more intuitive and respect the MDX specification, though it will require to rethink the way you load files.

atanasster commented 5 years ago

Great to hear the workarounds are workable. I think we can close this as an issue.

Just for completeness sake, you can also add a rule to the webpack config (as long as it doesn't conflict with another loader as in the case with raw-loader:

{
  test: /\.md$/, // or any other extension we would like to import
  use: ['babel-loader', '@mdx-js/loader'],
},
shilman commented 5 years ago

@darondel @atanasster I think the only concern is breaking compatibility with how storybook currently operates. We can:

module.exports = [
  {
    name: '@storybook/addon-docs/react/preset'
    options: {
      transcludeMarkdown: true
    }
  }
]

Now that I'm writing it out, I think the best solution is to add it as an option on the preset and then make that the default in 6.0 (possibly with an overhaul of how SB treats MD across the board).

What do you think?

atanasster commented 5 years ago

@shilman thats sounds a really good solution, without a breaking change.

cgatian commented 4 years ago

I wish this worked. bitmoji

markov00 commented 4 years ago

Hi I'm also interested into this feature. If I've understood correctly we can transclude only mdx file for the moment. If that is right, could you please provide a simplified example for this? Because using the following example it will throw an error:

table.mdx

### This is a table in an mdx

| Prop | Type | Default | Note |
|:------|:------:|:---------:|:------|
|a | a | a | a |

doc.mdx

import { Meta } from "@storybook/addon-docs/blocks";
import MyTable from './table.mdx'

<Meta title="my title" />

# A good title

A nice description

<MyTable />

It will throw an error saying:

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Check the render method of `MDXCreateElement`.
Hicksmix commented 4 years ago

Hello there,

I was also stuck on this problem for a while, until I found something that works for me:

test.md:

# Test

This is just an example of a Markdown for my first ever Github comment. 
I hope it can help some people.

test.stories.mdx:

import { Meta, Description } from '@storybook/addon-docs/blocks';
import test from './test.md';

<Meta title="Test"/>

<Description>{test}</Description>

I hope this is helps.

shilman commented 4 years ago

Thanks @Hicksmix! Was just about to try this on a project and really glad to hear that it works.

Sidnioulz commented 4 years ago

Great to hear the workarounds are workable. I think we can close this as an issue.

Just for completeness sake, you can also add a rule to the webpack config (as long as it doesn't conflict with another loader as in the case with raw-loader:

{
  test: /\.md$/, // or any other extension we would like to import
  use: ['babel-loader', '@mdx-js/loader'],
},

Building up on this, if you want to import .md files in the MDX loader by adding a rule to your webpackFile export in main.js, you'll need to filter out the original rule for .md files, like so:

    webpackFinal: async config => {
        config.module.rules = [ {
            test: /\.md$/,
            use: ['babel-loader', '@mdx-js/loader'],
        }, ...config.module.rules.filter(rule => rule.test.source !== '\\.md$')]

        return config
    },

Note that importing .md files and rendering them as MDX seems to be the only way to have coloured source code blocks in Storybook. Using <Description ... /> or <Markdown>...</Markdown> will result in unstyled source code blocks.

shilman commented 4 years ago

@Sidnioulz what about Source blocks?

Sidnioulz commented 4 years ago

@shilman the issue with those is that I'd need to take the string from the raw-loader and manually extract code blocks from my Markdown. I did this at some point and loaded my code samples into CodeOrSourceMdx blocks, but I couldn't find how to display my mixed Markdown and JSX content.

I could've insisted and found some way to export my content but it felt silly that I had to parse MD manually when the MDX loader does such a great job of turning MD content into JSX.

awacode21 commented 4 years ago

I tried to use the workaround mentioned by @atanasster

import { Meta, Markdown } from '@storybook/addon-docs/blocks';
import Readme from '../../README.md';

<Meta title="General|Introduction" />

<Markdown>{Readme}</Markdown>

But i always get this error when trying to serve storybook:

ERROR in ./README.md 1:0
Module parse failed: Unexpected character '#' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> # WIP: Introduction to the IBE UI Library
|
| This library projects purpose is to consolitate generic, reusable components in one project for better maintainability and provide them as NPM package to be imported and used through all IBE projects/mono-repos.
 @ ./src/storybook/introduction.stories.mdx 10:0-37 34:5-11
 @ ./src/storybook sync nonrecursive ^\.\/(?:(?:introduction\.stories\.mdx)$)$
 @ ./config/storybook/generated-entry.js
 @ multi ./node_modules/@storybook/core/dist/server/common/polyfills.js ./node_modules/@storybook/core/dist/server/preview/globals.js ./node_modules/@storybook/addon-docs/dist/frameworks/common/config.js ./node_modules/@storybook/addon-docs/dist/frameworks/vue/config.js ./node_modules/@storybook/addon-knobs/dist/preset/addDecorator.js ./config/storybook/generated-entry.js (webpack)-hot-middleware/client.js?reload=true&quiet=true

Shouldn't storybook out of the box be able to load those .md files?

I installed Storybook via vue-cli-plugin-storybook

shilman commented 4 years ago

cc @mrmckeb

awacode21 commented 4 years ago

This is not only a problem in mdx files this seems to be also a problem for CSF Format files. Here i get the same error while trying to import an .md file! So it seems that the preconfigured loader for normal markdown files does not do it's job

shilman commented 4 years ago

@awacode21 the preconfigured loader for markdown files probably needs to be updated. But we have not done that because it's a breaking change and we haven't done a major release since this issue was filed. However, we're working on a major release right now (6.0) so it's finally a good time to update it.

thany commented 4 years ago

@Sidnioulz

Building up on this, if you want to import .md files in the MDX loader by adding a rule to your webpackFile export in main.js, you'll need to filter out the original rule for .md files, like so:

  webpackFinal: async config => {
      config.module.rules = [ {
          test: /\.md$/,
          use: ['babel-loader', '@mdx-js/loader'],
      }, ...config.module.rules.filter(rule => rule.test.source !== '\\.md$')]

      return config
  },

Note that importing .md files and rendering them as MDX seems to be the only way to have coloured source code blocks in Storybook. Using <Description ... /> or <Markdown>...</Markdown> will result in unstyled source code blocks.

I tried this, but I'm getting an error while building, when importing an MD file:

ERROR in ./README.md 10:9
Module parse failed: Unexpected token (10:9)
File was processed with these loaders:
 * ./node_modules/@mdx-js/loader/index.js
You may need an additional loader to handle the result of these loaders.
| const makeShortcode = name => function MDXDefaultShortcode(props) {
|   console.warn("Component " + name + " was not imported, exported, or provided by MDXProvider as global scope")
>   return <div {...props}/>
| };
|

So how do you get it to work? It seems a slight bit more configuration is needed in order for this to work properly. I'm on SB 5.3.14, btw. I'll try updating to latest, but I doubt this will fix it.

/edit: And indeed updating does not fix this.

Sidnioulz commented 4 years ago

@thany below is my whole Webpack config, as you can see, nothing else than what I've posted should be involved when it comes to loading MD files. I'm using @mdx-js/loader and @mdx-js/mdx 1.5.5 and 1.5.7 according to my yarn.lock (not sure which version is used, thus). My SB is 5.3.10.

const webpackConfig = require('../webpack.config')

...

    webpackFinal: async config => {
        config.resolve.extensions = Array.from(
            new Set([...config.resolve.extensions, ...webpackConfig.resolve.extensions])
        )
        config.resolve.modules = Array.from(new Set([...config.resolve.modules, ...webpackConfig.resolve.modules]))

        config.module.rules = [
            {
                test: /\.md$/,
                use: ['babel-loader', '@mdx-js/loader'],
            },
            ...config.module.rules.filter(rule => rule.test.source !== '\\.md$'),
        ]

        return config
    },

Also, my MD files actually refer to documentation without stories. They are pure Markdown content. Is that the case for you too?

thany commented 4 years ago

@Sidnioulz Your config is a bit off as compared to the documentation, but functionally yours and mine are the same. But no worries, after some more digging, I found this documentation on MDX in Webpack, saying that it also requires some presets to be added in the .babelrc, which I didn't have yet.

So my .babelrc is now:

{
  "presets": ["@babel/env", "@babel/react"],
  "plugins": [
    "babel-plugin-styled-components"
  ]
}

And that makes it work!

Thanks for the help πŸ‘πŸ» This seems like a fine workaround until SB 6.0 is released 😎

Sidnioulz commented 4 years ago

Good job @thany, I'd forgotten about that! I also use those two presets.

chrisj-skinner commented 4 years ago

I am unable to get any of the above solutions to work in my angular storybook 6.0.0-beta.16. Has anyone got something workable within an angular storybook?

sunjain1 commented 4 years ago

When I try to do installation using- https://github.com/storybookjs/storybook/tree/master/addons/docs#installation on a react project, I get error as- image

sunjain1 commented 4 years ago

any update on the issue?

shilman commented 4 years ago

Slotted for 6.0, hopefully in the next week or two

shilman commented 4 years ago

Β‘Ay Caramba!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.0.0-beta.38 containing PR #11333 that references this issue. Upgrade today to try it out!

You can find this prerelease on the @next NPM tag.

Closing this issue. Please re-open if you think there's still more to do.

nrakochy commented 4 years ago

Can you clarify what the expected Api is here for Storybook 6? Do I need to add the extra opts in the main.js file to get the markdown transcluded?

Reading through the code I would have thought that .md could now be imported with zero-config, but not sure where I am going wrong.

Here's an example of a docs-only component from a create-react-app that was initialized with npx sb init

import Welcome from "../README.md";
import { Meta, Markdown, Description } from "@storybook/addon-docs/blocks";

<Meta title="Welcome to the Docs" />

<h1>As a component</h1>
<Welcome/>  //=> Does not load

<h1>Markdown</h1>

{Welcome} //=> Renders directly without interpolation (e.g. shows as {Welcome})

<h1>Not markdown</h1>

<Description>{Welcome}</Description> //=> Works
gptt916 commented 3 years ago

@shilman Thank you for your work on this, I also have the same question as @nrakochy, what does usage with the new change in storyboard 6.0 look like? I wasn't able to find any documentation when searching for transcludeMarkdown.

williamliangwl commented 3 years ago

Not sure if you guys have successfully used this feature. What I did to get my MD files to be embeded, is by doing the followings:

Storybook version: 6.1.21

.storybook/main.js

addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    // add the followings
    {
      name: "@storybook/addon-docs",
      options: { transcludeMarkdown: true },
    },
],

I followed the example from here https://github.com/storybookjs/storybook/pull/11334/files

OmarZeidan commented 2 years ago

Thanks @williamliangwl πŸŽ‰ πŸŽ‰

That was smooth indeed. Not sure if that has any other hidden impacts but working so far.


// your-story.mdx

import ChangeLog from "./CHANGELOG.md";

<ChangeLog />
gadingnst commented 2 years ago

Thanks @williamliangwl πŸŽ‰ πŸŽ‰

That was smooth indeed. Not sure if that has any other hidden impacts but working so far.

// your-story.mdx

import ChangeLog from "./CHANGELOG.md";

<ChangeLog />

I've tried it in version 6.4.21. Build success, but it displays Cannot get / if I start Storybook server.

hadagarcia commented 1 year ago

Thanks @williamliangwl πŸŽ‰ πŸŽ‰ That was smooth indeed. Not sure if that has any other hidden impacts but working so far.

// your-story.mdx

import ChangeLog from "./CHANGELOG.md";

<ChangeLog />

I've tried it in version 6.4.21. Build success, but it displays Cannot get / if I start Storybook server.

This code was working. After upgrading to Storybook 7 (I think that was the issue), this didn't work anymore. Reading the Documentaiton, it seems like we need to use another approach.

This was my previous code: MarkdownToJS_Before

This change worked now: MarkdownToJS_Issue

Here is the link for the Storybook documentation: https://storybook.js.org/docs/react/api/doc-block-markdown

dgateles commented 1 year ago

Still doesn't work on StB 7.3. Receiving this error:

WARN 🚨 Unable to index ./src/components/alc-alert/stories/alc-alert.mdx:
WARN   Error: Could not parse import/exports with acorn: SyntaxError: Unexpected token
WARN     at /Users/dgateles/Documents/alcance/node_modules/@storybook/core-server/dist/index.js:60:2277
WARN     at async Promise.all (index 0)
WARN     at async Promise.all (index 0)
WARN     at async StoryIndexGenerator.updateExtracted (/Users/dgateles/Documents/alcance/node_modules/@storybook/core-server/dist/index.js:60:1684)
WARN     at async StoryIndexGenerator.ensureExtracted (/Users/dgateles/Documents/alcance/node_modules/@storybook/core-server/dist/index.js:60:2641)
WARN     at async StoryIndexGenerator.initialize (/Users/dgateles/Documents/alcance/node_modules/@storybook/core-server/dist/index.js:60:1611)

alc-alert.mdx:

{/* alc-alert.mdx */}

import { Canvas, Meta, Controls, Unstyled } from '@storybook/blocks';

import * as AlertStories from './alc-alert.stories';

import AlertProps from './alc-alert.props.mdx':

<Meta of={AlertStories} />

----

<AlertProps/>