Riot.js + webpack + babel でitunes APIを利用したWEBアプリを作ってみる

javascriptは、jQueryを少し触った程度の私ですが、Riot.jsの公式でも紹介されている[こちら](https://github.com/riot/examples)のLive Ajax Searchをベースに、ES2015やRiot.jsを使ってwebアプリを作成しました。
この記事では、どんな資料を参考にしたか、どんなことに躓いたか、備忘的に記したいと思います。

完成したwebアプリは[こちら](https://github.com/higeaaa/riot-webpack)に公開していますので、興味がお有りでしたら遊んでみてください。
（はじめてgithubにリポジトリを公開しました。`.gitignore`を作る前に`commit`していたため、node_modulesフォルダもアップロードされてしまい、ドキドキしながら対処しました。）

```shell-session
git clone https://github.com/higeaaa/riot-webpack.git
cd riot-webpack
npm install
npm start 
```

# 環境
- node v7.2.0
- npm  v3.10.9

```package.jsonの抜粋.json
  "devDependencies": {
    "babel": "^6.5.2",
    "babel-core": "^6.21.0",
    "babel-loader": "^6.2.10",
    "babel-preset-es2015-riot": "^1.1.0",
    "eslint": "^3.13.1",
    "eslint-loader": "^1.6.1",
    "eslint-plugin-riot": "^0.1.6",
    "file-loader": "^0.9.0",
    "font-awesome": "^4.7.0",
    "font-awesome-webpack": "0.0.4",
    "node-sass": "^4.3.0",
    "riot": "^3.0.7",
    "riotjs-loader": "^4.0.0",
    "url-loader": "^0.5.7",
    "webpack": "^1.14.0",
    "webpack-dev-server": "^1.16.2"
  }

```
 

# 環境構築

## webpackの設定
たくさん困った結果が`webpack.config.js`にあらわれています。
1. cors
2. ES6からES5にトランスパイルされない
3. font-awesomeを使いたい


```webpack.config.js
const webpack = require('webpack')

module.exports = {
  entry: './app/main.js',
  output: {
    path: '/public',
    publicPath: '/public/',
    filename: 'bundle.js',
    crossOriginLoading: 'anonymous'
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.ProvidePlugin({ riot: 'riot' })
  ],
  module: {
    preLoaders: [
      {
//        test   : /\.js$|.tag$|.es6$/, <= k-kuwaharaさんからご指摘いただき、以下に修正
        test   : /\.js|\.tag|\.es6$/,
        exclude: /node_modules/,
        loader : 'eslint-loader'
      },
      {
        test   : /\.tag$/,
        exclude: /node_modules/,
        loader : 'riotjs-loader'
      }
    ],
    loaders: [
      {
        test   : /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: "url-loader?limit=10000&minetype=application/font-woff"
      },
      { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: "file-loader"
      }
    ],
    postLoaders: [
      {
//        test   : /\.js$|\.tag$/,　<=　k-kuwaharaさんからご指摘いただき、以下に修正
        test   : /\.js|\.tag$/,
        exclude: /node_modules/,
        loader : 'babel-loader',
        query  : {
          presets: ['es2015-riot']
        }
      }
    ]
  },
  devServer: {
    historyApiFallback: true,
    watchOptions: { aggregateTimeout: 300, poll: 1000 },
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
      "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
    }
  }

}

```

### 1. CORS問題 => 解決
fetch()をすると、chromeにCORSがなんちゃらと怒られ、APIで取得して表示ができない。
CORSについては[こちら](http://dev.classmethod.jp/cloud/cors-cross-origin-resource-sharing-cross-domain/)がわかりやすかった。
`webpack.config.js`の`devServer`の`header`にいろいろ設定しましたが、うまくいかなかったので、結局google chromeの拡張で[CORS Toggle](https://chrome.google.com/webstore/detail/cors-toggle/omcncfnpmcabckcddookmnajignpffnh)を利用することで、解決しました。

### 2. ES6からES5にトランスパイルされない => 未解決 => 解決
CORS問題が解決し、動いているのを確認していたら、javascriptがES5にトランスパイルされておらず、chromeがES6を直に読み込んで動いていることがわかりました。例えば、ファイル`search.tag`に`<script type='babel'>`とすると、riotのイベントハンドラとして作った`search(e){}`が、syntax errorとなってしまい、困ってしまいました。
（ちなみに、`<script>`とすると、`riotjs-loader`が`this.search = function(e) {}`としてくれます。）

その後、`loader`の`query`にいろいろな`type`や`presets`を設定しましたが、うまくいかず。また、`preLoader`（最初に読み込むローダーを指定する）に`riotjs-loader`、`postLoader`（最後に読み込むローダーを指定する）に`babel-loader`と設定しましたが、やはりbabelがES5にトランスパイルしてくれません。

調べていたところ、[こちらで](https://github.com/esnunes/riotjs-loader/issues/29)話題になってもいましたが、結局解決できず、ES6のままブラウザに読み込ますようにします。

<<2017/1/24 追記>>
webpack.config.jsの設定誤りであることがご指摘でわかりました。上記のwebpack.config.jsには反映済みです。k-kuwaharaさん、ありがとうございました。

### 3. font-awesomeを使いたい => 解決
なんとなく使ってみたかった。[公式HPのget-started](http://fontawesome.io/get-started/)を見ると、CDNを利用するか、ダウンロード、railsならgemがあるよと書いてある。調べてみると[NPMのパッケージ](https://www.npmjs.com/package/font-awesome-webpack)があったので使った。
`webpack.config.js`でローダーの設定をして、jsファイルでrequireすればよい。今回は、webpackが最初に読み込むjsファイル`main.js`でrequireする。

## sassを使いたい
なんとなく使ってみたかった。[node-sass](https://www.npmjs.com/package/node-sass)を入れて、各tagファイルの`style`に`type='scss'`と追記する。


# アプリ作成
基本は[こちら](https://github.com/riot/examples)のLive Ajax Searchをベースに、itunes APIを利用して、itunesにある曲を検索する、という簡単なアプリを作りました。riotを体感することも楽しかったのですが、`debounce()`や`fetch()`に触れることができて、楽しかったです。

アプリは、2つのtagファイルで構成されます。

- `search.tag`:　検索フォームやitunes APIを使って情報を取得する
- `result-cards`:　`search.tag`で取得した検索結果をカード形式で表示する

```js:search.tag
<search>
  <form onsubmit={ search }>
    <label>
      <input type="search"
             oninput={ search }
             onchange={ search }
             ref="s"
             placeholder="&#xf002; Search music tracks"
             id="search">
    </label>
  </form>

  <div if={ isLoading } class="loader">
    <div class="circle"> </div>
  </div>

  <p class="error" if={ error }>{ error }</p>


  <script>
    require('./results-cards.tag');

    var lastSearch = null;
    var searchUrl  = null;

    // Reset tag attributes to hide the errors and cleaning the results list
    let reset = () => {
      this.results = [];
      this.error   = false;
      searchUrl    = null;
    }

    // generate url for itunes API.
    let getApiUrl = searchWord => {
      let urlParams = {
        country: 'jp',
        lang: 'ja_jp',
        media: 'music',
        entity: 'song',
        app: 'music',
        limit: 15,
        term: searchWord
      };
      let url = 'https://itunes.apple.com/search';
      let keyValue, pat, key, value;

      for (key in urlParams) {
        key      = encodeURIComponent(key);
        value    = encodeURIComponent(urlParams[key]);
        keyValue = key + '=' + value;
        url += (url.length > 32 ? '&' : '?') + keyValue;
      };
      return url;
    }

    // Debounce the api requests
    let doApiRequest = debounce( searchUrl => {
      fetch(searchUrl, { method: 'get', mode: 'cors'})
        .then( res  => { return res.json() })
        .then( data => {
          reset();
          var r;
          if (this.refs.s.value) {
            if (data.resultCount) this.results = data.results;
            else this.error = data.Error;
          }
          this.isLoading = false;
          this.update();
          r = this.results;
          riot.mount('results-cards', { r });
        });
    }, 300, false);

    // Public API/method
    this.results = [];

    // Search callback
    search(e) {
      let searchWord = this.refs.s.value;
      if (!searchWord) {
        reset();
        setTimeout(function(){
          var r = [];
          riot.mount('results-cards', { r });
        }, 1000);
      } else if (lastSearch !== searchWord) {
        reset();
        this.isLoading = true;
        searchUrl = getApiUrl(searchWord)
        doApiRequest(searchUrl);
      };

      lastSearch = searchWord ;
    }

  </script>
  <style>
    省略
  </style>
</search>

```

```js:result-cards
<results-cards riot-tag='resultCards'>
  <div if={ opts.r.length } class="container">
    <a each={ opts.r } href="{ collectionViewUrl }" target="_blank">
      <div class="content">
        <img class="artwork" src={ artworkUrl100 } alt="">
        <div class="description">
          <label>{ trackName }</label>
          <span>{ collectionName } </span>
          <span>{ artistName }</span>
          <div class="logos">
            <img src="/public/img/Get_it_on_iTunes_Badge_JP_1214.svg" alt="">
            <img if={ isStreamable } src="/public/img/JP_Listen_on_Apple_Music_Badge.svg" alt="">
          </div>
        </div>
      </div>
    </a>
  </div>


  <style type="scss">
    省略
  </style>
</results-cards>

```
Riotの感想は次の通りです。
1. イベントハンドラがわかりやすい
2. refsがわかりやすい

### 1. イベントハンドラがわかりやすい
`input oninput={search}`でinput要素に入力があったら`search(e)`が発火する、という具合でとてもわかりやすい。

### 2. refsがわかりやすい
`input ref='s'`とすれば、javascriptから`this.refs.s.value`でinput要素に入力された値が取得できる、という具合でとてもわかりやすい。


また、次のことを備忘として記します。
3. itunes API
4. cssのflexbox

### 3. itunes API
[アップル](https://affiliate.itunes.apple.com/resources/documentation/itunes_search_api_jp/)が詳しい。ただ、`entity`をsongにしてもmusicにしても検索結果は一緒だった。検索結果の順番の指定方法はわからなかった。
results-cardsでapple musicでも視聴可能な場合、apple musicのロゴを表示している。この判定にitunes APIの戻り値にある`isStreamable`が`true`であればapple musicで視聴可能としました。ドキュメントに記載はありませんでしたが、数曲調べたところ、多分あっていると思います。

### 4. cssのflexbox
`results-cards`の表示や、カードの中身について、flexboxを利用しました。[コリス](http://coliss.com/articles/build-websites/operation/css/css3-flexbox-properties-by-scotch.html)が詳しい。
親要素を`display: 'flex'`など、中身をどう配置したいかを設定し、あとは子要素で設定する。floatよりもなんとなくイメージがつかみやすい。

# 次にやること
- Rails5のAPIと、このようなjsの環境を使って何か作りたいです。

# あとがき
riotは、javascriptを学んでいくとやれることが簡単にできそうな気がして、わくわくします。