前置き
今作っているシステムは社内ツールとして作っているのですが、将来的に弊社のクライアント向けに公開するみたいな話もあり、
古いJS・低速なネット環境も想定されるので、それを解決する手段の1つとしてSSR(サーバサイドレンダリング)を勉強します。
https://jp.vuejs.org/v2/guide/ssr.html
こちらのVue公式ドキュメントを参考に作ってます。
http://qiita.com/koki_cheese/items/ce5f16111e8ca251838d
Webpack周りはこちらが参考になりました。
SSRされていない状態
以下は画面に文字列を表示するだけのコードです。
import Vue from 'vue'
import App from './App'
new Vue({
render: h => h(App)
}).$mount('#app')
<template>
<div id="app">
<span>sample ssr!!</span>
<!-- 本来の実装では以下を設定している
<router-view></router-view>
-->
</div>
</template>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>sample ssr</title>
</head>
<body>
<div id="app" />
<script src="/bundle.js"></script>
</body>
</html>
Webpack等でindex.jsをエントリーポイントとしてビルドして、bundle.jsを作ります。
ビルド周りの設定は省略しますが、これを画面で表示させると「sample ssr!!」という文字だけが表示されます。
SSRをしていないので、<div id="app" />
のように空のHTMLになっています。
現状は、上記のサンプルコードはコメントアウトしていますが、vue-routerを使ったSPA(シングルページアプリケーション)として動作させています。
これを初期表示時にSSRしてあげるようにコードを改修していきます。
SSR用に変更
index.jsはブラウザとNode.js(サーバサイド)からの呼出しに対応させます。
import Vue from 'vue'
import App from './App'
const createApp = () => {
return new Vue({
render: h => h(App)
})
}
if (typeof module !== 'undefined' && module.exports) {
// Node.jsから呼出した場合の処理を追加
module.exports = createApp
} else {
// ブラウザから呼出した場合は特に変更無し
createApp().$mount('#app')
}
index.htmlはパスだけ変更
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>sample ssr</title>
</head>
<body>
<div id="app" />
<script src="/dist/client-bundle.js"></script>
</body>
</html>
サーバサイド作成
Node.js + Expressを使って作成しています。
const fs = require('fs')
const path = require('path')
const express = require('express')
const renderer = require('vue-server-renderer').createRenderer()
const createApp = require('./dist/server-bundle')
const app = express()
const layout = fs.readFileSync('./dist/index.html', 'utf-8')
const layoutSections = layout.split('<div id="app" />')
app.use('/dist', express.static(
path.resolve(__dirname, 'dist')
))
app.get('*', (req, res) => {
// 上記で分割したHTMLをストリーミング
const stream = renderer.renderToStream(createApp())
res.write(layoutSections[0])
stream.on('data', (chunk) => {
res.write(chunk)
})
stream.on('end', () => {
res.end(layoutSections[1])
})
stream.on('error', (err) => {
console.error(err)
return response.status(500).send('Server Error')
})
})
// 5000番ポートで待機
app.listen(5000, function (err) {
if (err) throw err
console.log('Server is running at localhost:5000')
})
WebpackをSSR用に作成
// ブラウザ用
const client = {
entry: [
`${__dirname}/../src/index.html`,
`${__dirname}/../src/index.js`
],
output: {
filename: 'client-bundle.js',
path: `${__dirname}/../dist`
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
exclude: /node_modules/
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.vue$|\.js$/,
use: 'eslint-loader',
exclude: /node_modules/
},
{
test: /\.html$/,
use: 'file-loader?name=[name].[ext]'
}
]
},
resolve: {
extensions: ['.js', '.vue']
}
}
// サーバ用
const server = Object.assign({}, client, {
target: 'node',
output: {
filename: 'server-bundle.js',
path: `${__dirname}/../dist`,
libraryTarget: 'commonjs2'
},
externals: Object.keys(require('../package.json').dependencies)
})
module.exports = [client, server]
以下はBabelの設定です。
私の環境だと以下のプラグインが無いと動きませんでした。
「babel-plugin-transform-es2015-modules-commonjs」
{
"presets": [["es2015", {"modules": false}], "stage-2"],
"plugins": ["transform-runtime", "transform-es2015-modules-commonjs"]
}
動作確認
以下のコマンドでビルドしてhttp://localhost:5000
で見てみると
SSR未対応の時と同様に「sample ssr!!」という文字だけが表示されます。
webpack --config build/webpack.config.js
node server.js
先程と違い、<div id="app" server-rendered="true"><span>sample ssr!!</span></div>
のようにSSRされていることが分かります。
今回は試してませんがvue-routerを使ってあげると、初期表示はSSRして、それ以降はSPAとして動作してくれるはずです。