使用Next.js的Github包搜索应用程序(附代码)

151 阅读4分钟

使用Next.js的Github包搜索应用

开发一个简单的搜索应用,在这里我们可以一次性获得选择软件包所需的所有信息。

每天作为开发的一部分,我们会在Github中用google搜索开源包。这将是一个耗时的过程,因为我们需要搜索关键词,进入每个Github仓库检查以下项目来选择软件包:

  • 它是完全开源的,可以改变它或用于商业应用?
  • 它是否没有被归档,或者是否有一些活跃的社区在为它工作?
  • 它是否有很多问题?
  • 这个资源库中使用的是什么语言?
  • 包的大小是多少,所以它不会使我的项目变得更庞大?
  • 最近的更新是什么时候发生的?

我想开发一个简单的搜索应用程序,在那里我们可以一次性获得选择软件包所需的所有信息。它也将帮助任何开发团队作为一个方便的工具来选择Github包。

Github提供了一个搜索API来搜索仓库的搜索词,对于每一个 repo,都可以得到详细的信息,如分支、标签和贡献者,并以单一视图显示。我们选择Next.js,这样我们可以在后台搜索,在客户端只渲染内容。它提供了一个免费的部署工具来进行部署,并且可以公开使用

Next.js的概况

Next.js是react.js框架的服务器端渲染,它支持我们创建单页网络应用。它拥有开发生产就绪的网络应用程序所需的一切--生产型SSR Web UI应用。混合静态和服务器渲染,支持TypeScript,智能捆绑,路由预取,等等。要了解更多关于Next.js的信息,请查看他们的官方网站。

项目设置

前提条件是要有node.js 12.2.0或更高版本。

我们可以通过一个简单的命令创建支持 TypeScript的Next应用程序。

npx create-next-app@latest --ts

要在一个现有的项目中开始,在根文件夹中创建一个空的tsconfig.json文件。

touch tsconfig.json

项目开始

然后,运行next(通常是npm run dev或yarn dev),Next.js会引导你安装所需的软件包,完成设置。

npm run dev

安装Chakra UI

我使用了Chakra UI,以便更快地准备使用组件来构建用户界面。它提供了Box和Stack布局组件,通过props可以很容易地对你的组件进行造型。

npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6

搜索应用程序。

其动机是提供搜索关键词,并获得Github中可用的存储库列表,并提供以下所有的基本信息。

Next.js应用程序以index.tsx页面开始。在这里面,我创建了服务器端的props函数,它将负责在页面到达客户端之前调用服务器来获取页面的数据。在这个函数中,我们使用了Github搜索库 的API。搜索文本在用户界面中给出,响应中的所需信息被提取到 repoItem 模型中。

JavaScript

export const getServerSideProps = async ({
  query,
}: GetServerSidePropsContext<QueryParams>): Promise<GetStaticPropsResult<ContentPageProps>> => {
  console.log("context.query :>> ", query);
 
  if (!query || !query?.q) {
    return { props: { repos: [], searchText: "", count: 0 } };
  }
  const searchText: string = `${query.q}`;
  const url = `https://api.github.com/search/repositories?q=${query?.q}`;
  const token: RequestInit = {
    headers: [
      ["Authorization", `${process.env.token}`],
      ["Accept", "application/vnd.github.v3+json"]
    ],
    method: "GET",
  };
  const res = await fetch(url, token);
  const data = await res.json();
  // console.log('data :>> ', data);
  const repos: RepoItem[] = data.items.map((item: any) => {
    const repo = {
      id: item.id,
      language: item.language,
      name: item.name,
      full_name: item.full_name,
      description: item.description,
      stars: item.stargazers_count,
      watchers: item.watchers_count,
      forks: item.forks,
      homepage: item.homepage,
      topics: item.topics,
      tags: item.tags_url,
      size:item.size,
      issue: item.open_issues_count,
      contributors: item.contributors_url,
      archived: item.archived,
      visibility: item.visibility,
      updated_at: item.updated_at,
      license: item.license,
      owner: item.owner,
      has_wiki: item.has_wiki
    };
    return repo;
  });
  const results = { repos: repos, searchText: searchText, count: data.total_count || 0 };
  return { props: results };
};

