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.

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

🤔 А знаете ли вы, что...
С Node.js можно легко создавать RESTful API с использованием фреймворков, таких как Express.js.


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