ChristopherBiscardi / gatsby-mdx

Gatsby+MDX • Transformers, CMS UI Extensions, and Ecosystem Components for ambitious projects
https://gatsby-mdx.netlify.com/
714 stars 101 forks source link

Improve the Docs #210

Closed ChristopherBiscardi closed 5 years ago

ChristopherBiscardi commented 5 years ago

The docs aren't nearly useful enough for the level of usage we're starting to see. Please use this thread to list any issues you've have learning to use gatsby-mdx as I start rewriting the docs and filing documentation issues for anyone to pick up.

This includes bugs, confusing errors, and other issues that just need to be documented better

rchrdnsh commented 5 years ago

I get confused reading the docs just how to get started. A nice, simple, step-by-step tutorial on just getting it set up would be really nice :-)

danoc commented 5 years ago

Hi Chris! A few ideas:

Thanks! Looking forward to seeing it.

ChristopherBiscardi commented 5 years ago

Another use case that needs to be documented:

How to use MDX via Contentful for fields that aren't the "core content type"

ChristopherBiscardi commented 5 years ago

Interactive Code Component docs for #211

ChristopherBiscardi commented 5 years ago

Include a page on the types of releases we have (stable, ci tag, dev)

LekoArts commented 5 years ago

https://gatsby-mdx.netlify.com/guides/programmatically-creating-pages

This whole page is and was a black-box to me. Where does componentWithMDXScope, scope, code scope come from. Why do I have to use that?

ChristopherBiscardi commented 5 years ago

starting to track improvements in #216

ChristopherBiscardi commented 5 years ago

interesting changes: componentWithMDXScope was removed on master.

hagnerd commented 5 years ago

Would love to help improve the docs a bit. It looks like Programmatically Creating Pages needs to be updated (since componetnWithMDXScope is a noop now)?

ChristopherBiscardi commented 5 years ago

@hagnerd I would love to accept a PR for that :)

hagnerd commented 5 years ago

@rchrdnsh is there anything in specific with gatsby-mdx that you would like more/better documentation on?

ChristopherBiscardi commented 5 years ago

Need to write docs for

https://github.com/ChristopherBiscardi/gatsby-mdx/issues/238

hagnerd commented 5 years ago

@ChristopherBiscardi how do feel about a Guided Tour of gatsby-mdx? Sort of like the Gatsby tutorial where it increases in how advanced the topics are but building onto a set of example projects.

  1. Starting with a new gatsby site and adding support for mdx pages (in src/pages)
  2. How to import react components
  3. How to apply a layout to a single mdx file (in the mdx file, then in a separate js file)
  4. How to configure webpack + gatsby for easier import
  5. How to add default layouts
  6. How to query for data inside MDX
  7. How to programmatically create pages (how to pass props through MDXRender, maybe some stuff about passing components in through scope?)
  8. How to transition from gatsby-transfromer-remark to gatsby-mdx(supporting .md and .mdx extensions in gatsby-config)
  9. gotchas, other advanced topics, etc.
rchrdnsh commented 5 years ago

Hi @hagnerd! I'm trying to set up programmatically creating pages and generating a list of said pages, similar to how it's done in the official Gatsby tutorial. I barely was able to keep up with my understanding from the official tutorial, but I was able to make it work, but the tutorial about that on the Gatsby-mdx page just leaves me completely lost. It might be nice to do a compare and contrast with what is similar and what is different between the vanilla Gatsby way and the mdx way, and to also update that page as well, maybe?

@dschau is actually helping me out with this over zoom, so when it's done and I have a better understanding I might be able to help out as well. He is showing me how to use async and await in gatsby-node and we got so far as to make the pages work from .mdx files, but haven't yet got react components to work in them and we also have not been able to create a list of pages yet, so those are next on the list, I think.

hagnerd commented 5 years ago

@rchrdnsh [gatsby-mdx.netlify.com]() hasn't been updated yet, but I did work on the guide (with edits from Chris) for Programmatically Creating Pages.

If you want to check out the new page, I can see if I can throw it up on Netlify quick, otherwise you can clone master, run yarn, cd into examples/docs, and run gatsby develop to see it locally.

