SolidJS + GraphQLのサンプルコードを作成した
マイページ(プライベートサイト)を作ろうとしていた
この前マイページ(プライベートサイト)の第三版を作ろうとしているという記事を書きました。 最初は純粋に個人的に使うサイトの準備のつもりだったんですが、だんだんこれってボイラープレートだよなぁと気づき、 公開前提で進めることにしました。
しかしあれも入れたい、これも入れたいといろいろやってたら2ヶ月掛かってしまいました。
最終的に公開したのはこれです。
コミット数が少ないのは、最初は公開するつもりがなかったためです。
公開する予定がなかったので、最初はプライベートな情報も入れていました。 公開前提で進めると決めてからプライベートな情報は少しずつ取り除いていったんですが、 履歴まで公開するのはマズいなと思ったので、履歴は公開していません。 Gitの履歴編集機能を使う手もあるんですが、そこまでやる労力はなかったので・・・
技術スタック
技術スタックはREADMEに書いていますが、主な選定理由を書きます。
フロントエンドフレームワーク: SolidJS
仕事で同僚の勧めで使ったんですが、使ってみたら結構気に入ったので採用しています。 Reactとの違いは、この記事がよくまとまっています。
Reactと違うので慣れるまではちょっと時間がかかりましたが、外部リソースを扱うcreateResourceがデフォルトで入っているとか、createEffectで引数が不要とか、基本的にパフォーマンスチューニングが不要(通常使うレベルでは)とか迷うところが少なく、慣れるとすらすら書けるようになります。
ルーティング: generouted
SolidJSにはSolid Routerというのがあって、これでも十分に使えます。 ただ最近ファイルベースのルーティングが流行しているので、 何か使えるものがないか?と思って見つけたのがgeneroutedです。ReactとSolidに対応しています。
UIコンポーネント: solid-ui, kobalte
Tailwindベースのライブラリはいろいろあるんですが、もうちょっとSolidJSらしいライブラリはないかなと見つけたのがこれです。 Reactで流行しているshadcn/uiをSolidJSで実装したものです。
shadcn/uiはRadix UIというヘッドレスUIコンポーネントをベースとしていますが、solid-uiではこのRadix UIに相当するものがkobalteです。
GraphQL API: Pothos
以前はPythonのStrawberryを使っていてそれなりに気に入っていたんですが、 まだ1.0より前で枯れておらず、Dataloaderを使おうとしたがうまく動かなかったりと、使い続けるには不安がありました。
また、GraphQLを使いこなすならPythonよりはTypeScriptだろうと思い、いろいろ調べてみました。 個人的にはStrawberryのコードで宣言的に書ける(コードファースト)というのが気に入ったので近いライブラリがないかなと思って見つけたのがPothosです。
GraphQLはThe Guildってところにいろんなライブラリがあることが分かったので、GraphQL Code Generatorなど積極的に利用しています。
CSSフレームワーク: Tailwind CSS
これは以前から使っているのですが、もうこれに慣れると他は使う気になれないなと感じます。 一番の利点はCSSの詳細度にまつわる問題を解決してくれることです。 ユーザースタイルシートだとかあえて上書きするケースもありますが、サイトを作る側にとって詳細度という悩みの種が消えるのはとても大きいです。
ビルドツール: Vite
これは言うまでもないですが速いのと、設定がシンプルなことが気に入っています。
テスト: Vitest, Solid Testing Library, Testcontainers for NodeJS
昔はJestを使っていたのですが、最近はVitestを使っています。 速いのと、Viteとの相性が良いのも理由ですが、一番の利点はESM関連のトラブルがないことです。 自分はCommonJS使いたくない、ESMだけで統一したい人なので、JestでのESMのトラブルに悩まされていました。 それがないのもVitestの利点かなと。
UIのテスト用にSolid Testing Libraryを入れています。これはTesting Libraryがデファクトになりつつあるなと感じるのでそのSolid版を。
DBのテスト用にはTestcontainersを入れています。起動には時間がかかりますが、コンテナでDBやその他サーバを立ち上げて使ったほうが後片付けは楽です。
パッケージマネージャ: pnpm
パッケージマネージャとしてこれも結構前から使っています。 その前はyarnだったのですが、yarn v2からの路線変更が全く馴染めなかったことと(特にzero install)、 速いという理由から、メインで使っています。
フォーム管理: Felte, zod
フォーム管理もそのうち専用のライブラリが必要になるだろうと思って最初から入れました。 いくつか検討したのですが、最終的にはFelteにしました。SolidJSだけでなくSvelteにも対応している(フレームワーク中立)のと、 v1になっていたので成熟しているのかなというのが判断理由です。
バリデーションはいろいろ挙げられていて、最初はyupを検討していたのですが、i18nの扱いが微妙だったのでzodにしています。 ただzodはバンドルサイズが大きくなるので妥協案という感じです。
リバースプロキシ: Traefik
最初はデプロイにKamalを検討していました。 Kamalは複数コンテナに対応していなかったので見送ったんですが、その過程でTraefikの存在を知りました。
Traefikは設定が難しいですが、compose.ymlでportsを書かなくて済むのが地味に気に入っています。
ストレージ(S3互換): Minio
ローカルで動くS3互換オブジェクトストレージ。ただS3と微妙に挙動が違うので、その対応を一部入れています。 テストはMinioですが、実際に使うのはCloudflare R2の予定。
デプロイ: GitHub Actions + Ansible + Docker Compose
以前からAnsibleによるデプロイは入れてたんですが、Mac上でコマンドを打たないとデプロイされないのが面倒だったので、 タグを打ったらリリースされる機能を入れました。
アーキテクチャ保護: Dependency Cruiser
依存関係をチェックしてくれたり、グラフ化してくれるツール。個人的にかなり気に入って毎回入れるようにしています。 パッケージ間の依存関係を定義するだけでも散らかりにくいのでおすすめ。
TypeScript実行: tsx
TypeScriptを直接実行するツールとしてはts-nodeがあるんですが、これもESM対応が微妙だったのでモヤっとしてたときに見つけたツールです。
開発環境: VS Code
以前はPyCharmとVS Codeを併用してたんですが、今回はVS Code一本にしています。
大きな特徴として、1つはモノレポに向いたワークスペースの採用。もう1つはタスクの自動起動。
部分的に抜き出すと、こんな設定を入れています。フォルダをオープンしたときに pnpm run watch
を実行させる設定です。
{
"tasks": {
"version": "2.0.0",
"tasks": [
{
"label": "watch",
"command": "pnpm",
"args": ["run", "watch"],
"isBackground": true,
"runOptions": {
"runOn": "folderOpen"
}
}
]
}
}
この中で、chokidarを使ってファイルの変更を検知して適切なコマンドを実行しています。
諦めたこと
今回いろいろ試したんですが、最終的に今回は見送りにしたものをいくつか挙げます。
- Hono
- 気にはなっててなんとなく入れたExpressから移行しようとしたんですが、
express-oauth2-jwt-bearer
の移行先が分からず断念。 - また次の機会があれば試してみる予定
- 気にはなっててなんとなく入れたExpressから移行しようとしたんですが、
- MDXファイルでのタイトル設定
- MDX対応は入れてBoldとかボタンとかは表示できたんですが、タイトルを取得する方法が見つからず断念。
- Storybook
- ちゃんと使ったことがないので今回は荷が重いと判断して断念。
- Subscriptionのテスト
- ちゃんと書いたはずなのに動かないので断念。
- E2Eテスト
- そこまで気力がなかったので断念。たぶんやるならPlaywrightだと思うけど。
- GraphQL Relay
- 面倒そうだったので断念。必要なら実装するかもしれないが、サンプルコードなのでやる気が起きず。
作ってみての感想
最初は個人用サイトの準備くらいの位置付けだったんですが、ボイラープレートだよなぁと思い、 最後にはこれフレームワーク作ってるのでは?と感じ始めました。 フレームワークというためには全然足りない、ただの寄せ集めなんですが、 いくつか実装を見ないとメンテナンスできないところがあるので、これをもうちょっと整備するとフレームワークっぽくなるのかなと。
←自分的ESM(ES Modules)対応方法