つまみネットの Pull Requests 2024
修論ヤバいとはいえ、現実逃避につまみネットのコードを触りまくってたので (は?)、今年もまあまあ変更があります。 今年マージした大きめの PR を見てみましょう。
- 2月: Tailwind CSS の導入
- 2月: テストを Jest から Vitest に移行
- 2月: Syntax Highlighter を Prism から Shiki に移行
- 5月: Monorepo 化
- 5月: Kawaii Mode
- 6月: ESLint Flat Config
- 11月: AI つまみアイコンのモデルが SD 3.5 LoRA に変わった
- 12月: AI つまみアイコンの履歴を出せるようにした
- 12月: Next.js 15 & React 19
- 12月: ハンバーガーメニューのアクセシビリティの改善
- 12月: Admin の追加
- 12月: SCSS を剥がして CSS に変更
- 12月: ブログを ISR するようにした
めちゃくちゃやってる
論文苦行の終わった2月、学会直前で大体準備の終わった5月、そしてアドカレ駆動開発 + 修論サボりの12月にデカPRが集中しているようです。
それぞれについて簡単に振り返ります。
2月: Tailwind CSS の導入
個人開発は時間がないので手早く書けるのが正義!ということで Tailwind CSS を導入しました。
Tailwind CSS は CSS を書かずにクラスを書くだけでスタイリングできるライブラリで、たとえばこんな感じで書けます。
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Button
</button>
.my-button {
background-color: #3182ce;
&:hover {
background-color: #2c5282;
}
color: #fff;
font-weight: 700;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
style 属性とは違って、ダークモード dark:
や hover などの疑似クラスも使えるので、とても便利です。
メディアクエリの breakpoint も数値ではなく sm:
などの名前で指定できるので、レスポンシブデザインも楽です。
今までも「この程度のスタイルのために CSS ファイル作って書くの大袈裟だなあ」と思って style 属性にスタイルを書きまくっていたことがあるので割と親和性がありました。
メディアクエリに決まった値を使えるのは CSS では難しい (CSS 変数が使えないので具体的な数字を指定する必要がある) ので、これは Tailwind CSS の強みだなあと思っています。 ただ、最近は Custom Media Queries で対応できるようになるという話もあるので、そのうちまた戻るかもしれません。 以下は W3C の Custom Media Queries のドキュメントからの引用です。
EXAMPLE 56
For example, if a responsive site uses a particular breakpoint in several places, it can alias that with a reasonable name:CSS@custom-media --narrow-window (max-width: 30em); @media (--narrow-window) { /* narrow window styles */ }
それと Tailwind CSS の良いところとして、Tailwind Merge などのライブラリを使うことで "クラスのマージ" ができることがあります。 スタイル付きコンポーネントのスタイルを上書きすることが簡単にできます。以下は Tailwind Merge のリポジトリの README からの引用です。
Tailwind Merge の例import { twMerge } from 'tailwind-merge' twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]') // → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
CSS だとクラスの並び順は関係ないのですが、Tailwind Merge を使うと後ろのクラス名を優先してマージしてくれるので楽で良いです。 ただこれも CSS 側で対応できるようになったという話 (CSS Cascade Layers だっけ?) があるので、実は CSS で良い説もあります。
凝ったスタイルの可読性は CSS の方が良いので、また来年以降もスタイルの管理には悩み続けることになりそうです。
2月: テストを Jest から Vitest に移行
テスティングフレームワークを Jest から Vitest に移行しました。 Jest は古いフレームワークなので、新しいこと (ESM-only のライブラリを使うとか) をしようとすると色々闇なハックをする必要があり、あまりにしんどかったので Vitest に移行しました。
これは副産物 (物?) でしたが、watch mode が良かったです。テスト対象のファイルを書き換えると即テストが走るので、リアルタイムで正しく実装できているか確認できます。リファクタリングするときに良さそうですね。
……と言いつつ今つまみネットのテストは壊れていて機能していないので、来年どうにかしようと思っています。(React Testing Library 関連が壊れてて PASS しなくなっちゃった)
2月: Syntax Highlighter を Prism から Shiki に移行
イケてる新しい Syntax Highlighter にしようぜ!ということで、Prism から Shiki に移行しました。 これまで使ってた Prism は古いライブラリで、そこそこハイライトが壊れているイメージがあったので、VSCode と同じエンジンでハイライトしている Shiki の方が良いかなと思いました。
お気に入りの機能は Transformers で、たとえばコードに // [!code error]
// [!code highlight]
と書くと、その行をエラーにしてくれたりハイライトしてくれたりします。
function hello() {
const msg = 'Hello, world!';
msg = 'Goodbye, world!'; // TypeError!
console.log(msg);
}
function hello() {
const msg = 'Hello, world!';
let msg = 'Goodbye, world!';
msg = 'Goodbye, world!'; // OK!
console.log(msg); // => Goodbye, world!
}
5月: Monorepo 化
つまみネットもデカくなりすぎてとうとう monorepo になりました。 Monorepo は複数プロジェクトを一つのリポジトリで管理するやつです。と言ってもわからないので今の構成を見てください。
.
├── README.md
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
├── apps
│ ├── admin # 管理画面
│ ├── content-server # 記事のデータを提供するサーバ
│ ├── dev-blog-server # 開発用ブログサーバ (プレビュー用)
│ ├── image-generation # AIつまみアイコンサーバ
│ └── trpfrog.net # つまみネット本体
├── packages
│ ├── config-eslint # ESLint の config
│ ├── config-tailwind # Tailwind CSS の config
│ ├── config-typescript # TypeScript の config
│ ├── config-vitest # Vitest の config
│ ├── constants # サービス全体で使う定数 (ポート番号とか)
│ ├── posts # 記事のデータ
│ └── utils # サービス全体で使うユーティリティ
... 以下省略
apps 以下が各サービスのディレクトリで、packages 以下が共通のコード/ライブラリのディレクトリです。 つまみネットでは複数のプロジェクト (サービス) 間での通信が発生するので、それらを一つのリポジトリで管理しています。
apps/trpfrog.net
の下に (も) package.json
とかお馴染みのやつがあります。
興味がある人はリポジトリを覗いてみてください。
Monorepo の管理には Turborepo を使っています。ビルドキャッシュとかしてくれます。Vercel いつもありがとう。
ビルドが重たいのは本体くらいなのでそこまで恩恵を受けている感じはしませんが、turbo dev
で全部のサービスが一発で立ち上がるのは便利だと思います。
Monorepo にしたおかげで新しいサービスを追加しやすくなりました。 つまみネットでしか使わないのにリポジトリを分けるのもなあと思っていたので良かったです。
きちんと monorepo にする意味があるくらいデカい個人サイト何?
5月: Kawaii Mode
流行りに便乗しました。そんなこともありましたね……
つまみネットで ?kawaii=true
をクエリパラメータにつけると kawaii モードになります。やったことない人はやってみてください。
6月: ESLint Flat Config
今年のつまみネットの苦行枠、ESLint Flat Config 移行です。 情報が少なすぎて Biome に乗り換えてやろうかと思いましたが、なんとか移行できました。
ESLint のコミュニティは全体的に枯れているのか、みんな ESLint v9 や ESLint Flat Config への対応が遅い……にも関わらず ESLint 本体は移行に熱心なので、 まだ v9 サポートしてないライブラリが多くある中で「今年の 10 月で v8 サポート終了!」と言っていてすげえなと思いました。 とはいえ、だいたいのプラグインはサポート終了ギリギリで v9 に対応してきたので、コントリビュータの皆さんには頭が上がりません。ありがとう
11月: AI つまみアイコンのモデルが SD 3.5 LoRA に変わった
ちくわぶさんに新しいつまみアイコン生成モデルを入れてもらいました。ありがとうございます。
リアルな表現が上手くなった気がします。Stable Diffusion 3.5 すごいぜ
12月: AI つまみアイコンの履歴を出せるようにした
ということでせっかく綺麗なつまみアイコンが作れるようになったのに、生成結果を捨て続けているのは勿体無い!ということで DB に保存するようにしました。
そして履歴を表示するページも作りました。この辺はブログにたくさん書いたので割愛します。読んでね
12月: Next.js 15 & React 19
来た!!!! Next.js 15 & React 19 の時代が…………
とりあえず React Compiler は有効にしてみました。
どうかな?どうかな?軽くなりました?僕はよくわかりません。もともと useMemo
とか useCallback
とか React.memo
とか多用してたしなー……あまり恩恵は受けられてないかもしれません。
不満があるとすれば、React Compiler が Babel にしか対応していない (SWC に対応していない) ので、Turbopack が使えないところです。 Twitter で調べたら React Team の人が「今後数ヶ月以内に SWC 対応するぞ!」と言っていた ので期待しています! いつもありがとう
React 19 の新機能はというと、Next.js が先行して Canary 版 React (※) をガンガン有効にしているのでそこまで驚くこともないかなーという感じです。Server Actions はつまみネットでもたくさん使っています!
あとは ref
が props 経由で渡せるようになったのは嬉しいかもしれません。滅 forwardRef
まだ Admin 画面 (後述) でしか使っていないのですが、ref
でクリーンアップ関数返せるようになったのはちょっと便利でした。たとえば「テキストボックスに何も入力していないままフォーカスを外すとデフォルト値に戻る」という機能は次のように書けました。
useRef
+ useEffect
しなくて良いのが良いですね。 (ところでこのくらいの機能を実現するならもっと良い書き方ありそうな気がしますね)
const [value, setValue] = useState(initialValue)
return (
<input
value={value}
onChange={e => setValue(e.currentTarget.value)}
ref={node => {
const handleFocusOut = () => {
if (node?.value === '') setValue(initialValue)
}
node?.addEventListener('focusout', handleFocusOut)
return () => {
node?.removeEventListener('focusout', handleFocusOut)
}
}}
/>
)
React Canary
「Canary 版の React の機能導入するな!」とよく Next.js が叩かれているイメージがありますが、React Team としては「React 本体リリースにはまだ入れてないけど、きちんとサポートするからフレームワーク作者は有効にしていいよ」という立場らしいです。 しかも
公式にサポートされているため、何らかのリグレッションが発生した場合、安定版リリースのバグと同様の緊急度で対応します。
とのことなので安心して使えますね! Next.js は悪くなかった
12月: ハンバーガーメニューのアクセシビリティの改善
つまみネットはアクセシビリティ終わっているなあと思ったので、モーダル部分をキーボード操作可能にしました。
そもそもモーダルを開く操作ができない、表示されていないはずのモーダルにフォーカスが当たる、などと酷い有様だったので改善しました。 それとFocus trap 入れてモーダル外にフォーカス行かなくしたりしました。
その他もガバガバなので、来年のつまみネットはこのあたりを意識したいなあと思いました。
そういえば最近の記事からブログ記事の画像に全部 alt を入れるようにしました。 正直な話、この画像の多すぎるブログで全部の画像に alt 書くのは大変なので、画像のアップロード時にGPT-4o で alt を自動生成 + 人間チェックする仕組みを導入しました。 いつも通り自作の仕組みです。比較的に楽に alt を書けるようになって良かったです。
12月: Admin の追加
もともと AI つまみアイコンの強制再生成などの処理を行うための Admin 画面に api.trpfrog.net/icongen/admin
を使っていたのですが、
Hono JSX + HTMX で作っていたのでまあまあメンテが辛く、きちんとした Admin が欲しいねえと思っていました。
ということで Next.js で作りました。UI ライブラリには Mantine を使いました。 今回初めてスタイル付きの UI ライブラリを使ったのですが便利ですね!モダンなイケてるサイトを爆速で構築できて良かったです。 モーダル作るのにめちゃくちゃ苦労してたのはなんだったんだ……。それも勉強になるから良いのです。
Mantine の採用理由は「オタクがおすすめしてたから」だけです。すみません。 もともとスタイル付きのライブラリはロックインされそうで怖いので使いたくなかったのですが「まあ自分しか見ない Admin だし良いか、爆速で作れる方が大事!」ということで導入しました。 結果めっちゃいい感じでした。よほどデザイン凝りたいとかじゃなければすごく良いと思います。
つまみネットのデザインは全部自分でやりたいので、今のところつまみネット本体への導入予定はありません。 あ でもヘッドレスUIとして使うのはやるかもしれません。どうしようかな〜
12月: SCSS を剥がして CSS に変更
あとこれはつい昨日ですが、3年前につまみネットを Next.js に移行したときから使い続けていた SCSS を全部剥がして CSS にしました。 現代の CSS はネストもできるので、わざわざ拡張言語を使う必要もないなあと戻しました。
SCSS はメデイアクエリに変数を使えて便利 (正確には mixin としてメディアクエリを呼び出していた) だったのですが、Custom Media Queries が使えるようになるのならば、もう SCSS はいらないかなと思いました。 現時点ではメディアクエリの値をハードコードしているので、PostCSS とか使って Custom Media Queries に対応していこうかなと思っています。
12月: ブログを ISR するようにした
これは今日導入した機能です。うまくいけばこのページもこの仕組みでビルドされるはずです。
ISR は Incremental Static Regeneration の略で、ビルド済みのページをキャッシュしておいて、リクエストが来たときにキャッシュを返しつつ、バックグラウンドで再ビルドを走らせる機能です。
ブログ記事の場合は更新時に再ビルドすれば良いのでキャッシュの保持期間を 30 日くらいにしました。(これ無期限にするオプションないんですかね? fetch オプションを cache: force-cache
にするとダメという情報を見た、なんもわからん)
これまではちょっとしたタイプミスの修正のために 5 分くらいかけてビルドし直していました。
- 記事を修正
- コミット & プッシュ
- Vercel 側で 5 分くらい書けて再ビルド
今回からは次のように修正をかけられるようになりました。
- 記事を修正
- コンテンツサーバに修正後のデータをアップロード (
cd apps/content-server && pnpm run deploy
) - Admin に入って対象記事の Revalidate ボタンを押す
- Vercel 側からコンテンツサーバにアクセス、記事を取得して記事のページだけ再ビルドが走る (30秒くらい)
- 適当なタイミングで Markdown をコミット & プッシュしておく
この「コンテンツサーバ」には Cloudflare Workers + Workers Static Assets を使っています。 Cloudflare いつもありがとう
「Admin に入って対象記事の Revalidate ボタンを押す」の部分は自動化できると良いなあと思っています。 今回は面倒なので手動更新する感じにしました。
来年以降のつまみネット
というのが今年のつまみネットの変化でした。
基本的には思いつきロマン駆動開発をしているのでロードマップとかはないのですが、今のところやりたいなあと思っているのをリストアップしておきます。
- アクセシビリティの改善
- スクリーンリーダーまわりはチェックしてないのでやりたい
- 過去記事の画像すべてに alt を入れる
- 大事だけど、普通にやると明らかに苦行になるので頑張って半自動でやるぞ
- ブログ関連のディレクトリの整理
- 3年前の Next.js / React 初心者だったときに構築したゴチャゴチャディレクトリのせいで毎回頭壊してるので、いい加減整理したい
- スタイルを一部 CSS Modules に戻すか考える
- Tailwind CSS v4 移行
- UI コンポーネントを
@trpfrog.net/ui
パッケージに分離する- 今後アプリ間で共通の UI コンポーネントを使いたくなるかもしれない…… (Admin とか)
- 自己紹介ページを追加する
- 実は結構書けているんだけども、文章が恥ずかしいので今放置しています。しばしお待ちを……
- 文章が恥ずかしいといえば大学の就活体験記まだ放置しててマズい、締切目安は 12/26 だった
- 画像配信を Cloudinary から別の何かに移行する
- 現時点では Cloudflare R2 + (Vercel Image Optimization or Cloudflare Images) が候補
- Cloudflare 依存が高すぎるので無料プランが死んだときの対策を検討する
- 壊れてるテストを直す
Issues に書け!あとで書きます。(やらないやつ)
来年はお仕事が始まるのでどのくらいメンテできるか分かりませんが、記事もコードもクソデカいつまみネットを来年もどうぞよろしくお願いします。 (たぶん仕事はバックエンド多めになりそうなので、趣味フロントエンドとして続けると思う)