vue.js
nuxt.js
adventcalendar2017
Vue.js #2Day 16

Web初心者がVue.jsから始めてnuxt.jsでサイトを作ったときにやったことまとめ

はじめに

これはVue.js #2 Advent Calendar 2017の16日目の記事です
ぼくの前日は ykhirao - Qiitaさん、Vue.jsでv-forをネストする - Qiitaでした

本記事はぼくがWebの初心者なだけなので、そこまで初心者向けではないかもしれません
 

追記:2018/11/30

2017/12/27に@altさん、
2017/12/18, 2018/09/05に@khskさん
2018/10/22に@tryforthさん、
2018/11/29に@masalennonさんから修正を頂きました
ありがとうございます!😊

ぼくのバックエンド

ぼくがどれくらいWeb初心者かをざっくりと説明すると今までの仕事の9割はiOSで1割はAndroidです。そのうちNativeアプリ率は100%で、JSを使用した回数は0回です
そんな中お客さんのWebサイトのリニューアルのお話が出てきたのでその話です

本記事ではおそらくもっと良い方法や、こうする必要はないということなどが見受けられると思います。コメントで指摘してもらえると幸いです :)

サンプル

大本のサイトの話をするわけにはいかないので同じような例として以下のようなサイトを作ることになったということにします

カードを紹介するサイトをリニューアルする
・Magic the Gatheringのカードを表示する
・同上のカードを検索する
・購入機能はない

というようなざっくりとした案件です

最終的に使ったいろいろ

細かいものは除きますが以下のようなライブラリ群に最終的になりました
()内は採用理由です

・Vue.js(学習コストの低さと後からのメンテを頼みやすいかどうか)
・Bulma(buefy) (Bulma自体のファイルサイズの小ささと単純さ)
・Algolia(検索機能が必要だったがfirebaseには良い検索がなかった)
・Cloudinary(管理者が画像を追加する必要があり、画像編集を行える上にHTTP/2に対応しているなどあったため)
・Nuxt.js(後述)
・now.sh(Nuxt.jsがそのまま動かせてデプロイも簡単)

Vue.jsは入っていますがSEO関係の都合上SPAにする利点が今回はなかったので実質Nuxt.jsで使っているだけです

作り始める

実際にサイトを作り始めていきます
インストールの説明は記述してますが、各種ライブラリの使い方に関しては公式サイト等を閲覧してください

Node.jsをインストールする

まずはnpmが使えないことには何も始まらないのでNode.jsをインストールします
nodejs.orgからLTSをダウンロードします(記事執筆時点では8.9.3でした)

yarnをインストールする

npmを使うと気を利かせてくれない事が多かったのでyarnを使うことにしました

npm i -g yarn

でインストールできます

Nuxt.jsを始める

インストール - Nuxt.jsをみるとスタータープロジェクトを使うにはどうやらvue-cliをまずはインストールするのが良さそうです

yarn global add vue-cli

でインストールできます

インストールしたらstarter-templateを使ってプロジェクトを作ってみます

vue init nuxt-community/starter-template mtgcardsite

これでmtgcardsiteというプロジェクトが出来上がりました
プロジェクトディレクトリに入ってnode_modulesを作り、実行してみましょう

cd mtgcardsite
yarn
yarn dev

記事執筆時点ではvue.jsとvue-server-rendererとvue-template-compilerのバージョンが合わないとエラーがでたため

yarn add vue-server-renderer vue-template-compiler

を実行してバージョンをあげました
もう一度yarn devを実行すると
http://localhost:3000 でnuxtのロゴが表示されているはずです

Buefyをインストールする

実際の開発ではBulmaをインストールしてその後Buefyに移行していますが、ここではBuefyのインストール方法のみを記述します

Bulmaのみのインストール方法を知りたい方はこちらを参考にしてください
@nuxtjs/bulma

yarnで追加する

まずはnode_modulesにBuefyを追加します

yarn add buefy

pluginを作る

サイト内のすべてのHTMLでBuefy(Bulma)を使用するのでpluginを作りnuxt.config.jsにpluginを登録します

pluginsディレクトリの中にbuefy.jsという名前でファイルを作ります

plugins/buefy.js
import Vue from 'vue'
import Buefy from 'buefy'
import 'buefy/dist/buefy.css'

Vue.use(Buefy)

これだと全てのBuefyのコンポーネントを使用できるようにしていますが、今回はAutoCompleteのみ使えればよかったので最終的には以下のようになりました

plugins/buefy.js
import Vue from 'vue'
import Buefy from 'buefy'
import 'buefy/dist/buefy.css'

