【中級編】Riot.js + Typescript + karmaで今日から始めるElixir on Phoenixフロント開発入門 その１

## はじめに

皆さん、おはこんばんちは。

年の瀬です。
コミケの入稿割引が減っていきます。
[第２回 企業対抗トーナメント](https://secure.nicovideo.jp/form/entry/kakugetoname2)も大晦日に、あります。ギルティです。

正直色々忙しいですね。分かります。だって師走だもの。
そんな時は肩の力をいったん抜いて、Elixir on Phoenix、入門していきましょう。

* [CentOS + Erlang + Elixir + PhoenixのDockerイメージで今日から始めるElixir on Phoenix入門](http://qiita.com/xtity/items/969eefb83fcac9023542)
* [【中級編】drone.io + exrmで今日から始めるElixir on Phoenixホットデプロイ入門](http://qiita.com/xtity/items/f84c40cb5e9b8258e3ed)

前回、前々回に引き続き、Elixir/Phoenixの入門記事中級編です。

Phoenixを使った時のサーバーサイドの開発は今年一年を通して色々とナレッジも溜まってきたと思うんですが、
フロントサイドのベストプラクティスが欲しいなあというのをずっと思ってまして、
無ければ作ればいいじゃないということで、色々あれやこれや試しました。

結論からいうと結構良いものができたので、記事にまとめたいと思います。

おそらく、かなりの分量になりそうなので、予めその１と付けております。
今回から数回に分けて、まとめていきたいと思います。

それでは、前置きはこの程度にしていってみましょう。


## 主な開発方針について
当初、以下のような方針でPhoenixでのフロントエンド開発をしたかった

* デザイナさんとエンジニアが弄るものをしっかり分ける
* できるだけ既存のフロントエンド開発の資産を活かしたい
  * Reactの登場以降、jQueryとかオワコンという風潮だが、「わたしは一向にかまわんッッ！」
  * デザイナさんの移行コストとか既存のデザインFWとか考えたら、使いたい時あるじゃん、jQuery。てか素朴なDOM操作
  * ぶっちゃけReactも辛い時は辛い
      * 特にPhoenixではまだまだ辛い
* とは言え、素のJSは辛い
  * 型欲しい
  * レガシーJSの生存能力とか異常だし、メンテとかどんどん無理ゲーになる
  * コンパイルさせて最初から綺麗なJSだけ見ていたい
  * 設計であんま宗教戦争したくない
* テストも書きたい
  * JSいじったら自動で回ってて欲しい
* できるだけサーバーサイドレンダリングしたい
  * APIだけに頼ると、どんどんリクエストの本数増えるじゃん
  * せっかくPhoenixなのに
* テンプレートエンジンも使えたりしたい
  * jadeとか
* FWは軽くて小さいのがいい
* 速く書きたい
* とにかく楽したい


## 構成
↑のようなことで試行錯誤したら、自然と↓のようになりました

* Riot.js
  * 軽い、小さい
      * 速い
      * 万が一止めたくなった時のインパクトが小さい
          * 他のFWに移っても、ViewとModelはぶっちゃけ最小限の修正で使えそう
  * MVC -> MVP
    * ModelとViewのやりとりは全部イベント
    * ModelとViewがより疎になる
    * Modelのテストがより書きやすくなる
    * Magic Controller予防
        * Presenterは単にViewのイベントとModelのイベントの仲介役
  * よくあるカスタムtagとして、ロジック、デザインを全て押し込められる
      * 最悪JS書かなくても全然tag作れる
  * Viewはjadeでも書ける
  * jQuery上等
  * プリコンパイル上等
  * 参考ページ
      * [Riot.js — The 1kb client-side MVP library](https://muut.com/blog/technology/riotjs-the-1kb-mvp-framework.html)
      * [StackOverFlowの「MVPとMVCの違い」についての回答を読んでみた](http://qiita.com/takahirom/items/597c48ece57b4623cdee)
      * [Qiitaのriotタグ](http://qiita.com/tags/riot)
* Typescript
  * 型大事
      * バグもそうだけど、書く時怒ってくれるから楽
  * 別に型無くても使える
      * tsd無くても、anyでいいじゃない
      * 暇な時にtsd書いてあげたらいいじゃない
  * コンパイルして出力されるJSがcommonjsとかamdの形式で保証されてる
      * Typescriptですら、いつでもやめられる
      * umdとかにすると両対応
  * とにかくJSでオブジェクト指向言語っぽく書ける
      * 個人的にはJavaっぽく書けるのがすごい楽
  * 欠点はimport周りで若干苦労しがち
      * 割りきった実装例出します
  * 参考ページ
      * [TypeScriptを使う理由](http://qiita.com/asaake88/items/a0f17d9f4f757d88a83a)
* karma
  * まあ、何でもよかったのですが、riotでテスト回す時に楽そうだったので
  * 参考ページ
      * [Riotのテストと宿命の反乱(karma-riot)](http://qiita.com/cognitom/items/5446493eba5198139471)

## 設定ファイル
上記の構成を実現するための各種設定ファイルです。

``` package.json
{
  "repository": {},
  "dependencies": {
    "babel-brunch": "^5.1.1",
    "brunch": "^1.8.5",
    "brunch-ts": "0.0.1",
    "clean-css-brunch": ">= 1.0 < 1.8",
    "css-brunch": ">= 1.0 < 1.8",
    "expect.js": "^0.3.1",
    "jade": "^1.11.0",
    "javascript-brunch": ">= 1.0 < 1.8",
    "karma": "^0.12.37",
    "karma-mocha": "^0.2.1",
    "karma-mocha-reporter": "^1.1.1",
    "karma-phantomjs-launcher": "^0.2.1",
    "karma-riot": "git://github.com/xtity/karma-riot.git",
    "mocha": "^2.3.4",
    "phantomjs": "^1.9.18",
    "riot": "git://github.com/xtity/riot.git",
    "riot-brunch": "0.0.2",
    "sass-brunch": "^1.8.10",
    "tsd": "^0.6.5",
    "typescript": "^1.6.2",
    "uglify-js-brunch": ">= 1.0 < 1.8"
  },
  "scripts": {
    "test": "./node_modules/karma/bin/karma start ./karma.conf.js"
  }
}

```

``` brunch-config.js
exports.config = {
  // See http://brunch.io/#documentation for docs.
  files: {
    javascripts: {
      joinTo: 'js/app.js'
      // joinTo: {
      //   'js/app.js': /^.*js$|^.*tag$/
      // }
      // To use a separate vendor.js bundle, specify two files path
      // https://github.com/brunch/brunch/blob/stable/docs/config.md#files
      // joinTo: {
      //  'js/app.js': /^(web\/static\/js)/,
      //  'js/vendor.js': /^(web\/static\/vendor)/
      // }
      //
      // To change the order of concatenation of files, explictly mention here
      // https://github.com/brunch/brunch/tree/stable/docs#concatenation
      // order: {
      //   before: [
      //     'web/static/vendor/js/jquery-2.1.1.js',
      //     'web/static/vendor/js/bootstrap.min.js'
      //   ]
      // }
    },
    stylesheets: {
      joinTo: 'css/app.css'
    },
    templates: {
      joinTo: 'js/app.js'
    }
  },

  conventions: {
    // This option sets where we should place non-css and non-js assets in.
    // By default, we set this to '/web/static/assets'. Files in this directory
    // will be copied to `paths.public`, which is "priv/static" by default.
    assets: /^(web\/static\/assets)/
  },

  // Phoenix paths configuration
  paths: {
    // Which directories to watch
    watched: [
      "web/static",
      // "test/static",
      "node_modules/riot-brunch/node_modules/riot/riot+compiler.min.js"
    ],

    // Where to compile files to
    public: "priv/static"
  },

  // Configure your plugins
  plugins: {
    babel: {
      // Do not use ES6 compiler in vendor code
      ignore: [/^(web\/static\/vendor)/]
    },
    brunchTypescript: {
      tscOption: "--module umd"
    },
    riot: {
      extension: 'tag',   // pattern overrides extension
      pattern: /\.tag$/,  // default
      template: 'jade'
    }
  },

  onCompile: function(generatedFiles) {
    console.log(generatedFiles);
    function puts(error, stdout, stderr) { console.log(stdout) }
    var exec = require('child_process').exec;
    exec("npm test", puts);
  }
};
```

``` karma.conf.js
'use strict';

var brunchConfig = require('./brunch-config').config;
var path = require('path');

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['mocha', 'riot'], // フレームワークにriotを指定
    plugins: [
      'karma-mocha',
      'karma-mocha-reporter',
      'karma-phantomjs-launcher',
      'karma-riot' // プラグイン指定
    ],
    files: [
      'node_modules/expect.js/index.js', // chaiとか好きなものでOK
      path.join(brunchConfig.paths.public, 'js/app.js'),
      'test/**/*.js' // テストファイルの指定
    ],
    preprocessors: {
      '**/*.tag': ['riot'] // プリプロセッサにriotを指定
    },
    browsers: ['PhantomJS'],
    reporters: ['mocha'],
    singleRun: true,
    logLevel: "debug",
    autoWatch: true
  })
}
```

``` tsconfig.json
{
    "compilerOptions": {
        "module": "umd"
    },
    "exclude": [
        "node_modules"
    ]
}
```

``` tsd.json
{
  "version": "v4",
  "repo": "borisyankov/DefinitelyTyped",
  "ref": "master",
  "path": "typings",
  "bundle": "typings/tsd.d.ts",
  "installed": {
    "riotjs/riotjs.d.ts": {
      "commit": "9c7621153358a6e51ec1ad5e822697b5aba3875a"
    },
    "jquery/jquery.d.ts": {
      "commit": "9c7621153358a6e51ec1ad5e822697b5aba3875a"
    },
    "node/node.d.ts": {
      "commit": "a95d16c8ca8c545bbe3989563271895aa3e14cfc"
    },
    "string/string.d.ts": {
      "commit": "421d6610bdb09e1015b71f5991d7a68d079eca21"
    }
  }
}
```

```bower.json
{
  "name": "hoge",
  "version": "0.0.0",
  "authors": [
    "you <you@yourdomain>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "materialize": "~0.97.0",
    "gitgraph.js": "~1.0.3"
  }
}
```

以上です。


## 次回予告
取り敢えず、必要な設定ファイルをだだっと紹介しました。
分かってる人ならこいつらをとりまプロジェクト直下におけば、
目指す環境は出来上がりです。

しかし、これでは上記のFWに不慣れな方にはまだまだとっつきにくいかもしれません。
なので、次回はこの設定ファイルを使って、実際にMVPっぽくカスタムタグを実装してみたいと思います。

それが終わったら、次々回で実装したコードのテストを書いてみます。

できればその３話構成で終わりたいと思いますが、はたしてすんなりいくかどうか。
このシリーズが終わったら、今度は１８０度反対側、datastoreの部分ができればと思います。

使うデータストアはずばり、[riak](http://docs.basho.com/riak/latest/) です。同じerlangで書かれたプロダクトなので、相性抜群です。
個人的には性能や堅牢性の両方を兼ね備えた現状ベストなデータストアだと思います。

お楽しみにーノシ

## 終わりに
はやいもので今年もアドカレの季節です。

まだまだやらなければいけないことは色々とありますが、
それらが全て終われば年末、お正月です。

コタツで寝っ転がりながらのElixir on Phoenix、
これもなかなか乙なものかもしれません。

それでは良いお年をーノシノシ