ChristopherBiscardi commented 5 years ago

yeah, the builds are failing on a sharp error for some reason. tried blowing the cache but it's still happening. Need a second to dig in.

2:25:39 PM:   Error: Cannot find module '../build/Release/sharp.node'
2:25:39 PM:   
2:25:39 PM:   - v8-compile-cache.js:159 require
2:25:39 PM:     [repo]/[v8-compile-cache]/v8-compile-cache.js:159:20
2:25:39 PM:   
2:25:39 PM:   - constructor.js:10 Object.<anonymous>
2:25:39 PM:     [repo]/[gatsby-plugin-sharp]/[sharp]/lib/constructor.js:10:15
2:25:39 PM:   
2:25:39 PM:   - v8-compile-cache.js:178 Module._compile
2:25:39 PM:     [repo]/[v8-compile-cache]/v8-compile-cache.js:178:30
2:25:39 PM:   
2:25:39 PM:   - v8-compile-cache.js:159 require
2:25:39 PM:     [repo]/[v8-compile-cache]/v8-compile-cache.js:159:20
2:25:39 PM:   
2:25:39 PM:   - index.js:3 Object.<anonymous>
2:25:39 PM:     [repo]/[gatsby-plugin-sharp]/[sharp]/lib/index.js:3:15
2:25:39 PM:   
2:25:39 PM:   - v8-compile-cache.js:178 Module._compile
2:25:39 PM:     [repo]/[v8-compile-cache]/v8-compile-cache.js:178:30
2:25:39 PM:   
2:25:39 PM:   - v8-compile-cache.js:159 require
2:25:39 PM:     [repo]/[v8-compile-cache]/v8-compile-cache.js:159:20
2:25:39 PM:   
2:25:39 PM:   - index.js:7 Object.<anonymous>
2:25:39 PM:     [repo]/[gatsby-plugin-sharp]/index.js:7:15
2:25:39 PM:   
2:25:39 PM:   - v8-compile-cache.js:178 Module._compile
2:25:39 PM:     [repo]/[v8-compile-cache]/v8-compile-cache.js:178:30
DSchau commented 5 years ago

@ChristopherBiscardi yikes, that's the worst. I've seen that error a bunch in Netlify, and clearing the cache has always worked. You could try bumping the yarn version (setting YARN_VERSION=1.13.0 in Netlify build/env variables) has oftentimes helped, as well.

ChristopherBiscardi commented 5 years ago

@DSchau Thanks for the YARN_VERSION suggestion, bumping that worked.

rchrdnsh commented 5 years ago

that would be awesome @hagnerd :-)

ChristopherBiscardi commented 5 years ago

@hagnerd sounds great. Want to make a new directory in the docs under src/pages/guides/guided-tour/* for a Guided Tour? `

4. How to configure webpack + gatsby for easier import

Let's skip this one. Most people import locally to MDX files and I don't want to encourage webpack aliasing if it's not critical.

  1. How to query for data inside MDX

And how to access props like frontmatter/pageContext maybe?

  1. How to programmatically create pages (how to pass props through MDXRender, maybe some stuff about passing components in through scope?)

Yeah, building this up from components on an MDXRenderer through to a wrapRootElement MDXProvider would be dope (similar to the way you built up layouts).

  1. How to transition from gatsby-transfromer-remark to gatsby-mdx(supporting .md and .mdx extensions in gatsby-config)

Transitioning from remark to mdx feels like its own guide rather than an extension of this one. Would include some content comparing remark plugins to gatsby-remark plugins etc

  1. gotchas, other advanced topics, etc.

Last page could be a pointer to other advanced guides

rchrdnsh commented 5 years ago

hmmmmm....can mdx support Tables of Contents?

ChristopherBiscardi commented 5 years ago

@rchrdnsh That is the tableOfContents field in the graphql schema. It should already work and you can render it however you want.

rchrdnsh commented 5 years ago

yay, awesome! XD

rchrdnsh commented 5 years ago

hmmmm...is there any documentation on how to use tableOfContents ? Can't seem to figure it out or find any supporting info...for example, if I query it like so inside a page query:

export const query = graphql`
  query GetMdxTemplate($slug: String!) {
    mdx(fields: { slug: { eq: $slug }}) {
      tableOfContents
      code {
        scope
        body
      }
      frontmatter {
        title
        subtitle
        date(formatString: "MMMM Do, YYYY")
      }
    }
  }
`

How would I then render it in my template?

export default ({ data }) => {
  const post = data.mdx
  return (
    <>
      <MainContent>
        <div>
          <H1>{post.frontmatter.title}</H1>
          <h2>{post.frontmatter.subtitle}</h2>
          <h3>{post.frontmatter.date}</h3>
          {/* <p>{post.tableOfContents}</p> ??? */}
          <MDXRenderer>{post.code.body}</MDXRenderer>
        </div>
      </MainContent>
    </>
  )
}

