(Gatsby) 如何在 MDX 组件中将图像源作为道具传递

(Gatsby) How to pass image source as a prop in MDX component

我正在尝试创建一个 Figure 组件,我在其中传递 img src 和其他数据。

我意识到这并不简单——例如,——但我认为它可以与普通的 HTML img 标签一起工作。

图像未显示。这是否意味着 this limitation 也适用于组件内的 HTML img 标签?

如果是这样的话,我想我确实应该使用Gatsby的动态图像。我将如何在静态查询(非页面组件)中执行此操作? This thread 让我相信这是不可能的——或者至少是 hack?

MDX文档中的组件:

<Figure 
  image=""
  size=""
  caption=""
  credits=""
/>

Figure.js:

import * as React from "react"

const Figure = ({ image, size, caption, credits }) => {
  return (
    <figure className={size}>
      <img src={image} alt={caption} />
      <figcaption>
        <span>{caption}</span>
        <span>{credits}</span>
      </figcaption>
    </figure>
  )
}

export default Figure

articlePostTemplate.js

import * as React from "react"
import { graphql } from "gatsby"
import { MDXRenderer } from "gatsby-plugin-mdx"
import Layout from "../components/layout.js"
import Seo from "../components/seo.js"

const PostTemplate = ({ data, location }) => {
  let post = data.mdx

  return (
    <Layout location={location}>
      <Seo
        title={post.frontmatter.title}
        description={post.frontmatter.lead}
        date={post.frontmatter.computerDate}
      />
      <article className="post">
        <header className="post" id="intro">
          <p className="date">
            <time dateTime={post.frontmatter.computerDate}>{post.frontmatter.humanDate}</time>
          </p>
          <h1 itemprop="headline">{post.frontmatter.title}</h1>
          <p className="lead">{post.frontmatter.lead}</p>
        </header>
        <section className="post" id="body-text">
          <MDXRenderer data={data}>{post.body}</MDXRenderer>
        </section>
      </article>
    </Layout>
  )
}

export default PostTemplate

export const pageQuery = graphql`
  query PostBySlug(
    $id: String!
  ) {
    site {
      siteMetadata {
        title
      }
    }
    mdx(id: { eq: $id }) {
      id
      excerpt(pruneLength: 160)
      body
      frontmatter {
        title
        computerDate: date(formatString: "YYYY-MM-DD")
        humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
        hook
        type
        lead
        featuredImage {
          childImageSharp {
            fluid(maxWidth: 800) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
  }
`

gatsby-config.js

module.exports = {
  …
  },
  plugins: [
    `gatsby-plugin-image`,
    `gatsby-plugin-sitemap`,
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        extensions: [`.md`, `.mdx`],
        gatsbyRemarkPlugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: `900000000000`,
              linkImagesToOriginal: false,
              backgroundColor: `none`,
            },
          },
          {
            resolve: `gatsby-remark-responsive-iframe`,
            options: {
              wrapperStyle: `margin-bottom: 1.07var(--line-length)`,
            },
          },
          `gatsby-remark-prismjs`,
          `gatsby-remark-copy-linked-files`,
          `gatsby-remark-smartypants`,
          {
            resolve: `gatsby-remark-autolink-headers`,
              options: {
                icon: false,
                itemprop: `heading`,
                maintainCase: false,
                removeAccents: true,
                elements: [`h2`, `h3`, `h4`],
              },
          }
        ],
      },
    },
    …
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `content`,
        path: `${__dirname}/content/`,
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/data`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages/`,
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `journalistikk`,
        path: `${__dirname}/content/journalism/`,
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `discussion`,
        path: `${__dirname}/content/discussion/`,
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `photography`,
        path: `${__dirname}/content/photography/`,
      }
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Gatsby Starter Blog`,
        short_name: `GatsbyJS`,
        start_url: `/`,
        background_color: `#ffffff`,
        display: `minimal-ui`,
        icon: `src/images/gatsby-icon.png`,
      },
    },
    `gatsby-plugin-react-helmet`,
  ],
}

gatsby-node.js

