在带有 Gatsby 的 Markdown post 中使用特色图片的绝对路径

Use absolute path for featured image in markdown post with Gatsby

我遵循了 Working With Images in Markdown Posts and Pages 的 Gatsby 教程,效果很好,但我想要实现的是从静态位置获取图像,而不是使用图像的相对路径。

想参考这样的图片(在前面)

featuredImage: img/IMG_20190621_112048_2.jpg

其中 IMG_20190621_112048_2.jpg/src/data/img 而不是与 /src/posts

下的 markdown 文件相同的目录

我试过这样设置 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 Error Field "featuredImage" must not have a selection since type "String" has no subfields.

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

在您的服务器架构中,您可能已将 featuredImage 变量声明为字符串,而在您的客户端 graphql 查询中,您正试图调用 featuredImage 变量的子对象,而该子对象不存在。

您可能需要检查 graphql 架构定义并将查询与架构定义对齐

您当前的架构可能是这样的

featuredImage: String

并且您需要根据服务器端的要求通过声明正确的类型来更改它。

有关 graphql 类型的更多信息。请参考这个 url - https://graphql.org/learn/schema/#object-types-and-fields

谢谢

瑞金奥姆曼

在 Gatsby 中实现这个曾经很麻烦,但是多亏了新的 createSchemaCustomization 节点 API docs(自 Gatsby 2.5 ) 比较容易。

这是我复制您的回购结构的演示: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 通过 createTypes
  2. 解析为文件节点而不是字符串
  3. 通过createFieldExtension
  4. 创建新字段扩展@fileByDataPath

创建类型

现在 Gatsby 将 frontmatter.featureImage 推断为一个字符串。我们将要求 Gatsby 通过修改其父类型来将 featureImage 读取为字符串:

  type Frontmatter {
    featureImage: File
  }

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

  type Frontmatter {
    featureImage: File
  }

  type MarkdownRemark implements Node {
    frontmatter: Frontmatter
  }

我们还将添加 @infer 标签,让 Gatsby 知道它可以推断这些类型的其他字段,即 frontmatter.titlemarkdownRemark.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
      }
    }
  }
}

我们得到...

Error: Cannot return null for non-nullable field File.id.

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

此时,我们可以使用 createResolvers 手动将字段解析为 File 节点,或者使用 createFileExtension 来做同样的事情。我选择 createFileExtension 是因为它允许更多的代码重用(您可以扩展任何字段),而 createResolvers 在这种情况下对特定字段更有用。鉴于您只想从 src/data 目录中解析一个文件,我将此扩展名为 fieldByDataPath.

创建文件扩展

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

  • source:父字段的数据(本例中为frontmatter
  • args:在查询中传递给 featureImage 的参数。我们不需要这个
  • 上下文:包含 nodeModel,我们将使用它从 Gatsby 节点存储中获取节点
  • 信息:有关此字段的元数据 + 整个模式

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

如果找不到路径或匹配节点,return 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% 的工作。最后要做的就是移动这个把这个resolve函数传给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 // <---
  }
  ...
`

这样,您现在可以在 frontmatter 中使用来自 src/data/ 的相对路径。

额外

fileByDataPath 的实施方式,仅适用于名为 featureImage 的字段。这不是很有用,所以我们应该修改它,以便它可以在任何字段上工作,比如说,其名称以 _data 结尾;或者至少接受要处理的字段名称列表。

Edit 有点时间,所以我 wrote a plugin that does this & also wrote a blog on it.

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

除了允许在任何地方使用任何类型的资产(声音、视频、gpx 等)的 Derek Answer 之外,如果只寻找图像的解决方案,可以使用:

https://www.gatsbyjs.org/packages/gatsby-remark-relative-images/