Vue.component(Buefy.Field.name, Buefy.Field)
Vue.component(Buefy.Autocomplete.name, Buefy.Autocomplete)

pluginを登録する

作ったpluginをnuxt.config.jsに登録します

nuxt.config.js
module.exports = {
...
  plugins: [
    '~plugins/buefy'
  ],
}

yarn devを実行するとBuefyがインストールされた状態でさきほどのサイトが表示されます

Warningが出ますがどう解決していいかはわかりません(´・ω・`)
ですがlayout/default.vueに

<style src="buefy/dist/buefy.css"></style>

を足して、pluginの方からcssのimportを消すとwarningは消えます

algoliaでカードを検索する

algoliaをつかってカードの検索機能をつくってみましょう

アカウントを作る

https://www.algolia.com/users/sign_up
こちらからアカウントを作り、チュートリアルをまず終わらせましょう

indexを作成する

indices -> ADD NEW INDEXから作成できます
Cardsという名前のindexを作成します

今のままだとカードのデータがないので、mtgjson.comから入手します
全てのカードデータを入れるとalgoliaの無料枠を超えてしまうので、記事執筆時点のスタンダードというフォーマットの最新セットイクサランのjsonだけ取得します
https://mtgjson.com/json/XLN.json

algoliaに登録する際はrootがjsonの配列である必要があるので、上のjsonファイルからcardsのみ抽出したものをindexに登録します
(おそらく抽出したデータを再配布するのは規約違反なのでここでは差し控えます)

登録が終わりましたがこのまま検索すると全てのプロパティに対して検索するので精度がよろしくないです。本来はもっと複雑な形にしますが今回はnameだけ検索するように変更します。

indices -> RANKING -> Searchable Attributesにnameを設定します

これで検索時にnameだけを検索するようになりました
(もちろん本当のアプリではもっと複雑に検索できるようにしています)

アプリ側に検索を実装する

JS側から検索ができるようにしていきます
まずはalgoliaのJS SDKをインストールします

yarn add algoliasearch

インストールが終わったらまずは検索のUIを作っていきます

<template>
  <div>
    <div>
      <div class="container" style="padding-left: 5%;padding-right: 5%">
        <div class="column">
          <label class="subtitle" style="">Search</label>
        </div>
        <div class="field">
          <input class="input" v-model="algoliaResults.name"/>
        </div>
        <div class="container">
          <div class="columns is-mobile is-multiline is-centered">
            <div class="column is-narrow" v-for="card in algoliaResults.data" :key="card.id">
              <figure class="image" style="width: 111px;height: 155px">
                <img :src="cardImage(card)">
              </figure>
            </div>
          </div>
        </div>
      </div>
      <br>
    </div>
  </div>
</template>
<script>
  import algoliasearch from 'algoliasearch'

  export default {
    data() {
      return {
        algoliaResults: {
          data: [],
          name: ''
        }
      }
    },
    methods: {
      cardImage(card) {
        return 'http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=' + card.multiverseid + '&type=card'
      }
    },
    watch: {
      'algoliaResults.name': function () {}
    }
  }
</script>

というわけでざっくりつくってみました
inputの入力がalgoliaResults.nameとBindingされているので、それの監視メソッドに検索を実装します

algoliasearchはclientとindexのインスタンスを生成する必要があるのでdata内で先に生成しておきましょう

<script>
  import algoliasearch from 'algoliasearch'
  export default {
    data() {
      var client = algoliasearch('YourApplicationID', 'YourAPIKey')
      var index = client.initIndex('Cards')
      return {
        algoliaResults: {
          data: [],
          name: ''
        },
        client: client,
        index: index
      }
    }
  }
</script>

indexのインスタンスを生成したらsearchメソッドを使って検索をします

    watch: {
      'algoliaResults.name': function () {
        if (this.algoliaResults.name === '') {
          this.algoliaResults.data = []
          return
        }

        this.index.search(this.algoliaResults.name, function (err, content) {
          if (err) {
            throw err
          }
          this.algoliaResults.data = content.hits
        }.bind(this))
      }
    }

これでinputにカード名を入力するとカードの画像がでてきます😊

Cloudinaryに画像を登録し、適切な形で取得できるようにする

さきほどのソースコードのなかでcardImageという関数でgathererというサイトの画像のURLを生成していますが、gathererはHTTP/2に対応していない上httpsでもありません
のでこちらでCloudinaryに画像を登録し直して使うことにします

サイトはこちらです
Cloudinary - Cloud image and video service, upload, storage & CDN

Cloudinaryでは画像のフォーマット、クオリティ、サイズを適切な形に変える事が可能で、たとえばChromeでサイトを見る際にはWebPを返すようにすることができます

アカウントを作成したらまずはgathererからCloudianryに画像をあげなければ行けませんが、今回の1カードセットだけでも299枚分あるので手作業では面倒です

なのでCloudinaryのnode.js SDKを使ってさくっとuploaderを書きます
新しいProjectを作りyarn add cloudinaryをしてindex.jsを作ります

uploader
var cloudinary = require('cloudinary')

cloudinary.config({
  cloud_name: 'your_cloud_name',
  api_key: 'your_api_key',
  api_secret: 'your_api_secret'
})

function gathererUrl (id) {
  return 'http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=' + id + '&type=card'
}

for (let id = 435152; id <= 435450; id++) {
  cloudinary.v2.uploader.upload(gathererUrl(id), {
    public_id: id
  }, function (error, result) {
    if(error) {
      console.log(gathererUrl(id))
    }
  })
}

cloudnameやapikeyなどはCloudinaryのdashboardに表示されているのでそちらを使います

これをnodeで実行してuploadさせます
たまに失敗するので失敗した分は複数回実行するなり手でUploadするなりして補ってください

画像が上がったところで先程のcardImageを少し変更します

ぼくのCloudinaryのアカウントのcloud_nameはdi6y1fplmなのでcardImageはこのように変わります

cardImage (card) {
  return 'https://res.cloudinary.com/di6y1fplm/image/upload/' + card.multiverseid
}

このままですと元の画像がくるだけなので、自動でフォーマットとクオリティを変更し、サイズを元の画像の半分の111px x 155pxなthumbnail画像を送るようにするとこうなります

cardImage (card) {
  return 'https://res.cloudinary.com/di6y1fplm/image/upload/c_thumb,f_auto,h_155,q_auto,w_111/' + card.multiverseid
}

追加でPWA対応をする

後からPWA対応をしたいと言われました
(いまのところPWAフレンドリーなUIになってないとかは禁句です)

nuxtjsには幸いPWAモジュールがあるのでそれを加えます
GitHub - nuxt-community/pwa-module: ⚡ Supercharge Nuxt with a heavily tested, updated and stable PWA solution

yarn add @nuxtjs/pwa

インストール後@nuxtjs/pwaをnuxt.config.jsに追記します

module.exports = {
...
    modules: [
    '@nuxtjs/pwa'
  ],
...
}

nuxtjs/pwaはキャッシュなどの設定は勝手にやってくれます
その他のPWAのいわゆるmanifestに設定する項目はnuxt.config.jsに書きます

module.exports = {
...
  manifest: {
    name: 'MtG Card Search',
    short_name: 'MtGSearch',
    description: 'Sample demo',
    lang: 'ja',
    theme_color: '#fff',
    background_color: '#FAFAFA',
  },
...
}

あとはiconが必要なので512x512のiconをstaticディレクトリにいれればおしまいです

これでAndroidでダウンロードダイアログが出現するようになるはずです
それを確かめるためにnow.shにデプロイしてみましょう

now.shにdeployする

now.shはNext.jsを作ったことで有名なzeitという会社が作っているホスティングサービスで、deployが単純でnuxt.jsが特に特別な設定などなく実行できるのがいいところです

Now – Realtime global deployments

やることは簡単でNow Desktopをインストールしてアカウントを作成し、ターミナルでnowと実行するだけです

本記事執筆時にはバージョンが1.0の時はnowと実行するだけでよかったのですが、今はバージョンが2.0になったのでそのままだとうまくいきません。
now.jsonを作成し、

{
  "version": 1,
}

としてバージョンを1に指定してやるとnowとターミナルで実行しただけでデプロイできます。
(@masalennonさんありがとうございます)

というわけで今回実装したもの+ちょっとだけ追加したものをdeployしたのがこちらです

https://mtgcardsite-ecelauvuwz.now.sh/

このサイトをLightHouseにかけてみると…

😊😊😊😊😊😊😊😊😊

終わりに

というわけでいかがだったでしょうか?
もっとごてごてさせようと思ったら時間が足りなくて駆け足になったりすごい単純なものになってしましました
実際の案件では実はこうなんだけど省略している部分多々ありますが誰かの参考になれば良いなぁと思います

今回のソースコードはこちらです
GitHub - corosukeK/mtgcardsite

明日はOJI3T - Qiitaさんです!