Sinatra+Vue.jsでSPA(Hot Module Replacementつき) Take 2

Sinatra+Vue.jsでSPAを作っていきます。

[以前の記事](https://qiita.com/isuke/items/6df8ab9bea944b23f65d)で「webpack-dev-serverのproxy使えば?」とコメント頂きましたので、それを受けて全面的に書き直しました。(Thank you @nak1114)

完成したコードは[こちら](https://github.com/isuke/sinatra-vuejs-template/tree/v1.0.0)で見ることができます。 

## フロントエンドの準備

yarnを使うので、インストールしていない人はインストールしてください。

vue-cliを使うのでインストールします。

```shell-session
$ yarn global add vue-cli
```
vue templateを使います。

```shell-session
$ vue init isuke/vuejs-template#v1.0.0 sinatara-vue-sample
```

`isuke/vuejs-template`は著者が、公式の`vuejs-templates/webpack-simple`をもとに作ったvue.jsのテンプレートです。
大分改造してあるので原型がおぼろげです。

`vue init`したら指示に従って以下のコマンド実行します。

```shell-session
$ yarn install
$ yarn run dev
```

http://localhost:8080/ をブラウザで開いてみましょう(といか勝手に開きます)。
一行もコードを書かずにこれだけでwebページができていることがわかると思います。
[Bye]リンクをクリックしてページ遷移ができていることも確認してみてください。

## サーバーサイドの作成

ここからsinatraを使っていきます。

```shell-session
$ bundle init
```

```rb:Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

ruby '2.4.1'

gem 'sinatra'
gem 'sinatra-contrib'
```

```shell-session
$ bundle install --path=vendor/bundle
```

sinatraのコードを書いていきます。

```rb:server.rb
require 'sinatra/base'
require "sinatra/reloader"

class Server < Sinatra::Base
  configure :development do
    register Sinatra::Reloader
  end

  get '/companies.json' do
    companies = []
    1.upto(50) do |i|
      companies << {
        id: i,
        name: "Company #{i}"
      }
    end
    companies.to_json
  end
end
```

```rb:config.ru
require './server'

run Server
```

何の変哲もない　APIが一つあるだけです。

立ち上げて、確認してみましょう。

```shell-session
$ bundle exec rackup
```

http://localhost:9292/companies.json

jsonが返ってきました。
次のステップに進みましょう。

# フロントとサーバーの連携

フロントは`npm run dev`でlocalhost:8080
サーバーは`bundle exec rackup`でlocalhost:9292
にそれぞれアクセスできるようになりました。
これをくっつけましょう。

`webpack.config.cofffee`に以下の行を追加します。

```diff:webpack.config.cofffee
  else
    config = merge baseConfig,
      output:
        filename: 'build.js'
      devtool: '#eval-source-map'
      devServer:
        contentBase: 'dist'
        historyApiFallback: true
        noInfo: true
+       proxy:
+         "/api":
+           target: "http://localhost:9292"
+           pathRewrite: {"^/api" : ""}
      performance:
        hints: false

  module.exports = config
```

サーバーを２つとも立ち上げて http://localhost:8080 を見てみましょう。

```shell-session
$ bundle exec rackup
$ yarn dev
```

ここでトップページが見れたらOKです。
さて、これでフロントからAPIが叩けるようになったので、ちょっと試してみましょう。

Bye.vueを以下のように書き換えます。

```html
<template lang="pug">
.bye
  button(@click="load") load
  ul(v-for="company in companies")
    li {{company.name}}
  router-link(:to="{ name: 'top'}") TOP
</template>

<script lang="coffee">
export default
  data: ->
    companies: []
  methods:
    load: ->
      axios.get('/api/companies.json')
        .then (responce) =>
          @companies = responce.data
</script>
```

[load]ボタンをおしてデータが表示されればOKです。

ついでにHot Module Replacementが動作しているかもためしてみましょう。

Compnayデータが表示された状態で`Bye.vue`のstyleを適当に変えてみてください。

```diff
  .bye
    -webkit-font-smoothing: antialiased
    -moz-osx-font-smoothing: grayscale
    text-align: center
    margin-top: 60px
+   background-color: red

    h1
      font-weight: normal
    a
      color: #42b983
```

ブラウザを**更新せずに**反映されたら成功です。
Hot Module Replacementが動作しています。

## Productionモードで動かす

最後にProductionでの動かし方です。

まず`yarn run build`を実行して`src`ディレクトリの中をコンパイルします。
コンパイルした結果は`dist`に吐かれます。

```shell-session
$ yarn run build
```

次に、静的ファイル配信用のサーバーを作ります。

```rb:static.rb
require 'sinatra/base'

class Static < Sinatra::Base
  configure :production do
    set :public_dir, File.join(settings.root, 'dist')
  end

  get '/' do
    send_file File.join(settings.public_dir, 'index.html')
  end
end
```

`dist`ディレクリをpublicとして公開するディレクトリに指定しています。
また、`/`にアクセスしたときに、'dist/index.html'を返しています。

`static.rb`を使うように`config.ru`を改良しましょう。

```diff:cofig.rb
+ require './static'
  require './server'

+ if ENV["APP_ENV"] == "production"
+   run Rack::URLMap.new(
+     '/' => Static,
+     '/api' => Server,
+   )
+ else
    run Server
+ end
```

あとは環境変数をつけて実行してあげるだけです。

```shell-session
$ APP_ENV=production bundle exec rackup 
```

以上です。お疲れ様でした。