iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 29
1
Modern Web

從 0 開始建一個 Static Site Generator系列 第 29

Day 29: 實作 Vue 的 Server Side Render

這篇的程式碼在 https://github.com/DanSnow/ironman-2020/tree/master/static-site-generator/packages/vue-ssr

這篇要來實際嘗試 Vue 的 SSR ,這篇做的事很簡單,就是在 server render 好一個 Vue 的程式,然後在 Client 端也把 js 加上去,程式的部份放在 App.vue 中,是個很簡單的 counter ,我們直接來看其它部份,首先我們要準備一個 app.js ,裡面 export 一個函式用來建立 Vue 的程式:

import Vue from 'vue'
import App from './App.vue'

export function createApp() {
  const app = new Vue({
    render: (h) => h(App),
  })
  return { app }
}

這邊做最主要的原因是確保每次的 render 都會是新的程式,不會受到之前的狀態影響之類的,如果有需要加上 store 等等的也要加在裡面,目前先不加

再來我們要準備兩個進入點,一個給 server 用來 render html,另一個是在 client 回復程式的狀態時使用,它們分別叫 entry-server.jsentry-client.js,先是 entry-server.js:

import { createApp } from './app'

export default (context) => {
  const { app } = createApp()
  return app
}

目前就很簡單的把 createApp 包裝一下再 export 出去而已,再來是 entry-client.js ,這其實跟平常寫 Vue 沒什麼兩樣:

import { createApp } from './app'

const { app } = createApp()

app.$mount('#root')

再來要準備 webpack 的設定打包這兩個檔案,我們來寫個 webpack.config.js,另外這篇用的是 webpack 5 ,原生就支援 PnP 感覺很方便:

const webpack = require('webpack')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

const baseConfig = {
  module: {
    rules: [
      // vue loader 的設定
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    // webpack 5 預設不再提供 node 的 API 了
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development'),
    }),
  ],
  optimization: {
    // 開一下串接模組,這樣產出來的檔案會小一點,比較好看
    // 如果全都是 es6 module , webpack 5 甚至有可能編出不含 runtime 的 code 喔
    concatenateModules: true,
  },
  mode: 'none',
  devtool: false,
}

module.exports = [
  // server 的設定
  {
    ...baseConfig,
    entry: {
      server: './src/entry-server',
    },
    output: {
      // 要把 app export 出來,所以這邊要設定 `libraryTarget`
      libraryTarget: 'commonjs2',
    },
    // 不需要打包進 vue
    externals: ['vue'],
    // 一定要指明是 `node` ,這同時會讓 vue-loader 編出 SSR 用的 code
    target: 'node',
  },
  // client 的設定
  {
    ...baseConfig,
    entry: {
      client: './src/entry-client',
    },
  },
]

寫完後就直接輸入 yarn webpack 跑一下打包吧,最後是 server.js:

const { resolve } = require('path')
const express = require('express')
const { createRenderer } = require('vue-server-renderer')
const createApp = require('./dist/server').default

// vue-ssr-outlet 是 vue 的 renderer 會插入 render 好的 html 的位置
const template = `
<html>
  <head>
    <title>SSR test</title>
  </head>
  <body>
    <!--vue-ssr-outlet-->
    <script src="client.js"></script>
  </body>
</html>
`

const app = express()
const renderer = createRenderer({
  template,
})

app.use(express.static(resolve(__dirname, 'dist')))

app.get('/', async (req, res) => {
  const context = {}
  const app = createApp()
  // 第一個參數是一個 vue 的 instance ,第二個則是一個 object ,回傳是一個 promise 包著 html
  const html = await renderer.renderToString(app, context)
  res.send(html)
})

app.listen(3000, () => {
  console.log('listen at http://localhost:3000')
})

就這樣,可以在終端機中輸入 yarn node server.js 試試了,應該可以在 http://localhost:3000 看到我們的網頁,到這邊基本的東西準備完了,下一篇我們再來把 vue-router 跟 Vuex 加進去,也順便試一下 serverPrefetch 跟 bundle renderer 吧


上一篇
Day 28: 介紹 Vue 的 Server Side Render
下一篇
Day 30: 更多的 Vue SSR
系列文
從 0 開始建一個 Static Site Generator30

尚未有邦友留言

立即登入留言