🧱

ミニマルな Trello クローン Trellith を PWA で作った

2023/12/05に公開

個人開発でミニマルな Trello クローン Trellith を 作りました。実装には Preact と TypeScript を用い、Web デザインにはライブラリを使わず素の CSS を工夫して使っています。DnD 周りの機能は HTML Drag and Drop API を使い自作しました。作ったものを紹介しつつ、開発過程で考えたこと、試したこと、学んだことを振り返ります。コードは GitHub で公開しています。

Trellith

Trellith は Trello の機能のうち、最小限のものを備えています。ボード、リスト、カードの作成ができ、それらを DnD 操作で並び替えて、個人向けのタスク管理に使えます。いわゆるバックエンドはなく、データはブラウザ上の localStorage に保存する PWA です。このため、ネットワークが不安定な場所でもアプリの動作は影響を受けることがなく、一度ブラウザで表示したことがあればキャッシュが効き、オフラインであっても動作します。

自分は最近、ツール系 Web アプリの理想は、Ink & Switch 社が提案するローカルファーストソフトウェアの特徴を満たすものになるのではと考えています。Trellith はあくまで個人向けでコラボレーション要素はないのですが、それ以外についてはローカルファーストであることを意識して作りました。

Preact

フロントのライブラリには Preact を使いました。React の軽量高速版で、細かな違いはありますが、ほぼ同様の書き味です。主な違いはドキュメントに記載されています。

しばらく使った感想としては、className が class で済み、SVG が React 向けコンバートなしで使えるなど、ライブラリへのロックイン度が低く感じました。なるべくブラウザでサポートされる DOM 仕様に基づきたい自分としては使い心地がよかったです。また、ソースコードをビルドした結果の js サイズは現在 55KB 程度と小さく抑えられました。

基本的に問題なく使えていますが、1 点 composition イベント周りは型定義通りに書くと動かない問題に遭遇しました。型があることで助かることも多いと思いますが、型定義のケースがそもそも違うこともあるようです。VS Code 上では間違っているように見えないので戸惑いました。

Preact は去年、React にはない状態管理として Signals を導入しました。React の軽量高速版という立ち位置からまた異なる路線を検討しているようにも見え、React 系のレンダリング周りの最適化手段について課題を感じている自分としては、今後に注目しています。

素の CSS による Web デザイン

Web デザインをどのように行うかは考えることが多く、トレンドとされるライブラリも多岐にわたり、話が尽きないトピックだと思います。

参考までに 2023 年の State of CSS を眺めると、Tailwind CSS のようなユーティリティファーストの考え方で作られたものがある程度人気を維持しつつ、Open Props のようなデザインシステムにおける構成要素のデザイントークン部分を担うもの、UnoCSS のような Atomic CSS Engine をかかげるものが出てきているようです。

色々な手法が乱立する中、合った方法を探すため、まずは自分がいいと思う Web デザイン手法の特徴を整理してみました。

  • 素の HTML に対して適用できること
  • フロントエンドの多様なライブラリに対しても適用できること
  • デザイントークンを管理できること
  • トレンドに左右されず寿命が長いこと
  • 将来のメンテナンスコストを最小化できること

この基準をもとに Web 上の色々な記事や参考書を読み、どの手法がいいか考えたのですが、たどりついた結論は、自分の個人開発では素の CSS を工夫して使うで十分かも、でした。

Tailwind は自分の要求を 6, 7 割は満たしていそうでした。しかし、以下の課題も感じました。

  • dark-mode の適用方法が手間
    • デザイントークンを CSS custom properties で管理していれば、それに基づいた CSS を書くだけで済むため、mode 用に class を列挙するのがいまいち
  • Tailwind に存在しないスタイルを使いたい場合は結局別ライブラリを探すか、自作する必要はある
  • 自分が作るアプリはそもそも小さいので、Tailwind が提供するもののごく一部しか使わなそう

こうして、Tailwind が掲げるユーティリティファーストの考え方をいいと思いつつ、満足しきれなかったため、参考にしつつ素の CSS を工夫して使っていい感じに Web デザインする方法を考えることにしました。以下の曼荼羅が、考えた手法の概念図です。

曼陀羅

各 CSS の役割:

  • Token CSS: デザイントークン担当
  • Reset CSS: いわゆるリセット CSS
  • Base CSS: アプリの標準的なスタイルを担当
  • Utility First CSS: 自前ユーティリティファースト CSS
  • Layout CSS: レイアウト担当
  • Pattern CSS: Utility First, Layout で賄うのにふさわしくないものを担当
    • ドロップダウン、スクロールバー用スタイルなど

この図と役割を参考に素の CSS で Web デザインを行いました。最初は何を作るにも CSS を書く必要があり労力がかかりましたが、よく使うものが出揃うとあとは使いまわしでやっていけて、現状大きな不満や破綻は感じていません。

素の CSS で Web デザインする手法はあまり聞かない気もしますが、時間が経てば変わってしまうライブラリのトレンドやその周りの騒々しさを気にしなくてよくなり、CSS の標準的な仕様やうまい使い方を追っていればいい状態に身を置けるのは快適でした。懸念点として、より複雑な UI を持ったアプリを作る場合に対応できるのかは謎ですが、開発を進めてみて、今後自分の考えが変わるかどうかみてみようと思います。

DnD 機能

DnD 周りの機能はライブラリを使わず、Drag and Drop API を使い自作しました。ボード、リスト、カードについて DnD 操作による並び替えができます。それぞれのアイテムをドロップする場所に応じて、ユーザーが期待するであろう位置に移動しています。

DnD

一例として、カード並び替え機能をどう作ったか紹介します。Trellith のカード並び替えには、「リスト内での並び替え」と「別リストへ移動しつつ並び替え」の 2 種類があります。

「リスト内での並び替え」は、ドロップ先カードの位置、ドラッグしたカードとドロップ先カードのリスト内 index の大小を考慮して、以下の処理を行っています。

  1. ドロップ先カードがリスト内先頭であれば、ドラッグしたカードをリスト内先頭へ移動
  2. ドロップ先カードがリスト内最後であれば、ドラッグしたカードをリスト内最後へ移動
  3. ドロップ先カードがリスト内先頭でも最後でもなく中間の場合
    • ドラッグしたカードとドロップ先カードのリスト内 index の大小を比較し、それに応じて移動

「別リストへ移動しつつ並び替え」については、「リスト内での並び替え」で作った処理がある程度使いまわせるものの、そのままだと不自然に感じるところもあり、調整しました。1 はそのまま使えますが、3 については操作後に別リストのカード数が増えるため、それを考慮して移動先をずらしています。また、2 のようにリストの最後に移動したい操作については、リストがそのままの見た目だと違和感あったため、リスト側で別リストからカードをドラッグオーバーした際にリストの高さを下に伸ばし余白を広げるフィードバックを加え、そこへドロップするとリストの最後に移動するようにしました。

おわりに

2023 年後半に個人開発で作った Trellith ついて、主な要素を振り返りました。

小規模ながら実用的なツールを作ることができたので、気の向くまま開発しようと思います。このプロジェクトに興味のある方はのぞいてみてください。

Discussion