Not worried about the CSS, just the right way to implement it in JSX.

DSchau commented 5 years ago

@rchrdnsh it's just data, so you'll want to implement it however you would any other piece of data.

Also note this is extremely tangential to mdx itself. This is moreso using GraphQL data in a React component, rather than an MDX documentation issue.

In this example, you would just display the title, then iterate over the items in the table of contents, e.g.

export default ({ data }) => {
  const { frontmatter, code, tableOfContents } = data.mdx
  return (
    <>
      <MainContent>
        <div>
          <H1>{frontmatter.title}</H1>
          <h2>{frontmatter.subtitle}</h2>
          <h3>{frontmatter.date}</h3>
          {tableOfContents && (
            <ul>
              {tableOfContents.items.map(item => (
                <li key={item.title}>{item.label}</li>
              ))}
            </ul>
          )}
          <MDXRenderer>{code.body}</MDXRenderer>
        </div>
      </MainContent>
    </>
  )
}

I don't know what the structure is off hand, but I know it returns a JS structure containing an array, so you'd use something like the above.

hagnerd commented 5 years ago

@rchrdnsh I suggest popping open GraphiQL and checking out the structure for what your query will return. Opening up GraphiQL on a quick MDX demo the return sort of looks like this.

image

Table of contents items can be nested according to sub-headings so you'll have to iterate through the array recursively unless you can guarantee the depth.

hagnerd commented 5 years ago

@rchrdnsh just to reiterate, the shape of the data returned when querying table of contents will be dependent on the headings, and sub headings you used in your MDX files.

rchrdnsh commented 5 years ago

hmmmm...ok, so I kind of understand so far, but how do I map recursively through all of my headings and sub-headings? I would also want to use custom styling for each level of headings so I'm thinking that a <ul> with <li>'s might not be the correct approach? dunno myself.

ChristopherBiscardi commented 5 years ago

Considering the following example:

import React from "react";
import ReactDOM from "react-dom";

const tableOfContents = {
  items: [
    { url: "#1", title: "one", items: [{ url: "#1a", title: "one-a" }] },
    {
      url: "#2",
      title: "two",
      items: [
        { url: "#2a", title: "two-a" },
        {
          url: "#2b",
          title: "two-b",
          items: [{ url: "#2b-a", title: "two-b-a" }]
        }
      ]
    }
  ]
};
function App() {
  return (
    <div>
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      {tableOfContents && <ul>{tableOfContents.items.map(renderItem)}</ul>}
    </div>
  );
}

