Я разрабатываю свой веб-сайт с помощью Gatsby v5, и в настоящее время я борюсь с проблемой GraphQL.
Я использую статический запрос GraphQL для извлечения openGraphImageUrl из некоторых репозиториев GitHub и отображения каждого в компоненте карты. Чтобы иметь больший контроль над изображениями, я написал преобразователь, который загружает файлы за openGraphImageUrl и добавляет их как узлы File в слой данных GraphQL, чтобы я мог использовать их с компонентом <GatsbyImage>
.
Этот подход обычно работает, я могу создать веб-сайт, а статический запрос предоставляет информацию из репозитория. Резолвер правильно добавляет узел image
с загруженным файлом, который я могу использовать с <GatsbyImage>
, как и ожидалось (см. далее).
Проблема, с которой я сталкиваюсь, заключается в следующем сообщении об ошибке, которое появляется только тогда, когда я вношу изменения на страницу, а затем сохраняю ее (например, index.tsx) после успешного запуска gatsby develop
, но не возникает при изменении отдельных компонентов, которые не страницы (например, code.tsx — см. ниже):
Отсутствует обработчик onError для вызова «схема построения», ошибка была «Ошибка: схема должна содержать типы с уникальными именами, но содержит несколько типов с именами «Файл».
Это происходит, когда шаг building schema
запускается снова (инициируется сохранением изменений на страницах), и процесс сборки зависает после возникновения ошибки.
Я искал часы подряд, чтобы выяснить проблему, и я также консультировался с
Сообщение об ошибке предполагает, что тип "Файл" переопределен, я просто не понимаю, почему это происходит и что я делаю неправильно. Как избежать переопределения типа «Файл» в схеме? Моя установка выглядит следующим образом.
Здесь я настроил доступ к GitHub GraphQL API:
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
});
import type { GatsbyConfig } from "gatsby";
const config: GatsbyConfig = {
graphqlTypegen: true,
plugins: [
{
resolve: "gatsby-source-graphql",
options: {
typeName: "GitHub",
fieldName: "github",
url: "https://api.github.com/graphql",
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
},
fetchOptions: {},
},
},
//... skipping the rest
],
};
export default config;
Преобразователь создается, как показано ниже. На узле GitHub_Repository
он добавляет узел image
типа File
и загружает данные изображения из репозитория openGraphImageUrl. Затем он сохраняет его как временный файл, считывает большой двоичный объект в буфер, а затем создает фактический узел из буфера, вызывая createFileNodeFromBuffer()
.
const fs = require("fs");
const path = require("path");
const fetch = require("node-fetch");
const { createFileNodeFromBuffer } = require("gatsby-source-filesystem");
exports.createResolvers = async ({
actions: { createNode },
createNodeId,
cache,
store,
createResolvers,
}) => {
const resolvers = {
GitHub_Repository: {
image: {
type: "File",
resolve: async (source) => {
const imageUrl = source.openGraphImageUrl;
const imageNodeId = createNodeId(imageUrl);
const response = await fetch(imageUrl);
const buffer = await response.buffer();
const dirPath = path.join(process.cwd(), "public", "tmp");
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
const filePath = path.join(
dirPath,
imageNodeId,
);
fs.writeFileSync(filePath, buffer);
const fileNode = await createFileNodeFromBuffer({
buffer,
store,
cache,
createNode,
createNodeId,
name: imageNodeId,
});
return fileNode;
},
},
},
};
createResolvers(resolvers);
};
Вот как выглядит запрос:
function QueryGitHubRepositories(): Repository[] {
const data: Data = useStaticQuery(graphql`
{
github {
viewer {
pinnedItems(first: 6, types: REPOSITORY) {
nodes {
... on GitHub_Repository {
id
name
url
openGraphImageUrl
image {
childImageSharp {
gatsbyImageData(layout: FIXED, width: 336, height: 168)
}
}
}
}
}
}
}
}
`);
return data.github.viewer.pinnedItems.nodes.map((node) => node);
}
Вот остальная часть связанного кода для полноты картины.
Я определил следующие типы для метода запроса (также в code.tsx):
type Repository = {
name?: string | null;
url?: string | null;
openGraphImageUrl?: string | null;
image?: {
childImageSharp: {
gatsbyImageData?: any | null;
};
} | null;
};
type Data = {
github: {
viewer: {
pinnedItems: {
nodes: Repository[];
};
};
};
};
Данные запроса используются здесь для построения раздела кода с карточками репозитория (также в code.tsx):
import * as React from "react";
import { graphql, useStaticQuery } from "gatsby";
import { GatsbyImage } from "gatsby-plugin-image";
//skipping type definitions and query here, since they're already shown above
const RepositoryCard = ({ repository }: { repository: Repository }) => {
const imageItem = repository.image?.childImageSharp.gatsbyImageData ?? "";
const altText = repository.name ?? "repository";
return (
<div>
<a href = {repository.url ?? ""} target = "_blank">
<div className = "flex h-fit flex-col">
<GatsbyImage image = {imageItem} alt = {altText} />
</div>
</a>
</div>
);
};
const CodeSection = () => {
const repositories: Repository[] = QueryGitHubRepositories();
return (
<div className = "w-full">
<div className = "flex flex-col">
{repositories.map((repository) => (
<RepositoryCard key = {repository.name} repository = {repository} />
))}
</div>
</div>
);
};
export default CodeSection;
Я также пробовал разные реализации и просмотрел две записи в блоге Пола Скэнлона о добавлении данных в слой данных Gatsby GraphQL и изменении типов данных Gatsby GraphQL с помощью createSchemaCustomization. Однако мне не удалось заставить это работать должным образом, вероятно, потому, что в его сообщениях в блоге узлы добавляются без каких-либо исходных плагинов, а я использую gatsby-plugin-graphql
.
Предложения по альтернативным реализациям приветствуются.
🤔 А знаете ли вы, что...
С Node.js можно легко создавать RESTful API с использованием фреймворков, таких как Express.js.
Я нашел рабочее решение. Мне просто пришлось заменить gatsby-source-graphql
на gatsby-source-github-api
, потому что первый, по-видимому, не поддерживает инкрементные сборки:
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
});
import type { GatsbyConfig } from "gatsby";
const config: GatsbyConfig = {
graphqlTypegen: true,
plugins: [
{
resolve: `gatsby-source-github-api`,
options: {
url: "https://api.github.com/graphql",
token: `${process.env.GITHUB_TOKEN}`,
graphQLQuery: `
query{
user(login: "someUserName") {
pinnedItems(first: 6, types: [REPOSITORY]) {
edges {
node {
... on Repository {
name
openGraphImageUrl
}
}
}
}
}
}`
}
},
//... skipping the rest
],
};
export default config;
const fs = require("fs");
const path = require("path");
const fetch = require("node-fetch");
const { createFileNodeFromBuffer } = require("gatsby-source-filesystem");
exports.createResolvers = async ({
actions: { createNode },
createNodeId,
cache,
store,
createResolvers,
}) => {
const resolvers = {
GithubDataDataUserPinnedItemsEdgesNode: {
image: {
type: "File",
resolve: async (source) => {
const imageUrl = source.openGraphImageUrl;
const imageNodeId = createNodeId(imageUrl);
const response = await fetch(imageUrl);
const buffer = await response.buffer();
const dirPath = path.join(process.cwd(), "public", "tmp");
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
const filePath = path.join(
dirPath,
imageNodeId,
);
fs.writeFileSync(filePath, buffer);
const fileNode = await createFileNodeFromBuffer({
buffer,
store,
cache,
createNode,
createNodeId,
name: imageNodeId,
});
return fileNode;
},
},
},
};
createResolvers(resolvers);
};
type Repository = {
openGraphImageUrl?: string | null;
image?: {
childImageSharp?: {
gatsbyImageData?: any | null;
} | null;
} | null;
};
type Data = {
githubData: {
data: {
user: {
pinnedItems: {
edges: {
node: Repository;
}[];
};
};
};
};
};
function QueryGitHubRepositories(): Repository[] {
const data: Data = useStaticQuery(graphql`
{
githubData {
data {
user {
pinnedItems {
edges {
node {
openGraphImageUrl
image {
childImageSharp {
gatsbyImageData
}
}
}
}
}
}
}
}
}
`);
const repos = data.githubData.data.user.pinnedItems.edges.map(
(edge) => edge.node
);
return repos;
}