const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions

  const articlePostTemplate = path.resolve(`./src/templates/articlePostTemplate.js`)

  const result = await graphql(
    `
      {
        allMdx(
          sort: { fields: [frontmatter___date], order: ASC }
          limit: 1000
        ) {
          nodes {
            id
            frontmatter {
              title
              computerDate: date(formatString: "YYYY-MM-DD")
              humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
            }
            fields {
              slug
            }
          }
        }
      }
    `
  )

  if (result.errors) {
    reporter.panicOnBuild(
      `There was an error loading your blog posts`,
      result.errors
    )
    return
  }

  const posts = result.data.allMdx.nodes

  if (posts.length > 0) {
    posts.forEach((post, index) => {
      [index + 1].id

      createPage({
        path: post.fields.slug,
        component: articlePostTemplate,
        context: {
          id: post.id
        },
      })
    })
  }
}

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `Mdx`) {
    const value = createFilePath({ node, getNode })

    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

exports.createSchemaCustomization = ({ actions }) => {
  const { createTypes } = actions

 
  createTypes(`
    type SiteSiteMetadata {
      author: Author
      siteUrl: String
      social: Social
    }

    type Author {
      name: String
      summary: String
    }

    type Social {
      twitter: String
      instagram: String
      mail: String
    }

    type MarkdownRemark implements Node {
      frontmatter: Frontmatter
      fields: Fields
    }

    type Frontmatter {
      title: String
      description:  String
      date: Date @dateformat
    }

    type Fields {
      slug: String
    }
  `)
}

layout.js

import * as React from "react"
import { MDXProvider } from "@mdx-js/react"
import { Link } from "gatsby"
import DWChart from "react-datawrapper-chart"
import Header from "./Header"
import Footer from "./Footer"
import Figure from "./Figure"

const shortcodes = { Link, DWChart, Figure }

export default function Layout({ children }) {
  return (
    <div className="layout-wrapper">
      <Header />
      <main>
        <MDXProvider components={shortcodes}>{children}</MDXProvider>
      </main>
      <Footer />
    </div>
  )
}

您在 中提到的限制仅适用于 StaticImage + 动态 props 数据(具有动态源),这意味着 returns StaticImage 无法接收 src 作为 props,因为 StaticImage 的所有 props 都需要静态分析。

换句话说,您的 MDX 可以接收一个 src 用于 <img> 标签,或者如果配置正确,您可以使用 GatsbyImage 组件。

请记住,将获取 GatsbyImage 数据(childImageSharpgatsbyImageData 等)的查询必须放在 top-level 组件(页面)中,如果如果在别处使用,则使用 page query or in a useStaticQuery 钩子。

两种情况下的方法(使用 <img>GatsbyImage)是相似的。您需要:

  • 在 markdown 文件中提供图片来源

  • 使用页面查询或 useStaticQuery 查询这些图像以向您的 MDXProvider 提供查询图像 props。使用博客示例:

      <MDXProvider>
        <MDXRenderer data={data}/>
      </MDXProvider>
    

    data代表你在GraphQL页面查询中查询的数据或者useStaticQuery。在不知道您的数据结构的情况下,我没有添加 GraphQL 部分,因为我不知道可用的节点。

  • 此时,您的 MDXProvider 保存了图像数据(全部来自您的 markdown 文件),您只需将其提供给您的 GatsbyImageFigure 组成部分:

    // some.mdx 
    
    import Figure from 'path/to/figure/component';
    
    ## Post Body
    
    Lorem ipsum dolor...
    
    <Figure img={props.data} />
    

    如您所见,我将所有道具提升到 Figure 只是为了允许调试(例如使用 console.logs())以查看那里可用的道具,例如:

    const Figure = (props) => {
       console.log(props);
       return (
         <div>I will be a figure in a future</div>
       )
    }
    
    export default Figure
    

    这样,您将能够传递给 Figure 类似 img={props.someNode.frontmatter.imageNodeSource} 的内容。

    再一次,不知道你的数据结构就像天上掉馅饼,但明白了。

Ferran 致敬以获得有用的指导。

经过更多研究,我修改了我的解决方案—

articleTemplate.js

/* shortcodes */

const ArticleTemplate = ({ data, location }) => {
  let post = data.mdx

  return (
    <Layout location={location}>
      <Seo
        title={post.frontmatter.title}
        description={post.frontmatter.lead}
        date={post.frontmatter.computerDate}
      />
      <article className="article">
        <p className="date">
          <time dateTime={post.frontmatter.computerDate}>{post.frontmatter.humanDate}</time>
        </p>
        <h1 itemprop="headline">{post.frontmatter.title}</h1>
        <p className="lead" itemprop="introduction">{post.frontmatter.lead}</p>
        <MDXProvider components={shortcodes}>
          <MDXRenderer
            data={post.frontmatter.thumbnail}
            localImages={post.frontmatter.embeddedImagesLocal}
          >
            {post.body}
          </MDXRenderer>
        </MDXProvider>
      </article>
    </Layout>
  )
}

export default ArticleTemplate

export const pageQuery = graphql`
  query ArticleBySlug($id: String!) {
    site {
      siteMetadata {
        title
      }
    }
    mdx(id: {eq: $id}) {
      id
      excerpt(pruneLength: 160)
      body
      frontmatter {
        title
        computerDate: date(formatString: "YYYY-MM-DD")
        humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
        hook
        type
        lead
        thumbnail {
          childImageSharp {
            gatsbyImageData(
              layout: FULL_WIDTH
            )
          }
        }
        embeddedImagesLocal {
          childImageSharp {
            gatsbyImageData
          }
        }
      }
    }
  }
`

figure.js

import * as React from "react"
import { GatsbyImage, getImage } from 'gatsby-plugin-image'

const Figure = ({ source, size, caption, credit }) => {
  return (
    <figure className={size}>
      <GatsbyImage image={getImage(source)} alt={caption} />
      <figcaption>
        <span>{caption}</span>
        <span>{credit}</span>
      </figcaption>
    </figure>
  );
}

export default Figure

index.mdx

---
…
thumbnail: "thumb.jpeg"
embeddedImagesLocal:
  - "first.jpeg"
  - "second.jpeg"
---

<Figure
  source={(props.localImages [0])} <!-- first image; second image would be `[1]` -->
  size="I'm a `className`"
  caption="I'm a caption"
  photographer="I'm a name."
/>

(将此标记为解决方案,因为它对将来希望这样做的人最有帮助。它还展示了如何同时查询嵌入图像和特色图像。)