使用无服务器绘制你的 Github 个人资料

2025-06-09

使用无服务器绘制你的 Github 个人资料

我经常被问到“我应该做什么?”或“这些想法从何而来?”之类的问题。我之前讲过我是如何产生想法的。要点是,把你所有的想法,无论大小,都写下来。

这对于演示来说非常有效。但是当你想学习一些更实用的东西时该怎么办呢?比如组建一个项目或尝试更多的工具。

我提倡的一件事是构建工具。你想用的工具。能帮你解决问题的工具。没错,为自己打造。

这有很多好处:

  • 你对这个想法很感兴趣。
  • 您可以学到很多东西来解决您的问题。
  • 您可以向潜在雇主/客户展示一些与众不同的东西。

最后一点可能特别有用。有趣的业余项目可以成为很好的谈资。我已经记不清有多少次因为我的Github 个人资料而收到评论了。因为招聘人员会查看它,并在贡献图表中看到一幅图像。

今天,我们将介绍我去年做的一个项目。“ Vincent van Git ”教你如何绘制你的 Github 贡献图。我会讲解“是什么?”、 “为什么?” 以及“怎么做?”。


什么?

如上所述,“Vincent van Git” 可以帮助你绘制 GitHub 贡献图。它是一个 Web 应用,可以生成一个 Shell 脚本供你在机器上运行。最终,你会用绘制图表的提交来填充图表。随着时间的推移(大约 3 个月),图表会发生变化,你需要重新创建它。

jh3y 的 Github 个人资料(含绘画)

为什么?

这部分分成两部分,“为什么要这样做?”和“为什么要这样做?”哈哈。

首先,在创作“Vincent”之前,我一直使用“ gitfiti ”这个包。它是一个命令行工具,可以用来在贡献图上涂鸦。它使用 Python 语言,可以用数组来绘制图像。

KITTY = [
  [0,0,0,4,0,0,0,0,4,0,0,0],
  [0,0,4,2,4,4,4,4,2,4,0,0],
  [0,0,4,2,2,2,2,2,2,4,0,0],
  [2,2,4,2,4,2,2,4,2,4,2,2],
  [0,0,4,2,2,3,3,2,2,4,0,0],
  [2,2,4,2,2,2,2,2,2,4,2,2],
  [0,0,0,3,4,4,4,4,3,0,0,0],
]
Enter fullscreen mode Exit fullscreen mode

如果你眯起眼睛,就能看到那只小猫。但是,它本身就是一个非视觉化的工具,却能带来视觉效果,这让我用起来很棘手。别误会,它是个很棒的工具。但我一直想用一种视觉的方式来创作。

现在,我可以创建一个前端来生成该数组,然后将其与 gitfiti 一起使用。但是,为什么要止步于此呢?为什么不尝试从头开始创建我自己的版本呢?

这引出了我们的第二个“为什么”。因为这里有学习各种不同工具的机会,也有机会尝试新事物。这又回到了我们在引言中提到的观点。通过一些非常规的副业项目,你可以解决一些非常规的问题。这将帮助你提升解决问题的能力。

在深入探讨学到的东西和方法之前,以下是一些我尝试过的东西。

它们不太可能出现在教程式的 CRUD 应用中。这并不是说我们刚开始学习时不应该遵循这些教程。但是,当我们开始思考“下一步是什么”时,勇于尝试是有好处的。

蜘蛛侠指着蜘蛛侠表情包,上面写着

如何?

是时候聊聊“怎么做?”了。我会把这部分分成几个小节来讲。我不会深入探讨,但我会讲解一下某些事情是如何实现的。也就是所谓的讨论要点。

电子

我脑子里有个想法,想electron为“文森特”开发一款应用。一款桌面应用,我可以打开它,画点东西,然后点击“提交”。虽然最终没能实现,但这就是我的开始。

这是项目的关键部分。我选择使用electron是因为我想开发一个可以在用户机器上使用 Node 的 React 应用。这样就能在 内部调用“git”了electron

我之前没怎么尝试过这个想法,但这是一个熟悉ipcRenderer的机会。它是一种在 Reactrenderer和 Nodemain进程之间进行通信的方式。这意味着你可以在 React 世界中点击一个按钮,然后在 Node 世界中触发一个函数。

我整理了这个代码库,演示了如何实现这一点。在 OSX 上,如果你在前端按下消息按钮,它会使用say命令行读出消息。

前端

我很清楚自己想要什么。我们需要一个类似于 Github 贡献图的网格。用户可以用指针来绘制网格。每个单元格可以是透明的,也可以是四种绿色中的一种。最终的网格效果如下。

这些类型的交互和 React 的棘手之处在于,我们不想在每次绘制时都更新状态。那样会导致大量的重新渲染。我们可以用 refs 来追踪正在发生的事情。

创造一些不同的东西会挑战我们以不同的方式使用我们常用的工具。像 Vincent 这样的工具非常适合处理 DOM 操作和 React。我也在其他项目上做过类似的尝试,比如“PxL”

