読者です 読者をやめる 読者になる 読者になる

リズムのじかん

javascript、typescriptなど中心に書きます。

Mithril.jsでがっつりSPAを作った話 - コード譜共有サイト「ChordKitchen」

Mithril.jsを使って、がっつりSPA(シングルページアプリケーション)を作ったので紹介します。 Mithril使いやすいので広まって欲しいです。

作ったもの

コード譜をweb上で作成して共有できるサービスを作りました。

コード譜共有サイト ChordKitchen http://chordkitchen.net

サービス紹介動画です。

www.youtube.com

開発環境

プログラミング言語

  • typescript(versionは1.4)

typescriptいいよ!! コードはできるだけjavascriptっぽく書いて、 コンパイル時にケアレスミスを検出する感じに使いました。

スクランナー

  • gulp
  • webpack(後述)

クライアントサイド

使用しているライブラリ

  • Mithril.js
  • underscore.js

Mithril.jsについて

公式サイト

https://lhorie.github.io/mithril/

今度オライリーから本が出るみたいです!タイムリーですね。

O'Reilly Japan - Mithril

日本ではあまり有名ではありませんが(ググると日本語の技術ブログが数件ヒットする程度)、 海外で評判の良いライブラリです。

仮想DOM、router、ajax機能など、SPAを作るための一通りの機能が揃っていて、 すごく軽量 です。

ソースは5KBで(ver. 2.0から実装が増えたので、今は5KBには収まっていない)、 APIが少ないので学習が容易です。 ソースコードは1500行くらいなので、困ったときにソースを追っても苦になりません。

SPAの構造

このアプリケーションはSPAです。

ヘッダー・フッターの枠は静的HTMLですが、メイン部分はMithrilで動的に生成しています。ページ遷移してもページの再読み込みはありません。

f:id:chords:20150803012228j:plain

ヘッダーのメニュー部分は、メイン部分とは独立した仮想DOMです。 メニューの「コード譜を探す」をクリックしたときなどは、 MithrilのRouterでメイン部分と連携するので、ページの再読み込みはありません。

(左上のロゴをクリックしたときだけ、 メモリリフレッシュと、 万が一のエラーからの復帰のため、 ページを再読み込みするようにしています。)

Viewの実装

Viewの実装はReact.jsのjsxをフォークしたmsxを使っています。 msxはjsxのシンタックスが使えて、コンパイルするとMithrilのviewオブジェクト(仮想DOMオブジェクト)を出力します。

以下はメニュー部分のViewの実装(抜粋)です。jsx(msx)で書くとすっきりしていいですね。

menu.jsx

ちなみにjQueryは使っていません。

仮想DOMを扱うMithril.jsと生DOMを扱うjQueryは相性が悪いためです。 生DOMの操作も一部ありますが、jQueryを使うには冗長なので、生のjavascriptで実装しています。

jsxでできないこと

jsxでは名前空間のimportが必要なタグ・属性は使えないようです。 譜面部分はHTML5のinline svgで出力していますが、 上記仕様により

<use xlink:href="#bar" />

は、jsxではコンパイルできません。 (このためReact.jsは使いませんでした)

mithrilはview部分について、

m("use", {"xlink:href":"#bar"})

とか

{tag:"use", attrs:{"xlink:href":"#bar"}}

という書き方ができ、どんなタグ・属性でも出力できます。 svg部分は後者の書き方で実装しました。

ちなみに前者がMithrilの標準的な書き方で、後者はMithril内部のプリプロセス後の形式です。 プリプロセスが不要な分、後者のほうが実行が早いです。 msxコンパイル後の形式も後者になります。

webpackさまさま

typescriptやmsxコンパイルはwebpackを使っています。

webpackかしこ過ぎます!!まだ使っていない人は絶対使ったほうがいいです!!

typescript-loaderは、webpackのwatchと合わせて使うと、 ちゃんと差分コンパイルしてくれるので、無駄な時間がかかりません。

扱うファイルが、.ts(typescript)、.jsx(msx)、js(外部ライブラリ)と混ざっていても、 当たり前のように1ファイルに結合してくれます。

