ルーティングとコード分割

vue-router によるルーティング

サーバーコードが任意の URL を受け入れる * ハンドラを使用していることに気付いたかもしれません。これにより訪れた URL を Vue アプリケーションに渡し、クライアントとサーバーの両方に同一のルーティング設定を再利用することが可能になります!

この目的のために公式の vue-router を使用することが推奨されています。まずはルーターを作成するファイルを作成しましょう。 createApp に似ていますが、 リクエストごとに新たなルーターインスタンスも必要となるため、 createRouter 関数をエクスポートします:

// router.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export function createRouter () {
  return new Router({
    mode: 'history',
    routes: [
      // ...
    ]
  })
}

そして app.js を更新します:

// app.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'

export function createApp () {
  // ルーターインスタンスを作成します
  const router = createRouter()

  const app = new Vue({
    // ルーターをルートVueインスタンスに注入します
    router,
    render: h => h(App)
  })

  // アプリケーションとルーターの両方を返します
  return { app, router }
}

entry-server.js にサーバー側のルーティングロジックを実装する必要があります:

// entry-server.js
import { createApp } from './app'

export default context => {
  // 非同期のルートフックまたはコンポーネントが存在する可能性があるため、
  // 描画する前にすべての準備が整うまでサーバーが待機できるように
  // プロミスを返します
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()

    // サーバーサイドのルーターの場所を設定します
    router.push(context.url)

    // ルーターが非同期コンポーネントとフックを解決するまで待機します
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      // 一致するルートがない場合、404で拒否します
      if (!matchedComponents.length) {
        reject({ code: 404 })
      }

      // プロミスは描画できるようにアプリケーションインスタンスを解決するべきです
      resolve(app)
    }, reject)
  })
}

サーバーバンドルがすでにビルドされていると仮定すると(再度になりますが、今はビルド設定は無視します)、サーバーでの使用方法は次のようになります:

// server.js
const createApp = require('/path/to/built-server-bundle.js')

server.get('*', (req, res) => {
  const context = { url: req.url }

  createApp(context).then(app => {
    renderer.renderToString(app, (err, html) => {
      if (err) {
        if (err.code === 404) {
          res.status(404).end('Page not found')
        } else {
          res.status(500).end('Internal Server Error')
        }
      } else {
        res.end(html)
      }
    })
  })
})

コード分割

コード分割やアプリケーションの部分的な遅延ローディングは初期描画のためにブラウザがダウンロードする必要のあるアセットの量を減らすのに役立ち、巨大なバンドルを持つアプリケーションの TTI (操作可能になるまでの時間)を大幅に改善します。重要なことは初期画面では"必要なものだけを読み込む"ということです。

Vue は非同期コンポーネントを最重要コンセプトとして提供しており、 webpack 2の動的インポートをコード分割点として使用することへのサポート (opens new window) と組み合わせることも可能です。そのためにすべきことは以下です:

// これを...
import Foo from './Foo.vue'

// このように変えます
const Foo = () => import('./Foo.vue')

純粋なクライアントサイドの Vue アプリケーションを構築する場合、これはどんなシナリオでも機能するでしょう。ただし、これをサーバーサイドの描画で使用する場合はいくつかの制限があります。まず、描画を開始する前にサーバー上のすべての非同期コンポーネントを先に解決する必要があります。そうしなければ、マークアップ内に空のプレースホルダが表示されます。クライアント側では、ハイドレーションを開始する前にこれを行う必要があります。そうしなければ、クライアントはコンテンツの不一致エラーに陥ります。

アプリケーション内の任意の場所で非同期コンポーネントを使用するのは少し難解です(これは将来的に改善される可能性があります)。 ただし、ルート (route) レベルで行うとシームレスに動作します(すなわち、ルート設定で非同期コンポーネントを使用する)。ルートを解決する際に、 vue-router は一致した非同期コンポーネントを自動的に解決するためです。 必要なことは、サーバーとクライアントの両方で router.onReady を使用することです。すでにサーバーのエントリーで行ったので、クライアントのエントリーを更新するだけです。

// entry-client.js

import { createApp } from './app'

const { app, router } = createApp()

router.onReady(() => {
  app.$mount('#app')
})

非同期ルートコンポーネントを使用したルート設定の例:

// router.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export function createRouter () {
  return new Router({
    mode: 'history',
    routes: [
      { path: '/', component: () => import('./components/Home.vue') },
      { path: '/item/:id', component: () => import('./components/Item.vue') }
    ]
  })
}