すごくカンタンなSSR with Vue.js - 実践2. SPA

「御託はいいからやり方だけ教えてくれ」というあなたは　→　[実践 - Vue.jsでSSR](https://github.com/nkzttt/vue-ssr-test/blob/master/guide/ja.md)

# この記事は

別途私が書いている[実践 - Vue.jsでSSR](https://github.com/nkzttt/vue-ssr-test/blob/master/guide/ja.md)に、割とどうでもいい私の感情を交えながら、いくつかのパートに分けて書いていく実践形式のSSR with Vue.js説明記事です。
実際の開発フローに沿って、なるべく予備知識がなくても読み進められるように心がけています。

[実践1. ビルド環境](https://qiita.com/bgn_nakazato/items/e57a7a293c3bb23de5e6)
実践2. SPA　←　当記事
[実践3. SSR](https://qiita.com/bgn_nakazato/items/93ecffca36d6f7d0ec6d)

# 誰に役立つ？

私のような

- フロントエンジニアなりにSSRの実際のやり方を知りたくてググったのに概念的なものしか見つけられない~~検索能力の低い~~人
- 意を決して[サーバサイドレンダリング — Vue.js](https://jp.vuejs.org/v2/guide/ssr.html)を通してやったのに、途中から「あれ、なんか違う」ってなって結局出来なかった~~理解力の低い~~人
- 概要をザクッと知ってあとは自分流にやっていきたい人

# ゴール

**SSR環境を構築でき、拡張することができる**

- 極シンプルなSSR環境を構築します
- ジェネレータ的なものは使わず、全体の概要を理解しながら進めていきます

# 実践2. SPA

Vue.jsで簡単なSPAを作っていきます。

## 単一ファイルコンポーネント

単一ファイルコンポーネント（`.vue`）を使えるようにしていきましょう。

必要パッケージをインストールします。

```bash
npm i -S vue
```

```bash
npm i -D vue-loader
```

`vue`はブラウザ上で使用するため、`--save`のエイリアス`-S`でインストールしておきます。

まずはブラウザ上で動かすコードから書いていきます。

`vue`を動かせるように`index.html`を作ります。
重要なのはビルド結果の`dist/main.js`を読み込むことと、マウント先となる空div`<div id="app"></div>`を置くことです。
`dist/main.js`は`DOMContentLoaded`後に走らせるように`script`タグに`defer`属性を追加しておきます。

**index.html**

```html
<!doctype html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>title</title>
  <script src="./dist/main.js" defer></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

```

`src/index.js`を`vue`を使用するように変更します。
マウント先に`#app`を、レンダリングにコンポーネント`App.vue`を指定します。

**src/index.js**

```js
import Vue from 'vue';
import App from './App.vue';

new Vue({
  el: '#app',
  render: h => h(App)
});
```

コンポーネント`App.vue`を作成します。

**src/App.vue**

```html
<template>
  <p>{{message}}</p>
</template>

<script>
  export default {
    data() {
      return {
        message: 'message'
      }
    }
  }
</script>
```

これでブラウザ上で`vue`を動かす準備は出来ました。

次に単一ファイルコンポーネントをコンパイルできるように`webpack.config.js`を変更します。

**webpack.config.js**

```js
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

const config = {
  entry: path.join(__dirname, 'src/index.js'),
  output: {
    filename: 'main.js',
    path: path.join(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ]
};
```

ビルドして表示してみましょう。

```bash
npm run build:dev
```

ローカルサーバーを任意の方法で立ち上げてください。
macOSであれば以下のコマンドが利用できます。

```bash
# プロジェクトルートにて
python -m SimpleHTTPServer 8000

# localhost:8000にアクセス可能
# Ctrl + c で終了
```

「message」という文字列が表示されていれば成功です。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/100188/3a3768f6-9a55-e97c-761f-7a810ecade6e.png)

コンポーネント内でcssも書けるようにしておきましょう。
`src/App.vue`にcssを追加します。

**src/App.vue**

```html
<template>
  <p class="message">{{message}}</p>
</template>

...

<style>
  .message {
    color: red;
  }
</style>
```

必要パッケージをインストールし、`webpack.config.js`にルールを追加します。

```bash
npm i -D vue-style-loader css-loader
```

**webpack.config.js**

```js
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      // ここを追加
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      }
    ]
  },
```

再度ビルド、（閉じていれば）ローカルサーバーを立ち上げて表示してみましょう。
先ほどの「message」が赤くなっていれば成功です。

![2.png](https://qiita-image-store.s3.amazonaws.com/0/100188/3b894384-fe56-957b-3a39-4d92e7b9d53c.png)

`vue`で出来ることや`vue-loader`の設定について、より詳しく知りたい場合は以下を参照してください。

- [Vue.jsガイド](https://jp.vuejs.org/v2/guide/)
- [Vue Loaderガイド](https://vue-loader.vuejs.org/guide/)

現在のディレクトリ構成は以下のようになっています。

```
root
|- /dist
  |- main.js
|- /node_modules
  |- /some_modules
|- /src
  |- App.vue
  |- index.js
|- .babelrc
|- index.html
|- package.json
|- package-lock.json
|- webpack.config.js
```

## ルーティング

`vue-router`でルーティングを行い、SPAにしていきます。

パッケージをインストールします。

```bash
npm i -S vue-router
```

`src/router.js`を作成し、ルーティングの設定をします。

**src/router.js**

```js
import Vue from 'vue';
import Router from 'vue-router';
import Top from './components/Top.vue';
import Page from './components/Page.vue';

Vue.use(Router);

const routes = [
  { path: '/', component: Top },
  { path: '/page', component: Page }
];

const router = new Router({ routes });

export default router;
```

パス`/`ではコンポーネント`Top.vue`を、パス`/page`ではコンポーネント`Page.vue`をレンダリングします。
それぞれのコンポーネントも作りましょう。

**src/components/Top.vue**

```html
<template>
  <p>Top.</p>
</template>
```

**src/components/Page.vue**

```html
<template>
  <p>under pages.</p>
</template>
```

これらを有効化させるために、`src/index.js`で`router`を読み込みます。

**src/index.js**

```js
import Vue from 'vue';
import App from './App.vue';
import router from './router';

new Vue({
  el: '#app',
  router,
  render: h => h(App)
});
```

`src/App.vue`にリンクと表示の設定を行います。

```html
<template>
  <div>
    <p class="message">{{message}}</p>
    <ul>
      <li><router-link to="/">Top</router-link></li>
      <li><router-link to="/page">Page</router-link></li>
    </ul>
    <router-view></router-view>
  </div>
</template>

...
```

パスが変わると`<router-view></router-view>`の内容が対応するコンポーネントに変わる構成です。

ビルドして表示を確認してみましょう。

![3.png](https://qiita-image-store.s3.amazonaws.com/0/100188/3c194f9a-8809-91a4-8efd-fbe5daa3d958.png)

URLとコンテンツが変わっても画面の再読み込みは行われません。
とても簡単ですが、これでSPAができました。

`vue-router`についてより詳しく知りたい場合は[公式ガイド](https://router.vuejs.org/ja/guide/)を参照してください。

現在のディレクトリ構成は以下のようになっています。

```
root
|- /dist
  |- main.js
|- /node_modules
  |- /some_modules
|- /src
  |- /components
    |- Page.vue
    |- Top.vue
  |- App.vue
  |- index.js
  |- router.js
|- .babelrc
|- index.html
|- package.json
|- package-lock.json
|- webpack.config.js
```

# 次
[実践3. SSR](https://qiita.com/bgn_nakazato/items/93ecffca36d6f7d0ec6d)