inline-source-mapも便利です。 コンパイル後のjsファイル内にsource-mapの情報が出力されるので、 .js.mapみないなファイルを作らなくてもsource-map機能が使えます。 chromeなら、ブラウザ上でtypescriptやjsxソースのデバッグができます。

f:id:chords:20150803012637p:plain

Mithril.jsによるSPAの実装

MithrilでSPAを実装すると、コードが全体的に関数型っぽくなります。

関数型プログラミングに詳しくないので、深く突っ込まないでください。。。

Mithrilの代表的なAPIm.propがあります。

var title = m.prop('test'); // m.propはgetter-setterオブジェクトのfactory

title(); // 引数なしで実行するとgetter
// 出力:'test'

title('hoge'); // 引数ありで実行するとsetter

title();
// 出力:'hoge'

このAPIで生成したオブジェクトは、画面からの入力を受け取り、かつ、画面への出力を担います。

var title = 'hoge'でいいじゃんと思いがちですが、これをやると失敗します。(何度も失敗しました。。)

変数に状態を持たせると、(使い方を誤ると、)

SPA初期化時に関数に引数が束縛され、画面への入力で値を変更したつもりでも、 束縛された値が画面に出力され、画面に値が反映されない

という自体に陥ります。←説明にあまり自信がない。。詳しい方お願いします。

これを防ぐには、入力が出力まで伝わるよう、 値を変換する関数をいくつもつなげるように(組み合わせるように)実装する必要があります。 関数型プログラミングっぽい雰囲気が出てきましたね。

また単純に関数をつなげるだけだと計算量増えてブラウザが嫌がるので (特に今回は譜面の描画部分)、

  • (諸々考慮した上で)計算結果を変数にキャッシュする
  • カリー化
  • メモ化

などのテクニックを使うといいみたいです。あまり詳しk(ry

あとコードをすっきりさせるには関数合成がよいみたいです。あm(ry

このあたりのことは、「JavaScriptで学ぶ関数型プログラミング」に参考になることがたくさん書かれていました。

JavaScriptで学ぶ関数型プログラミング

JavaScriptで学ぶ関数型プログラミング

SPAと履歴管理(History API)とSEO対策

SPAなので、明示的にpushstateするなどしないと、ブラウザに履歴が残りません。 試行錯誤した結果以下のように落ち着きました。

  • 履歴機能はMithrilのRouterに任せる
    • Routerが内部的にpushstateを呼び出す
      • routeが切り替わるたびにURLを履歴にpush
      • Mithril.jsの公式サイトにもあまり詳しく書かれていなかったのでソースを読んだ
  • アプリはどのURLでアクセスされても画面が正常に表示できるように実装
    • クライアントサイドをステートレスに実装するイメージ

SPAのSEO対策は結局ベストプラクティスがよくわかりませんでしたが、、、 (そもそもSEO対策自体初めてだし、、、)

上の履歴の実装をしたところ、 GoogleAnalyticsにページごとのアクセス結果が出ているので、 とりあえず大丈夫でしょう。

SPAのメリデリ

上にいっぱい書いたので細かいことは省略しますが、

メリット

  • 何より動きが快適!!さくさく!!
  • クライアントサイドに業務ロジックが集約する
    • 開発後半の機能追加・修正が局所的で開発スピード◎
    • サーバサイドの役割が簡潔

デメリット

  • ノウハウが十分に公開されていない
    • 開発初期の方式を決める部分で試行錯誤(時間かかる)
  • クライアント環境依存の問題
    • とりあえず古いブラウザは諦めた
  • トラブルシューティング
    • クライアント側でエラーが発生した場合どうやって追跡するか

サーバーサイド

構成

  • nginx
  • node
    • express
    • mongoose
  • mongodb

nginxは静的コンテンツの配信用、nodeはステートレスでRESTFULなAPIサーバです。

さくらVPSで動いています。

まとめ

週末プログラマとして作って、1年ちょっとかかりました。

終盤はMithrilにがっつり乗っかったことで、開発スピードが上がりました。 最初のプロトタイプはjQuery + d3.jsで作っていたのですが、SPA向きではなかった気がします。

これからSPAを作りたい人には、学習が容易なMithril.jsおすすめします。

p.s.

typescriptもいいよ(^^ゞ