このエントリは、Mithril.js Advent Calendar 2016 の23日目の記事です。
ブラウザで使えるMarkdownエディタをMithril.jsで作ってみたので、事例紹介します。
Mithril.jsを選んだ理由
ドキュメントが日本語化されていたので、とっつき易かったのと、フレームワークなのに、とにかく、覚えることが少ないという印象で選びました。
経緯としては、こんな感じです
- Vue.jsで試作したけどrouterがプラグインだったりと、なんとなく面倒に...
- Mithril.jsは全部込みでしかも速いし、覚えることが少なさそうだった。
- あと、購入していた(が積ん読になっていた)黒ムツ本が手元にあったのが大きかったです。
アプリの構成
- Node.js(サーバサイド)
- Mithril.js(クライアントサイド)
- webpack(ビルドと開発サーバ)
ソースのディレクトリ構造
├── component
│ ├── editor.js
│ ├── header.js
│ └── side_menu.js
├── model
│ └── doc_model.js
├── app.js
└── view_model.js
componentディレクトリ
Mithril.jsのコンポーネントファイル(controllerとviewのあるオブジェクトを返すモジュール)を設置
modelディレクトリ
Mithril.jsのモデルファイルを設置
app.js
アプリのエントリポイント
view_model.js
Mithril.jsのビューモデル
index.htmlの中身
初めてSPA作ったんですが、HTMLは本当に空っぽで拍子抜けしました。ほとんどJavaScriptで生成するんですね。
大雑把な構造として、ヘッダーエリアと、コンテンツエリアにわかれています。コンテンツエリアはサイドメニューとエディタエリアに分かれています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>CodeDesigner</title>
<link rel="stylesheet" href="/assets/bundle/style.css">
</head>
<body>
<section id="header"></section>
<section class="main-contents">
<div class="columns">
<div id="documents" class="column is-2"></div>
<div id="designer" class="column"></div>
</div>
</section>
<script type="text/javascript" src="/assets/bundle/app.js"></script>
</body>
</html>
app.jsの中身
m.component()
で生成したコンポーネントをm.mount()
でidタグ内に突っ込んでくイメージです。
エディタエリアだけ動的に切り替わるので、m.mount()
ではなく、m.route()
を使っています。
"use strict";
var m = require("mithril");
// コンポーネント
var Header = require('./component/header');
var SideMenu = require('./component/side_menu');
var Editor = require('./component/editor');
// ヘッダー
m.mount(document.getElementById('header'), m.component(Header));
// サイドメニュー
m.mount(document.getElementById('documents'), m.component(SideMenu));
m.route.mode = "pathname";
// エディター部分
m.route(document.getElementById('designer'), '/', {
'/': Editor,
'/:title/:id' : Editor
});
コードの流れ
以下では、Saveボタンをクリックした時の、コードの流れを追ってみます。
ビュー
ビューでは、m()
でHTMLを作成していきます。
ここでは、Saveボタンを作成し、コントローラーのメソッドを呼んでいます。({onclick: ctrl.save}
の部分です)
m('a.button.is-primary', {onclick: ctrl.save}, [
m('span', 'Save')
]),
コントローラー
コントローラーでは、ビューモデルのメソッドを呼びます(vm.insert();
の部分です)
this.save = function () {
var link = vm.insert();
m.route(link);
};
ビューモデル
ビューモデルでは、モデルのメソッドを呼びます(doc_model.save(vm.edit());
の部分です)
insert: function () {
var doc = doc_model.save(vm.edit());
// 省略
},
モデル
モデルにロジックを書きます。今回の処理ではデータをローカルストレージに保存しています。
Doc.save = function (text) {
var
data = {
title : text.split(/\r\n|\r|\n/)[0].replace('# ', ''),
body : text,
},
json
;
json = JSON.stringify(data);
localStorage.setItem('docs', json);
return new Doc(data);
};
まとめると、こんな感じです。
- ビュー : コントローラーのメソッドでイベントを登録する
- コントローラー : ビューモデルのメソッドを呼ぶ
- ビューモデル : モデルのメソッドを呼ぶ
- モデル : ロジックを書く
コントローラーとビューモデルのやってることが同じじゃね?って感じですが、コントローラーは基本的にビューモデルのメソッドを呼ぶだけで、ビューモデルでは、一時的に保持するデータやコンポーネントをまたぐデータをプロパティに持たせておいて、それを使ってモデルのメソッドを呼ぶというように使い分けました。
ビューの生成について
テンプレートコンバータという変換サービスがあるので、それを使うのが早いです。
EmmetでHTMLを一度書いて、テンプレートコンバータでビューを生成した後で、動的に変えたいパラメータだけをMithrilで処理するようにしました。
ただ、ビューは書いてるうちに慣れると思います。
サーバサイド
- node.jsで作成
と言ってもサイト内のURLでページ遷移しないようにしたくらいです。
感想
覚えることが少なくて、情報源も公式サイトと黒ムツ本で必要充分でした。もうちょっと使い込んでからですが、仕事でも使ってみたいと思います。あと、jQuery不要論がなんとなく理解できた気がします。