使用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中获取数据。