搜索框是一个表单组件,我们允许用户输入搜索文本,并在提交表单时,将查询字符串发送到后台。后台是servicesideprops,它将反过来触发Github API,以获得搜索关键词的匹配库(如上所述)。

有一点需要注意的是,我没有使用onchange,因为它将在每次按键时触发。相反,我使用了useRef,以便在提交按钮时获得输入值。

搜索结果将使用自定义组件 - RepoContainer来显示 VStack布局 组件下的每个RepoItem数据。

JavaScript

<VStack>
  <Spacer />
  {repos && repos.map((repoItem: RepoItem) => <RepoContainer key={repoItem.id} repo={repoItem} />)}
</VStack>

每个RepoItem由四个堆栈组件组成,在下面的代码片断中用注释标记。第一个堆栈组件包括版本库名称、全名和语言细节。在第二个堆栈中,元细节,如标签、分支、包的大小、许可证和所有者被保留。第三个堆栈,版本库是如何稳定和积极更新的,第四个堆栈显示版本库社区的受欢迎程度。

JavaScript

<Box
      color={useColorModeValue("gray.700", "gray.200")}
      w={"100%"}
      borderRadius={10}
      bg="#B1F1F3"
    >
      <Container as={Stack} maxW={"9xl"} py={10}>
        <SimpleGrid templateColumns={{ sm: "1fr 1fr", md: "2fr 2fr 2fr 2fr" }} spacing={3}>
          <Stack>{/* First stack component */}
            <ListHeader>{repo?.name}</ListHeader>
            <Text fontSize={"sm"}>
              <NextLink href={`https://github.com/${repo?.full_name}`} passHref>
                <Link color="blue">{repo?.full_name}</Link>
              </NextLink>
              </Text>
            <Stack direction={"row"} mt={10}>
              <Text fontSize={"sm"}>
                Language:{" "}
                <Badge colorScheme="red" variant="solid">
                  {repo.language}
                </Badge>
              </Text>
            </Stack>
            <Stack direction={"row"}>frameworks :</Stack>
          </Stack>

          <Stack align={"flex-start"} fontSize={"sm"}>{/* Second stack component */}
            <Stack direction={"row"} align={"center"}>
              {/* <Text>Releases  </Text> */}
              <GoTag />  <Tags url={repo.tags} />
              <GoGitBranch /> <Branches repo={repoName} /> 
            </Stack>
            <Text>Size of the package : {Math.round((repo.size / 32768) * 100) / 100} MB </Text>
            <Text>License : {repo.license?.name ? repo.license?.name:"None"}  </Text>
            <Text>Owner : <Avatar src={repo?.owner?.avatar_url} size='xs' /> {repo?.owner?.login}</Text>
          </Stack>
          <Stack align={"flex-start"} fontSize={"sm"}>{/* Third stack component */}
            <Stack direction={"row"} align={"center"}>
              <GoIssueOpened /> <Text>Issues :{repo.issue} |</Text>
              <GoOrganization /> <Contributors url={repo.contributors} icon={<GoOrganization />} />
            </Stack>
            <Stack direction={"row"} align={"center"}>
              <GoArchive />
              <Text style={{ color: repo.archived ? 'red': 'black'}}> IsArchived: {repo.archived ? "true" : "false"} |</Text>
              <GoEye />
              <Text> Visibility: {repo.visibility}</Text>
            </Stack>
            <Stack direction={"row"} align={"center"}>
              <GoWatch /> <Text>Last Updated : {updatedTime}</Text>
            </Stack>
            <Stack direction={"row"} align={"center"}>
              <GoGitPullRequest />  <PR repo={repoName} />
            </Stack>
          </Stack>
          <Stack align={"flex-start"}>{/* Fourth stack component */}
            <Stack direction={"row"} align={"center"}>
              <GoStar /> <Text>{repo.stars} |</Text> <GoRepoForked /> <Text>{repo.forks} |</Text>
              <GoEye /> <Text>{repo.watchers} </Text>
            </Stack>
            <NextLink href={`${repo.homepage}`} passHref>
              <Link color="blue">Homepage</Link>
            </NextLink>
            {/* <Text>Examples</Text> */}
            {repo.has_wiki ? (
              <NextLink href={`https://github.com/${repo?.full_name}/wiki`} passHref>
                <Link color="blue">Wiki Link</Link>
              </NextLink>
            ): ""}
          </Stack>
        </SimpleGrid>
        <SimpleGrid>{/* Repo description */}
          <Text>Description :{repo.description}</Text>
        </SimpleGrid>
        <SimpleGrid>{/* Repo Topics */}
          <Stack direction={"row"} align={"center"}>
            <Wrap>
              {repo.topics &&
                repo.topics.map((res: string) => (
                  <>
                    <WrapItem key={res}>
                      <Badge variant="solid" colorScheme="messenger" >
                        {res}
                      </Badge>
                    </WrapItem>
                  </>
                ))}
            </Wrap>
          </Stack>
        </SimpleGrid>
      </Container>
    </Box>

