WebサイトのフロントエンドをVue.js でリプレースしてみた

#はじめに
運営してるゲームサークルのWebのフロントを `Vue.js` でリプレースした話です。

ちなみに、[実際のWebはこちら](https://gamelinksweb.herokuapp.com)

ソースは[こちら](https://github.com/S-H-GAMELINKS/Gamelinks.Web)

#開発環境
```
Windows 10 Pro
Rails:5.2.1
npm:6.1.0
yarn:1.7.0
Vue.js:2.5.16
```

#やったこと
##Webpackerの導入

もともとはRailsでWebサイトを構築しており、Vue.jsをwebpacker経由で導入する必要があった。

そのため[既存のRails環境にVue.jsを追加する](https://masaqu.id/2018-02-01/rails+vue.js.html)を参考にして以下のように導入した。

```ruby:Gemfile
gem 'webpacker'
```

まず、`Gemfile`に`webpacker`を追加して、`bundle install`

それから、以下のコマンドで`Vue.js`を導入。

```shell:terminal
rake webpacker:install
rake webpacker:install:vue
```

あとは、`application.html.erb`にこれを追加するだけ

```slim:application.html.slim
head
    = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
```

これで、`Vue.js`を既存のRailsアプリに導入できた。


##各ページをVue.jsへリプレース
###テンプレートの作成

うちのWebでは各ページごとにタイトルやちょっとした説明文などを書いてた。
その辺を`template.vue`と`template.js`で共通のコンポーネントにして使いまわすことにした。

```vue:template.vue
<template>
    <div id="template">
        <title>{{title}}</title>
        <h1>{{about}}</h1>
        <h2>{{message}}</h2>
        <br>
        <br>
        <br>
    </div>
</template>

<script>
export default {
  data: function () {
    return {
        title: page_title,
        about: page_about,
        message: page_message
    }
  }
}
</script>
```

```javascript:template.js
import Vue from 'vue/dist/vue.esm'
import Template from './components/template.vue'

document.addEventListener('DOMContentLoaded', () => {
  const el = document.body.appendChild(document.createElement('template'))
  const template = new Vue({
    el,
    render: h => h(Template)
  })

  console.log(template)
})
```

あとは、作ったテンプレートを各ページで以下のように呼び出してやるだけ
ちなみに、`page_title`などで各ページの説明文などを`JavaScript`経由で渡してる

```slim:about.html.slim
javascript:
  var page_title    = "About"; 
  var page_about    = "About";
  var page_message  = "「人と人を繋ぐゲームデザイン」をモットーに日々ゲーム制作に力を入れているゲームデザインサークルです。";

= javascript_pack_tag 'template'
```

### ModelのデータをVue.js 側に受け渡す

下記のように`JavaScript`を経由して、`JSON`形式で渡している

```slim:about.html.slim
javascript:
  var products = #{{@products.to_json}};
  var members = #{{@members.to_json}};

= javascript_pack_tag 'product'
= javascript_pack_tag 'member'
```

個別に作成しておいた`product.vue`などを`= javascript_pack_tag 'product'`で呼び出している。

```vue:product.vue
<template>
    <div id="product">
        <h1>Product</h1>
        <h2 v-for="p in product">
            <p><a href="p.link">{{p.name}}</a></p>
            <p>{{p.about}}</p>
            <br><br>
        </h2>
    </div>
</template>

<script>
export default {
  data: function () {
    return {
        product: products
    }
  }
}
</script>
```

```javascript:
import Vue from 'vue/dist/vue.esm'
import Product from './components/product.vue'

document.addEventListener('DOMContentLoaded', () => {
  const el = document.body.appendChild(document.createElement('product'))
  const product = new Vue({
    el,
    render: h => h(Product)
  })

  console.log(product)
})
```

キモは`product: products`と`<h2 v-for="p in product">`

`product: products`で`JSON`形式でRailsからデータを受け取り、`<h2 v-for="p in product">`でそれを一つづつ取り出してる。

だいたいのページはこれを流用する形でリプレースできた

###画像の動的描画

うちのWebには制作したノベルゲームの一覧ページがある。そのページではsplitメソッド、eachメソッドや`image_tag`を使って描画していた

```ruby:gamelinks_controller.rb
  def game
    @games = Game.page(params[:page]).per(PER)

    @games.each do |game|
      game.images = game.images.split(",")
    end
  end
```

```slim:game.html.slim
  - @games.each do |game|
    l
      h1
        = game.title
    - image_path = game.images.split(",")
    = image_tag image_path[0], :width =>"300", :height => "200"
    = image_tag image_path[1], :width =>"300", :height => "200"
    br
    br
    h2
      = game.about
    = button_to "ダウンロード", game.link
```

で、`Vue.js`でリプレースするにあたって以下のようにしてみた

```slim:game.html.slim
javascript:
  var page_title    = "Game"; 
  var page_about    = "Game";
  var page_message  = "ゲームリンクスでこれまでに制作してきたゲームです。ダウンロードボタンをクリックするとダウンロード先へ移動します。";
  var games = #{{@games.to_json}};

= javascript_pack_tag 'template'
= javascript_pack_tag 'game'
```

```vue:game.vue
<template>
    <div id="game">
        <h2 v-for="g in game">
            <p><h1>{{g.title}}</h1></p>
            <p v-for="img in g.images">
                <img :src="require('../../../assets/images/' + img.replace('\'./ ', ''))" width="300" height="200">
            </p>
            <p><h1>{{g.about}}</h1></p>
            <p><a href="g.link" class="button">ダウンロード</a></p>
        </h2>
    </div>
</template>

<script>

for(var i = 0; i < games.length; i++){
    games[i].images = games[i].images.split(",");

    for(var j = 0; j < 2; j++){
        games[i].images[j] = games[i].images[j].replace('"', '').replace('[', '').replace('"', '').replace(']', '').replace(' ', '');
        console.log(games[i].images[j]);
    }
}

export default {
  data: function () {
    return {
        game: games
    }
  }
}
</script>
```

```javascript:game.js
import Vue from 'vue/dist/vue.esm'
import Game from './components/game.vue'

document.addEventListener('DOMContentLoaded', () => {
  const el = document.body.appendChild(document.createElement('game'))
  const game = new Vue({
    el,
    render: h => h(Game)
  })

  console.log(game)
})
```

`var games = #{{@games.to_json}};`でデータを`JSON`形式で受け渡してる

それを`for`で回して成型

```javascript
for(var i = 0; i < games.length; i++){
    games[i].images = games[i].images.split(",");

    for(var j = 0; j < 2; j++){
        games[i].images[j] = games[i].images[j].replace('"', '').replace('[', '').replace('"', '').replace(']', '').replace(' ', '');
        console.log(games[i].images[j]);
    }
}
```

そして、`game`へとデータを渡している

```javascript
export default {
  data: function () {
    return {
        game: games
    }
  }
}
```

あとは`v-for`で画像ファイルの名前を一つずつとりだしている。

```vue
<template>
    <div id="game">
        <h2 v-for="g in game">
            <p><h1>{{g.title}}</h1></p>
            <p v-for="img in g.images">
                <img :src="require('../../../assets/images/' + img.replace('\'./ ', ''))" width="300" height="200">
            </p>
            <p><h1>{{g.about}}</h1></p>
            <p><a href="g.link" class="button">ダウンロード</a></p>
        </h2>
    </div>
</template>
```

`v-bind`した`src`と`require`で画像へのパスを読み込んで描画させている。

これで動的に画像を描画できた。


###スライダーの作成

トップページにスライダーを作っていたので、それも`Vue.js`で置き換えた。

元々のソースは以下

```slim:home.html.slim
title GAMELINKS

css class="text-align:center"
    == render 'slider'
```

```slim:_slider.htmlslim
.flexslider
  ul class="slides"
    - @slide.each do |slide|
        li
            = image_tag slide
```

```css:application.css
 *= require flexslider.css
```

```javascript:application.js
//= require jquery.flexslider

$(window).load(function() {
    $('.flexslider').flexslider({
      animation: "slide"
    });
  });
```

それを以下のように変更した。

```slim:home.html.slim
title GAMELINKS

javascript:
    var slides = #{{@slide.to_json}};

= javascript_pack_tag 'slide'
```

```vue:slide.vue
<template>
<div id="slide">
  <slider animation="fade" height="600px">
    <p style="line-height: 600px; font-size: 5rem; text-align: center;" v-if="!list.length">Loading...</p>
    <slider-item v-for="i in list">
        <p style="line-height: 680px; font-size: 5rem; text-align: center;">
            <img :src="require('../../../assets/images/' + i.path)" width="800" height="600" >
        </p>
    </slider-item>
  </slider>
</div>    
</template>

<style>
p {
  margin: 0;
}
</style>

<script>
import { Slider, SliderItem } from 'vue-easy-slider'

var array = [];

for(var i = 0; i < slides.length; i++){
    array.push({path: slides[i]});
}

export default {
    data: function () {
        return {
            list: array
        }
    },
    mounted () {
        setTimeout(() => {
            this.list
        }, 1000)
    },

    components: {
        'Slider': Slider,
        'SliderItem': SliderItem
    }
}
</script>

```

```javascript:slide.js
import Vue from 'vue/dist/vue.esm'
import Slide from './components/slide.vue'

document.addEventListener('DOMContentLoaded', () => {
  const el = document.body.appendChild(document.createElement('slide'))
  const slide = new Vue({
    el,
    render: h => h(Slide)
  })

  console.log(slide)
})
```

`vue-easy-slider`という`Vue.js`で使えるシンプルなスライダーがあったのでそれを使っている。

`@slide`を例のごとく`JSON`で渡してやり、それを`vue-easy-slider`で扱えるデータに成型しなおしている

あとは画像の動的描画で使ったのと同じ方法(`v-for` + `v-bind:src` + `require`)で画像を読み込んで表示させている。


#おわりに

とりあえず、こんな感じで凡そのページは改修できた。
まだいくつか手を加えていきたいのでその辺はまた追記していこうかと思う。

あと、今回初めてまもとに`JavaScript`を書いたのでおかしなところがあれば編集リクエストなどを投げていただければと思います。

#参考資料

[既存のRails環境にVue.jsを追加する](https://masaqu.id/2018-02-01/rails+vue.js.html)

[Vue.jsの外から情報を渡したい](https://qiita.com/rinsyan0518/items/3f125ededf66723ce3d6)

[RailsのオブジェクトをJavaScriptに渡す](https://qiita.com/ttaka66/items/8f2cadb39d8db7562f2a)

[RailsからJavaScriptへ配列を渡す方法](https://qiita.com/beaa/items/1649108c5f3bfc59be21)

[shhdgit/vue-easy-slider](https://github.com/shhdgit/vue-easy-slider)

[Vue.js](https://jp.vuejs.org/index.html)