项目的这一部分是关于生成我们之前提到的数组的。我们为用户提供了一种无需手动输入就能生成 0 到 4 的数字数组的方法。

使用无服务器进行 Web 抓取

现在,“Vincent”之所以能够实现,是因为空提交。它的工作原理是,我们会生成数百个空提交,并将它们提交到您选择的存储库。这些空提交会显示在贡献图中。

如何获得四种不同的绿色?这取决于提交次数。例如,假设你每年的最大提交次数是 100 次。那么为了获得 4 个级别,我们可以分别使用每天 400、300、200 和 100 次提交。这样就能生成四种不同的绿色。

我们最需要的是用户名的最大提交次数。为了获取这个信息,我们进行了一些检查,然后抓取 Github 上的活动页面。在“Vincent”中,我们要求输入用户名、分支名称和仓库名称。“Vincent”会检查这些信息是否存在,并且为空,然后再抓取提交信息。

我们这里大概有 4 到 5 个请求。这时无服务器就派上用场了。我们可以把这些请求放到Netlify 函数里,这样前端只需要发出一个请求。

这是该函数最重要的部分。在这里,我们向“贡献”页面发出请求。然后,我们用它cheerio来抓取过去一年中提交量最高的代码。

const getCommitMultiplier = async (username) => {
  // Grab the page HTML
  const PAGE = await (
    await fetch(`https://github.com/users/${username}/contributions`)
  ).text()
  // Use Cheerio to parse the highest commit count for a day
  const $ = cheerio.load(PAGE)
  // Instantiate an Array
  const COUNTS = []
  // Grab all the commit days from the HTML
  const COMMIT_DAYS = $('[data-count]')
  // Loop over the commit days and grab the "data-count" attribute
  // Push it into the Array
  COMMIT_DAYS.each((DAY) => {
    COUNTS.push(parseInt(COMMIT_DAYS[DAY].attribs['data-count'], 10))
  })
  // console.info(`Largest amount of commits for a day is ${Math.max(...COUNTS)}`)
  return Math.max(...COUNTS)
}
Enter fullscreen mode Exit fullscreen mode

您也可以创建一个本地版本并解析响应。尝试使用您自己的用户名发出该请求。

生成 Shell 脚本

接下来,我们需要一个 Shell 脚本来推送所有生成的空提交。这部分内容是在循环中创建一个长字符串。对于每个提交,我们都会根据绘制级别分配一个日期和多个提交。

第一部分需要使用luxon(我们不再需要了moment.js)来将日期与提交进行匹配。日期相关的一些数学运算在前几次尝试中有点棘手。但一旦搞清楚了,就大功告成了!