最后,描述和Github主题被保留下来以了解项目的情况。

如果你敏锐地注意到,有像Branches,和Prs这样的组件,它反应了自定义组件。一旦 repo 容器被加载,它将被异步渲染。

让我们仔细看看PR组件;它返回一个带有pr计数的div标签。在搜索仓库的调用中,PR计数是不能直接使用的。要获得PR计数还需要一个调用,所以我们使用API路由。这个API路由又用令牌调用Github的API,获得拉动请求的计数。这个计数计算的负载已经被委托给了后端。

这里reponame是通过props传递给组件的,计数状态在获取API调用后被解析。

JavaScript

interface propTypes {
  repo: string;
}

function PR(props: propTypes) {
  const { repo } = props;
  const [count, setCount] = useState(0);
  useEffect(() => {
      if (repo) {
        fetch(`/api/prcounts?repo=${repo}`).then((res) => res.json()).then(res => {
          const prcount = res.data;
          setCount(prcount);
        });
      } else {
        setCount(0);
      }
  }, [repo])
  return (
    <div className='center'>   Pull Request : {count}</div>
  )
}

export default PR

在pages/api/pr.ts文件中,我们调用Github API调用,获得拉动请求,并找到拉动请求的计数。然后,拉动请求的值将以 JSON格式返回给前端,如{data: prcount}。

TypeScript

type ResponseData = {
  data: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
    const repoName: string = `${req.query.repo}`;
    const url = new URL(`https://api.github.com/repos/${repoName}/pulls`);
    const options = { 'headers' : {
        'Authorization': process.env.token,
        'Accept': "application/vnd.github.v3+json",
        'User-Agent': 'Mozilla/5.0' 
     },
    'method': 'GET',
    };

    const clientreq: ClientRequest = https.request(url, options, (apiresponse: IncomingMessage) => {
        let respJson: string = "";

        apiresponse.on('data', (chunk: string) => { 
          respJson += chunk;
        });

        apiresponse.on('end', () => {
          if (apiresponse.statusCode === 200) {
            let prResponses = JSON.parse(respJson);
            const prCounts = prResponses.length;
            res.status(200).json({'data': prCounts});
          }
        })
    });

    clientreq.on('error', (e: Error) => {
        console.error('error message', e.message);
    });
    clientreq.end();
}

我为分支和贡献者做了同样的模式,就像一个调用API路由的react组件,从Github APIs中获取数据。