Gatsby v5 и GraphQL: «Ошибка: схема должна содержать типы с уникальными именами, но содержит несколько типов с именами «Файл».»

Я разрабатываю свой веб-сайт с помощью Gatsby v5, и в настоящее время я борюсь с проблемой GraphQL.

Проблема

Я использую статический запрос GraphQL для извлечения openGraphImageUrl из некоторых репозиториев GitHub и отображения каждого в компоненте карты. Чтобы иметь больший контроль над изображениями, я написал преобразователь, который загружает файлы за openGraphImageUrl и добавляет их как узлы File в слой данных GraphQL, чтобы я мог использовать их с компонентом <GatsbyImage>.

Этот подход обычно работает, я могу создать веб-сайт, а статический запрос предоставляет информацию из репозитория. Резолвер правильно добавляет узел image с загруженным файлом, который я могу использовать с <GatsbyImage>, как и ожидалось (см. далее).

Проблема, с которой я сталкиваюсь, заключается в следующем сообщении об ошибке, которое появляется только тогда, когда я вношу изменения на страницу, а затем сохраняю ее (например, index.tsx) после успешного запуска gatsby develop, но не возникает при изменении отдельных компонентов, которые не страницы (например, code.tsx — см. ниже):

Отсутствует обработчик onError для вызова «схема построения», ошибка была «Ошибка: схема должна содержать типы с уникальными именами, но содержит несколько типов с именами «Файл».

Это происходит, когда шаг building schema запускается снова (инициируется сохранением изменений на страницах), и процесс сборки зависает после возникновения ошибки.

Я искал часы подряд, чтобы выяснить проблему, и я также консультировался с

Сообщение об ошибке предполагает, что тип "Файл" переопределен, я просто не понимаю, почему это происходит и что я делаю неправильно. Как избежать переопределения типа «Файл» в схеме? Моя установка выглядит следующим образом.

Настраивать

gatsby-config.ts

Здесь я настроил доступ к 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;

gatsby-node.js

Преобразователь создается, как показано ниже. На узле 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);
};

код.tsx

Вот как выглядит запрос:

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);
}

TL;DR

Вот остальная часть связанного кода для полноты картины.

code.tsx (продолжение)

Я определил следующие типы для метода запроса (также в 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.

Предложения по альтернативным реализациям приветствуются.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
91
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я нашел рабочее решение. Мне просто пришлось заменить gatsby-source-graphql на gatsby-source-github-api, потому что первый, по-видимому, не поддерживает инкрементные сборки:

gatsby-config.ts

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;

gatsby-node.js

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);
};

код.tsx

Определения типов

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;
}

Другие вопросы по теме