const processCommits = async (commits, multiplier, onCommit, dispatch) => {
  const TODAY = DateTime.local()
  const START_DAY = TODAY.minus({ days: commits.length - 1 })
  let total = 0
  let genArr = []
  for (let c = 0; c < commits.length; c++) {
    const LEVEL = commits[c]
    const NUMBER_COMMITS = LEVEL * multiplier
    total += NUMBER_COMMITS
    genArr.push(NUMBER_COMMITS)
  }
  // Dispatch a message.
  dispatch({
    type: ACTIONS.TOASTING,
    toast: {
      type: TOASTS.INFO,
      message: MESSAGES.TOTAL(total),
      life: 4000,
    },
  })
  // Loop through the commits matching up the dates and creating empty commits
  for (let d = 0; d < genArr.length; d++) {
    // Git commit structure
    // git commit --allow-empty --date "Mon Oct 12 23:17:02 2020 +0100" -m "Vincent paints again"
    const COMMITS = genArr[d]
    if (COMMITS > 0) {
      const COMMIT_DAY = START_DAY.plus({ days: d })
      for (let c = 0; c < COMMITS; c++) {
        onCommit(COMMIT_DAY.toISO({ includeOffset: true }))
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

一旦我们准备好所有提交数据,就可以生成该脚本了。它是一个基于提交日期、用户名、分支等信息的长字符串。

const generateShellScript = async (
  commits,
  username,
  multiplier,
  repository,
  branch,
  repoPath,
  dispatch
) => {
  let SCRIPT = `mkdir ${repoPath}
cd ${repoPath}
git init
`
  await processCommits(
    commits,
    multiplier,
    (date) => {
      SCRIPT += `git commit --allow-empty --date "${date})}" -m "Vincent paints again"\n`
    },
    dispatch
  )
  SCRIPT += `git remote add origin https://github.com/${username}/${repository}.git\n`
  SCRIPT += `git push -u origin ${branch}\n`
  SCRIPT += `cd ../\n`
  SCRIPT += `rm -rf ${repoPath}\n`
  return SCRIPT
}
Enter fullscreen mode Exit fullscreen mode

抛弃 Electron

“等等。我以为你想用 Electron?” – 读者

我做到了。

我进展得相当顺利。不过,也遇到了一些阻碍,不过没关系。问题在于通过 Node 推送提交。这需要很长时间,有时还会耗尽缓冲区。另一个问题是,我无法以清晰的方式将这些信息传达给前端。

我的一些涂鸦笔记

这就是我开始编写 Shell 脚本的原因。我开始深入研究electron-dl,突然electron-store灵光一闪:“这应该放在 Web 上。”

我只研究过如何为不同平台打包桌面应用,看起来还不错。但是,从测试和反馈来看,Windows 系统已经存在一些问题。

还有可用性因素。这不是你每天都会用到的工具。而且网页比下载安装应用程序等更容易访问。

我决定在此时放弃 Electron。而这正是 React 的强项。因为我已经为前端创建了各种构建块,所以将它们移植到 Web 应用中非常轻松。

是不是浪费时间?不!

虽然我最终没有用到Electron,但这并不意味着尝试是浪费时间。事实上,我electron在短时间内学到了很多东西,这很棒。

用户界面的乐趣

在这个阶段,我有一个可行的概念证明🙌

现在我可以好好利用它了,把所有方便用户的功能整合起来。一个可配置的表单,保存和加载绘图、动画等等。

这些是令我印象深刻的事情。

配置

我需要表单进行配置。用户可以在其中填写用户名、分支和仓库信息。同时,我还想创建一个滑动抽屉效果。

对于表单处理,我本来可以自己实现formik或创建表单处理。但我决定尝试一下react-hook-form,结果很棒。这又是一个尝试不同事物的机会。滑动抽屉的外观如下。

构建此类功能的另一个好处是,你可以寻找重构的模式。这个抽屉菜单已经成为一个可复用的组件。我把它复用到应用右侧的“信息”抽屉菜单上。

声音的

我喜欢在我的项目里添加一些奇思妙想。这是人们与我联系在一起的东西。声音是必须的,我用一个快速自定义的钩子将一些按钮点击和操作与音频连接起来。

import { useRef } from 'react'

const useSound = (path) => {
  const soundRef = useRef(new Audio(path))
  const play = () => {
    soundRef.current.currentTime = 0
    soundRef.current.play()
  }
  const pause = () => soundRef.current.pause()
  const stop = () => {
    soundRef.current.pause()
    soundRef.current.currentTime = 0
  }
  return {
    play,
    stop,
    pause,
  }
}

export default useSound
Enter fullscreen mode Exit fullscreen mode

但真正有趣的是绘制网格时的声音。在“跟 Jason 学习”上看到 Tone.js 之后,我就想再多试试看。这看起来是个好机会。不同的级别会发出不同的音符。擦除会发出沉闷的音符。

祝酒词

该应用需要一些小的 toast 组件来让用户了解正在发生的事情。例如,确认保存或告知用户正在生成提交。

我本来可以尝试一些现成的组件。但是,我不记得自己在开源组件中做过什么。这感觉是个不错的机会。用一点 React 和 GreenSock,我做了一个很棒的 Toasts 组件。创建 Toast 组件的妙处在于,它能让你更多地思考组件本身。你需要使用状态来触发创建。但是,你并没有将状态与 Toasts 绑定。这部分代码值得检查一下。

动画片

我喜欢在某个地方添加一些动画。而且这是我自己的项目,我可以放任意多的动画。

还有什么比生成 shell 脚本时添加加载动画更棒的呢?在琢磨了一番项目名称和编写代码之后,我最终选择了这个。

一些音频和 8 位风格的音乐使它更加完美!

Zip文件

如果你尝试为用户下载shell脚本,系统会提示你安全警告。我以前从未遇到过这种情况,这对我来说很新鲜。

直播中的观众建议尝试一下jszip。这巧妙地解决了一个问题。jszip我可以为用户打包一个文件README和shell脚本,并让他们下载一个zip文件。这样,用户也能获得运行该文件的指令。

const FILE = new zip()
FILE.file('vincent-van-git.sh', SCRIPT)
FILE.file('README.md', README)
const ZIP_FILE = await FILE.generateAsync({ type: 'blob' })
downloadFile(ZIP_FILE, 'vincent-van-git.zip')
Enter fullscreen mode Exit fullscreen mode

这很方便,也是我尝试一些我以前没有尝试过的新事物的另一个机会。

就是这样!

我部署了它,制作了一个简短的视频,并分享了它!所有代码都是开源的。你可以用这个应用通过无服务器的方式将提交内容写入你的 Github 个人资料。我从创建“ Vincent van Git ”的过程中学到了很多东西。它帮我解决了一个问题。它提供了一些技巧供我尝试,也让我有机会尝试不同的软件包。

这里有什么可行的建议?

为自己动手做。这是切实可行的建议。做一些你觉得有用的东西。做一个工具或你感兴趣的东西。它可以为你解决某个特定的问题,也可能为别人解决某个问题。它还能给你一个学习和尝试新事物的渠道。

为自己做。

鏂囩珷鏉ユ簮锛�https://dev.to/jh3y/paint-your-github-profile-with-serverless-15i7
PREV
吱吱作响的肖像:使用 CSS path() 函数的乐趣
NEXT
使用 CSS 创建定向发光 3D 按钮