ジンジャー研究室

長めのつぶやき。難しいことは書きません。

Elm で個人ホームページを作ってみた

まず個人ホームページって響きが懐かしいな!という話はさておき。

普段は複雑な GUI 作りたいモチベーションで Elm をやっているんだけど、もちろん普通の Web サイトも普通に作れますよということで、参考になれば。

作ったサイト

world-maker.com

高校の頃からやっている自作音楽を公開するサイト。タイトルが中二っぽいけど気にしてはいけない。

ソース

github.com

使った技術

数年前のリニューアルでは Polymer を使ったが、ブラウザのバージョンアップで動かなくなって放置していた(トラウマ)。今回はもっと安心感のある Elm を使う。

MIDI + MP3 プレイヤー

f:id:jinjor:20170511074806p:plain

ピアノロール大好きなので、MP3 と並行して MIDI から読み取ったノートをピアノロールで流せるようにした。描画は Elm 公式サポートの SVG ライブラリ(elm-lang/svg)を使う。普通の HTML 要素と同じく Virtual DOM なので宣言的・富豪的にプログラミングできる。

バイナリデコーダ

MIDI はバイナリ形式だが、現在 Elm はバイナリをサポートしていない。そういう時は JavaScript 側で読んで Elm 側に送り込むのが推奨のなのだが、どうしても Elm でやりたかったので Native(Kernel) モジュールという裏技を使って実現した。もちろんバイナリ用のデコーダーなど用意されていないので、自分で作った。

github.com

Native(Kernel) モジュールを使うのはエコシステムとして健全な方法ではないので広めないでほしい。星は欲しいのでください。

関数型界隈ではお馴染み(?)のパーサーコンビネーターと同じ仕組みで動く。バイナリデータは言語などと違って記述にあいまいさがないので「パーサー」ではなく「デコーダー」としている。これを使うと次のように簡単にかける。

wave : Decoder Wave
wave =
  succeed Wave
    |. symbol "RIFF"
    |= uint32BE
    |. symbol "WAVE"
    |= formatChunk
    |= dataChunk

Web Audio API

現在の Elm は音声周りもサポートしていないので、Portを使って JavaScript 側で処理する。 Port は JavaScript as a Service と考えるのが良くて、クラウド上の画像処理エンジンを使うのと同じ要領で設計すればいい。

今回は MP3 を再生するだけなので大したことはしていない。はまったのは iOS で遭遇する以下の問題。

  1. ユーザーインタラクションを起点にしないと音が出ない
  2. マナーモードにしていると、直に Web Audio API を使うと音が出ない

1 に関しては、有名なハックがあるのでそれを利用。

var unlock = function() {
  var buffer = context.createBuffer(1, 1, 22050);
  var source = context.createBufferSource();
  source.buffer = buffer;
  source.connect(context.destination);
  source.start(0);
  window.removeEventListener('touchend', unlock, true);
};
window.addEventListener('touchend', unlock, true);

2 に関しては、以下の記事を参考にした。

WebAudioAPIを使っているはずなのに、マナーモードで音が出る!? - Qiita

HTML Audio、もしくはHTML Videoをページ内で1つでも使用していた場合、 そのページでは WebAudioAPI の音がマナーモード時にも鳴ってしまう

これを逆に利用して、ダミー要素を設置して音が出るようにした。

<audio src="./assets/dummy.mp3"></audio>

Twitter Card

Twitter Card の Player card を利用すると、かなり自由度の高いプレイヤーが Twitter に埋め込めるというのを Picotune@cagpie さんに教えてもらったので、作ってみた。Card Validator を使うのだが、player card の場合は承認されるまでプレビュー出来ない。知らずに何度も試したが徒労だったらしい。

GAE/GO

Twitter Card を利用するためには、 Twitter bot が読むための タグが必要。曲ごとに タグを作らないといけないので静的 HTML 配信だと無理だということに気づく。それまで GitHub Pages での配信を想定したいたのが、急遽 GAE に移動。

サーバーサイドは Go。Node だと Flexible Environment が必要で割高らしい。Java はだるいし、 Python もつまらない。Go はマルチプラットフォーム用にバイナリを吐けるので、習得しておくと何かと便利そう。

Elm アーキテクチャ

The Elm Architecture · An Introduction to Elm

Elm アーキテクチャは Redux の原型みたいなので有名になったのだが、経験知を元に常に進化を続けている。具体的に言うと、昔は「すべては階層型のコンポーネントである」的なノリで作るのは今では無駄に複雑化させるアンチパターンと見なされていて、ビューは積極的に再利用していいけど可能な限りコンポーネントに状態を持たせるのは避けるべしということになっている。

とはいえ、状態の共有が必要なコンポーネントというのは実際には存在して、今回は Twitter Card でホームページと同じ MIDI+MP3 プレイヤーを再利用した。最初はメインにべったり書いていたプレイヤーをモジュールに分離。このリファクタリング機械的に出来る。実際、大掛かりだったわりにバグが一切出ていない。

Platform.program

誤算だったのが、サーバサイドで タグを仕込もうと思ったらコンテンツ情報が Elm で書かれてたこと。何十個か JSON に書き直せばいいのだが、Elm の方が記述量が少なく書けている気もしたので、逆にElmの定義からJSONエンコードするプログラムを書いて(Platform.program) Node から呼び出して JSON ファイルに吐き出した。

普通の CSS

以前に CSS in JS(Elm) を試していて、それはそれで CSS の弱点を消せてよかったのだが、今回は別にそこまで大規模になる予定がなかったので、普通に CSS ファイルを使った。 CSS in Elm だと DevTools からコピペしたのを Elm で書き直す必要があったり、親子関係を見てスタイルを設定できないなどの弱点があった。

あとは、最近になって IE11 以外で CSS Variable が使えるようになった ので使ってみた。便利。

モバイルファースト

スマホは使うけど PC は使わないみたいな人が増えてるので真面目にやってみた。グリッドを駆使したらレスポンシブでモダンなサイトになった。

elm-format

github.com

今や準公式くらいの位置づけのフォーマッター。昔からあったのだが、npm でインストールできるようになった(npm install -g elm-format)のを機に試してみた。

事前に予想していた意図しないフォーマット崩れはほとんど起きなかった(パイプを多用するとネストが深くなる以外)ので、みんな使えばいいと思う。4 スペースインデントもまあ慣れた。

elm-live

github.com

こちらはいわゆるライブリロードのためのツール。Elm 製 Mastodon クライアントの Tooty で使われていたので、試してみた。

操作中の状態を保持したまま CSS だけを入れ替えてくれるのが嬉しい。

感想

Web 技術が色々試せて面白いし、みんなホームページ作ろう。