Cal-Heatmapを使ってブログのアクティビティを表示していたけど(当時のメモ)、本家GitHugのActivity Calendarを再現しようとすると、微妙に痒いところに手が届かない1。もっと本家GitHubに寄せたライブラリないかな、と物色していたところ、react-activity-calendarを見つけた。最近もメンテナンスされてそうなので、これに切り替えることにした。

react-activity-calendarはその名の通りReact用のコンポーネントなので、このままではHugoからは使えない。

スタンドアロンのJSにビルドしようかとCopilotに相談したら、Hugoのjs.Buildという機能を教えてもらった。サイトビルド時にesbuildを呼び出して、tsファイルやjsxファイルをトランスパイルしてくれるらしい。Hugo、なんでもあるな…

というわけで、jsxで書いたコンポーネントをあっさりHugoから呼び出せたので、そのメモ。

1. ライブラリのインストール

js.Buildの依存ライブラリの解決はpackage.jsonを参照する。というわけで、まずは必要なライブラリのインストール

npm install react react-dom react-activity-calendar

2. JSXの作成

Reactで開発するのと同様にjsxファイルを書く。例えばBlogActivity.jsxというファイル名にして、assets/jsに配置する。

import React from 'react';
import ReactDOM from 'react-dom/client';
import { ActivityCalendar } from 'react-activity-calendar';

const data = [
  { date: '2025-02-26', count: 2, level: 1 },
  { date: '2025-04-10', count: 16, level: 4 },
  { date: '2025-06-15', count: 11, level: 3 },
  { date: '2025-08-26', count: 0, level: 0 },
];

function BlogActivity() {
  return <ActivityCalendar data={data} />;
}

const root = ReactDOM.createRoot(document.getElementById('cal-heatmap'));
root.render(<BlogActivity />);

3. Hugoのテンプレートに埋め込む

js.Buildを使ってビルド。ついでに、Minifyもしておく。

<div id="cal-heatmap"></div>
{{- with resources.Get "js/BlogActivity.jsx" -}}
{{- $opts := dict 
  "targetPath" "js/blog-activity.min.js"
  "minify" true -}}
{{- $js := . | js.Build $opts -}}
<script src="{{ $js.RelPermalink }}"></script>
{{- end -}}

上記jsxだと以下のようにレンダリングされる。下記は jsbuild という、上記埋め込みと同等のshortcodesを用意してレンダリングしている。

あとは、サイズや色、label変えたり、イベントハンドラを仕込めば完成。以前より、より本家GitHubの見た目に近づいたと思う。

8/26時点でのキャプチャ

8/26時点でのキャプチャ

Page Insights

ちなみに、Reactのライブラリをまるっと抱えることになるため、パフォーマンスはやや悪化する。これまで約150KBで済んでいたファイルが400KB弱に膨れている。下記はモバイル時の計測結果2

before

before

after

after

ただ、Cal-Heatmapに比べると、D3フリーでSVGでレンダリングするreact-activity-calendarの方が、体感として描画は早くなっている気がする。

気が向いたら、この辺のモバイル向け最適化もやろうと思う。


  1. 例えば、subDomainghDayを指定した時、開始日と終了日が月単位で丸められるという仕様 ↩︎

  2. デスクトップはほぼ誤差でそもそもパフォーマンスは悪くない ↩︎