Ruby
Sinatra
SPA
vue.js
webpack

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

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

以前の記事で「webpack-dev-serverのproxy使えば?」とコメント頂きましたので、それを受けて全面的に書き直しました。(Thank you @nak1114)

完成したコードはこちらで見ることができます。

フロントエンドの準備

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

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

$ yarn global add vue-cli

vue templateを使います。

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

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

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

$ yarn install
$ yarn run dev

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

サーバーサイドの作成

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

$ bundle init
Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

ruby '2.4.1'

gem 'sinatra'
gem 'sinatra-contrib'
$ bundle install --path=vendor/bundle

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

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
config.ru
require './server'

run Server

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

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

$ bundle exec rackup

http://localhost:9292/companies.json

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

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

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

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

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

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

$ bundle exec rackup
$ yarn dev

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

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

<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を適当に変えてみてください。

  .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に吐かれます。

$ yarn run build

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

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を改良しましょう。

cofig.rb
+ require './static'
  require './server'

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

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

$ APP_ENV=production bundle exec rackup 

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