const renderItem = item => (
  <li key={item.title}>
    {item.items ? (
      <ul>
        <a href={item.url}>{item.title}</a>
        {item.items.map(renderItem)}
      </ul>
    ) : (
      <a href={item.url}>{item.title}</a>
    )}
  </li>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

You will end up with a structure like

image


Recursive algorithms are best built as a single function that handles one step of the process. In this case, it's renderItem which handles rendering one item, and then calls itself again to render the next item OR returns the final value.

That is: renderItem takes an item. It then decides whether or not the item has any child items. If it does, then we set up the containing elements and call renderItem again, if not, then it is the last item and we render and return.

You can choose to style this via just CSS, or you can use different react elements for each level if you want to

rchrdnsh commented 5 years ago

ok, this seems to be working now, for the most part, thank you @DSchau and @hagnerd and @ChristopherBiscardi! The last piece of the puzzle in terms of functionality is for each anchor in the TOC to actually jump to that point in the document, which it does not currently do. Right now the url changes according to the hash route, but the document does not jump to that point.

In terms of styling, I think I also need to figure out how to style recursively now...do any of y’allz know of a technique to make the font-size of li elements recursively smaller with every nested level? Can plain CSS do that? Do I need CSS-in-JS for something like that? Never tried to do something like that before 🤔

hagnerd commented 5 years ago

@rchrdnsh if you pop open your inspector, it looks like by default headings do not include the inner text as an id. So the table of contents is aware of the headings, but is not ready out of the box to be used as id links.

To fix that either the default heading components provided by gatsby-mdx need to include the id, or you can provide your own custom headings via <MDXProvider>

Using <MDXProvider> in gatsby-ssr and gatsby-browser (this page shows the basics) you can pass in your own headings that handle the logic of generating an id.

As for making nested lis smaller, you can definitely do it with plain css but you'll have to handle each possible level of nesting, and manually set sizes. (There is only six possible levels of nesting).

Pop open your web developer tools on a page with a nested table of contents and write css selectors to match the shape of a nest.

rchrdnsh commented 5 years ago

hi @hagnerd, so I ended up doing something like this and it worked, but it feels soooooo wrong...

  ul li a {
    color: blue;
  }

  ul li ul li a {
    color: green;
  }

  ul li ul li ul li a {
    color: coral;
  }

  ul li ul li ul li ul li a {
    color: black;
  }

  ul li ul li ul li ul li ul li a {
    color: grey;
  }

  ul li ul li ul li ul li ul li ul li a {
    color: purple;

going to investigate if there are any CSS-in-JS solutions that allow for actual recursive styling...somehting along the lines of "make even successive level 10% percent smaller" or something like that...

and @ChristopherBiscardi, would it possible to add the ability to generate ids for headings in Gatsby-mdx so that the TOC can be used to navigate through the doc? Gonna try and figure it out with the MDXProvider, but that kind of thing is pretty far over my head at this point. I can check back in if I figure anything out, if you are interested.

and I dunno if it goes without saying, but @DSchau, @ChristopherBiscardi and @hagnerd...

...you are all wizards to me!

XD

ChristopherBiscardi commented 5 years ago

@rchrdnsh

Here's a revised version of that codesandbox that has two variations of LevelComponent, one uses the passed-through level number to set the padding and the other uses a completely different component based on what level it's on.

You can also accomplish this sort of this with plain CSS using nested relative units like em, but IMO this way is easier for people to understand as it is more explicit about what's happening.

ChristopherBiscardi commented 5 years ago

would it possible to add the ability to generate ids for headings in Gatsby-mdx so that the TOC can be used to navigate through the doc

you can replace the headings for this (h1-6)

const components = {
  h1: props => <h1 id={slugify(props.children)} {...props}/>
}
<MDXProvider components={components}>{children}</MDXProvider>
rchrdnsh commented 5 years ago

hmmmm...would I wrap the <TOC> with the <MDXProvider>? Where would that go in my app?

I have this:

export default ({ data }) => {
  const { frontmatter, code, tableOfContents } = data.mdx
  return (
    <>
      <TOC>
        {tableOfContents && <ul>{tableOfContents.items.map(renderItem)}</ul>}
      </TOC>
      <MainContent>
        <div>
          <h1>{frontmatter.title}</h1>
          <h2>{frontmatter.subtitle}</h2>
          <h3>{frontmatter.date}</h3>
          <MDXRenderer>{code.body}</MDXRenderer>
        </div>
      </MainContent>
    </>
  )
}

would I do this? Or is this completely not right?


export default ({ data }) => {
  const { frontmatter, code, tableOfContents } = data.mdx
  return (
    <>
      <TOC>
        <MDXProvider components={components}>
          {tableOfContents && <ul>{tableOfContents.items.map(renderItem)}</ul>}
        </MDXProvider>
      </TOC>
      <MainContent>
        <div>
          <h1>{frontmatter.title}</h1>
          <h2>{frontmatter.subtitle}</h2>
          <h3>{frontmatter.date}</h3>
          <MDXRenderer>{code.body}</MDXRenderer>
        </div>
      </MainContent>
    </>
  )
}

sorry, just not grokking how to use the `<MDXProvider>` component :-(
ChristopherBiscardi commented 5 years ago

you could put the MDXProvider anywhere that is "above" the markdown content being rendered. Since there is no markdown content in the TOC, MDXProvider would do nothing there.

I usually put a single MDXProvider in wrapRootElement like

export const wrapRootElement = ({ element }) => {
  return (
    <MDXProvider components={components}>
      {element}
    </MDXProvider>
  )
}

by putting it around the root of the site, you don't have to worry about targeting anywhere MDX content is rendered.

rchrdnsh commented 5 years ago

hmmmm, so do I wrap this around the thing I want affected or do I put it in front of the thing I want affected?

so I have this now, higher up in the file:

const components = {
  h1: props => <h1 id={slugify(props.children)} {...props}/>
}

and this as the rendered element:

export default ({ data }) => {
  const { frontmatter, code, tableOfContents } = data.mdx
  return (
    <>
      <TOC>
        {tableOfContents && <ul>{tableOfContents.items.map(renderItem)}</ul>}
      </TOC>
      <MainContent>
        <div>
          <H1>{frontmatter.title}</H1>
          <h2>{frontmatter.subtitle}</h2>
          <h3>{frontmatter.date}</h3>
          <MDXProvider components={components}>{children}</MDXProvider>
          <MDXRenderer>{code.body}</MDXRenderer>
        </div>
      </MainContent>
    </>
  )
}

and what I want then is just for the <MDXProvider> to affect the <MDXRenderer>, or so I think...but this is not working and I am getting the following errors in the console:

/Users/rchrdnsh/Dropbox/Code/Gatsby/mdx-blog/src/templates/article.js
  41:16  warning  Headings must have content and the content must be accessible by a screen reader  jsx-a11y/heading-has-content
  41:24  error    'slugify' is not defined                                                          no-undef
  82:49  error    'children' is not defined                                                         no-undef

✖ 3 problems (2 errors, 1 warning)

 @ ./.cache/sync-requires.js 13:56-136
 @ ./.cache/app.js
 @ multi ./node_modules/react-hot-loader/patch.js (webpack)-hot-middleware/client.js?path=/__webpack_hmr&reload=true&overlay=false ./.cache/app
ℹ 「wdm」: Failed to compile.

so, I am still a bit confused...

should <MDXRenderer> be the {children}? Should I be wrapping it? If so, then how would I go about doing this? Should I be importing slugify? What is slugify?

ChristopherBiscardi commented 5 years ago

MDXProvider communicates with MDXRenderer via context. Any MDXRenderer needs to be a child of the provider. It doesn't matter how far away it is, but it needs to be a child.

slugify is some random function that turns your text into a slug. https://github.com/Flet/github-slugger would work, for example. you'd have to import it or write it yourself.

<MDXProvider>
  ...anything
  <MDXRenderer>{body}</MDXRenderer>
  ... anything
<MDXProvider>

In your example, this would work.

export default ({ data }) => {
  const { frontmatter, code, tableOfContents } = data.mdx
  return (
    <MDXProvider components={components}>
      <TOC>
        {tableOfContents && <ul>{tableOfContents.items.map(renderItem)}</ul>}
      </TOC>
      <MainContent>
        <div>
          <H1>{frontmatter.title}</H1>
          <h2>{frontmatter.subtitle}</h2>
          <h3>{frontmatter.date}</h3>
          <MDXRenderer>{code.body}</MDXRenderer>
        </div>
      </MainContent>
    </MDXProvider>
  )
}

or if you do this in wrapRootElement it will wrap the entire site so that any MDXRenderer uses the components you put in.

rchrdnsh commented 5 years ago

ok, I think I got this working ok :-)

Getting odd warning messages that don't make much sense, as the headings have both an id and content, like so:


/Users/rchrdnsh/Dropbox/Code/Gatsby/mdx-blog/src/templates/article.js
  42:16  warning  Headings must have content and the content must be accessible by a screen reader  jsx-a11y/heading-has-content
  43:16  warning  Headings must have content and the content must be accessible by a screen reader  jsx-a11y/heading-has-content
  44:16  warning  Headings must have content and the content must be accessible by a screen reader  jsx-a11y/heading-has-content
  45:16  warning  Headings must have content and the content must be accessible by a screen reader  jsx-a11y/heading-has-content
  46:16  warning  Headings must have content and the content must be accessible by a screen reader  jsx-a11y/heading-has-content
  47:16  warning  Headings must have content and the content must be accessible by a screen reader  jsx-a11y/heading-has-content

✖ 6 problems (0 errors, 6 warnings)

You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.
⚠ 「wdm」:
ℹ 「wdm」: Compiled with warnings.

but otherwise it's working ok :-)

liltechnomancer commented 5 years ago

Should this code block

export const pageQuery = graphql`
  query BlogPostQuery($id: String) {
    mdx(id: { id: $id }) {
      id
      frontmatter {
        title
      }
      code {
        body
      }
    }
  }
`;

From the following page https://gatsby-mdx.netlify.com/guides/programmatically-creating-pages read mdx(id: { eq: $id }) { instead? I was getting the following error Field "id" is not defined by type mdxIdQueryString_2; Did you mean in?

ChristopherBiscardi commented 5 years ago

That's an eslint rule that is explained at https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/9924d037ed7b40f9681d16f429e4b21bb0b7a568/docs/rules/heading-has-content.md

ChristopherBiscardi commented 5 years ago

@lvrbrtsn yes it should be id: { eq: $id }

liltechnomancer commented 5 years ago

@ChristopherBiscardi cool I made a PR : )

dashed commented 5 years ago

One thing I'd like to see some docs on is testing mdx-based components.

I tried to look at the tests in this repo; and I wasn't sure how to mirror it into a gatsby project.

ChristopherBiscardi commented 5 years ago

@dashed Can you expand on what you mean by "testing MDX based components"? Do you mean testing components you pass into MDXProvider?

dashed commented 5 years ago

@ChristopherBiscardi Essentially jest snapshot tests on .mdx files.

thundernixon commented 5 years ago

I had no idea that Gatsby MDX automatically injected imports until Googling for awhile and finally finding this comment in an MDXjs GitHub issue.

This is an awesome feature! But I totally didn't realize it was there, so I spent a bunch of time worrying how I'd explain imports to less-technical markdown contributors.

Potentially, this could go into Programmatically Creating Pages with a new section such as "A Quick Example of a React Component in an MDX File."

ChristopherBiscardi commented 5 years ago

Yes, that's currently documented at /api-reference/options/global-scope but it could be useful to have a guide that mentions it. Using React Components as Shortcodes or something like that.

Note that if you don't use globalScope, then you still have to import the components.

raulrpearson commented 5 years ago

I've started a Gatsby/MDX site and wanted to do something like this:

<!-- index.mdx -->
import { graphql } from 'gatsby';
export const pageQuery = graphql`
  query {
    site {
      siteMetadata {
        title
        description
      }
    }
  }
`;

# ${props.data.site.siteMetadata.title}

${props.data.site.siteMetadata.description}

It doesn't work very well. Prettier insists on escaping those $ signs. If I escape them, I end up with a backslash in the generated HTML. If I don't escape them, I end up with the literal property reference string instead of the actual value.

I'm assuming that this will eventually be documented here.

Not sure is this is the place to post this question, just let me know if it isn't. If there're any parts of the documentation I could help out with as a beginner, let me know.

johno commented 5 years ago

@raulrpearson: you can achieve this by adding a JSX block (MDX doesn't support inline interpolation):

import { graphql } from 'gatsby';
export const pageQuery = graphql`
  query {
    site {
      siteMetadata {
        title
        description
      }
    }
  }
`;

# <span>{props.data.site.siteMetadata.title}</span>

<Box>
  {props.data.site.siteMetadata.description}
</Box>