在带有 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)
}
工作原理:
这个有两部分:
- 扩展
markdownRemark.frontmatter.featureImage
以便 graphql 通过 createTypes
解析为文件节点而不是字符串
- 通过
createFieldExtension
创建新字段扩展@fileByDataPath
创建类型
现在 Gatsby 将 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
}
}
}
}
我们得到...
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/
我遵循了 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
我试过这样设置 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)
}
工作原理:
这个有两部分:
- 扩展
markdownRemark.frontmatter.featureImage
以便 graphql 通过createTypes
解析为文件节点而不是字符串
- 通过
createFieldExtension
创建新字段扩展
@fileByDataPath
创建类型
现在 Gatsby 将 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
}
}
}
}
我们得到...
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/