首页 > 解决方案 > 使用 Gatsby 的 Markdown 帖子中的特色图像的绝对路径

问题描述

我已经按照 Gatsby 教程在 Markdown 帖子和页面中处理图像,它运行良好,但我想要实现的是从静态位置获取图像,而不是使用图像的相对路径。

想参考这样的图像(在frontmatter中)

featuredImage: img/IMG_20190621_112048_2.jpg

IMG_20190621_112048_2.jpg位于/src/data/img不是与markdown文件相同的目录下 /src/posts

我试过这样设置gatsby-source-filesystem

{
  resolve: `gatsby-source-filesystem`,
  options: {
    name: `posts`,
    path: `${__dirname}/src/posts`,
  },
},
{
  resolve: `gatsby-source-filesystem`,
  options: {
    name: `data`,
    path: `${__dirname}/src/data/`,
  },
},

但 post 模板中的 graphQL 查询失败:

export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
        featuredImage {
          childImageSharp {
            fluid(maxWidth: 800) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
  }

GraphQL 错误字段“featuredImage”不能有选择,因为“字符串”类型没有子字段。

知道如何从与 post markdown 目录不同的位置获取图像吗?

标签: graphqlgatsby

解决方案


在 Gatsby 中实现这一点曾经非常麻烦,但是由于新的createSchemaCustomizationNode API文档(自 Gatsby 2.5 起),它相对容易。

这是一个演示,我在其中复制了您的 repo 结构:github

这是相关代码所在的位置:github

这是使其工作的代码:

// gatsby-node.js

const path = require('path')

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

  createFieldExtension({
    name: 'fileByDataPath',
    extend: () => ({
      resolve: function (src, args, context, info) {
        const partialPath = src.featureImage
          if (!partialPath) {
            return null
          }

        const filePath = path.join(__dirname, 'src/data', partialPath)
        const fileNode = context.nodeModel.runQuery({
          firstOnly: true,
          type: 'File',
          query: {
            filter: {
              absolutePath: {
                eq: filePath
              }
            }
          }
        })

        if (!fileNode) {
          return null
        }

        return fileNode
      }
    })
  })

  const typeDefs = `
    type Frontmatter @infer {
      featureImage: File @fileByDataPath
    }

    type MarkdownRemark implements Node @infer {
      frontmatter: Frontmatter
    }
  `

  createTypes(typeDefs)
}

这个怎么运作:

这有两个部分:

  1. 扩展markdownRemark.frontmatter.featureImage,因此 graphql 解析为 File 节点而不是字符串 viacreateTypes
  2. @fileByDataPath通过创建一个新的字段扩展createFieldExtension

创建类型

现在盖茨比的推断frontmatter.featureImage是一个字符串。我们将要求 Gatsby 将 featureImage 读取为字符串,方法是修改其父类型:

  type Frontmatter {
    featureImage: File
  }

然而,这还不够,我们还需要将此Frontmatter类型传递给它的父类:

  type Frontmatter {
    featureImage: File
  }

  type MarkdownRemark implements Node {
    frontmatter: Frontmatter
  }

我们还将添加@infer标签,让 Gatsby 知道它可以推断这些类型的其他字段,即frontmatter.title,markdownRemark.html等。

然后将这些自定义类型传递给createTypes

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

  const typeDefs = `
    type Frontmatter @infer {
      featureImage: File
    }

    type MarkdownRemark implements Node @infer {
      frontmatter: Frontmatter
    }
  `

  createTypes(typeDefs)
}

现在,我们可以启动localhost:8000/___graphql并尝试查询图像

query Post {
  markdownRemark {
    frontmatter {
      featureImage {
        id
      }
    }
  }
}

我们得到...

错误:不能为不可为空的字段 File.id 返回 null。

这是因为虽然 Gatsby 现在知道featureImage应该是一个 File 节点,但它不知道从哪里获取该文件。

此时,我们既可以使用createResolvers手动将字段解析为 File 节点,也createFileExtension可以执行相同的操作。我选择createFileExtension它是因为它允许更多的代码重用(您可以扩展任何字段),而createResolvers在这种情况下,对于特定字段更有用。看到你想要的只是从src/data目录中解析一个文件,我将调用这个扩展名fieldByDataPath

创建文件扩展

让我们看一下resolve属性。它是一个接受以下内容的函数:

  • source:父字段的数据(本例中为frontmatter
  • featureImageargs:在查询中传递给的参数。我们不需要这个
  • context: contains nodeModel,我们将使用它从 Gatsby 节点存储中获取节点
  • 信息:关于该字段的元数据 + 整个架构

我们将从 中找到原始路径 ( img/photo.jpg) src.featureImage,然后将其粘贴到src/data以获得完整的绝对路径。接下来,我们查询 nodeModel 以找到具有匹配绝对路径的 File 节点。由于您已经指向gatsby-source-filesystemsrc/data因此图像 (photo.jpg) 将在 Gatsby 节点存储中。

如果我们找不到路径或匹配的节点,请返回null

  resolve: async function (src, args, context) {
    // look up original string, i.e img/photo.jpg
    const partialPath = src.featureImage
      if (!partialPath) {
        return null
      }

    // get the absolute path of the image file in the filesystem
    const filePath = path.join(__dirname, 'src/data', partialPath)
    
    // look for a node with matching path
    const fileNode = await context.nodeModel.runQuery({
      firstOnly: true,
      type: 'File',
      query: {
        filter: {
          absolutePath: {
            eq: filePath
          }
        }
      }
    })

    // no node? return
    if (!fileNode) {
      return null
    }

    // else return the node
    return fileNode
  }

我们已经完成了 99% 的工作。最后要做的是移动 this 以将此解析函数传递给createFieldExtension; 以及将新扩展添加到 createTypes

createFieldExtension({
  name: 'fileByDataPath' // we'll use it in createTypes as `@fileByDataPath`
  extend: () => ({
    resolve,             // the resolve function above
  })
})

const typeDef = `
  type Frontmatter @infer {
    featureImage: File @fileByDataPath // <---
  }
  ...
`

有了它,您现在可以使用src/data/frontmatter 中的相对路径。

额外的

实现方式fileByDataPath,它只适用于名为featureImage. 这不是太有用,所以我们应该修改它,使其适用于任何字段,例如,名称以_data;结尾的字段。或者至少接受要处理的字段名称列表。

Edit手头有一点时间,所以我写了一个插件来做这个,还写了一个博客

编辑 2 Gatsby 此后进行了runQuery异步(2020 年 7 月),更新了答案以反映这一点。


推荐阅读