过去在 Gatsby 中实现这一点相当麻烦,但感谢新的 createSchemaCustomization Node 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
}
}
}
}
我们得到...
错误:不能为不可为空的字段 File.id 返回 null。
这是因为虽然 Gatsby 现在知道 featureImage 应该是一个文件节点,但它不知道从哪里获取该文件。
此时,我们既可以使用createResolvers 手动将字段解析为文件节点,也可以使用createFileExtension 来做同样的事情。我选择createFileExtension 是因为它允许更多代码重用(您可以扩展任何字段),而在这种情况下,createResolvers 对于特定字段更有用。看到你想要的只是从src/data目录解析一个文件,我将这个扩展称为fieldByDataPath。
创建文件扩展
让我们看看 resolve 属性。它是一个接受以下内容的函数:
- 来源:父字段的数据(本例为
frontmatter)
- args:在查询中传递给
featureImage 的参数。我们不需要这个
- context:包含
nodeModel,我们将使用它从 Gatsby 节点存储中获取节点
- 信息:关于此字段的元数据 + 整个架构
我们会从src.featureImage中找到原始路径(img/photo.jpg),然后将其粘到src/data,得到一个完整的绝对路径。接下来,我们查询 nodeModel 以找到具有匹配绝对路径的 File 节点。由于您已经将gatsby-source-filesystem 指向src/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% 的工作。最后要做的是移动它以将此解析函数传递给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 结尾的字段;或者至少接受要处理的字段名称列表。
编辑我手头有一点时间,所以我wrote a plugin 这样做了,还有wrote a blog on it。
编辑 2 此后,Gatsby 使 runQuery 异步(2020 年 7 月),更新了答案以反映这一点。