Day 30: 更多的 Vue SSR

这篇程序码在 https://github.com/DanSnow/ironman-2020/tree/master/static-site-generator/packages/vue-ssr

接续上篇的内容,我们来把 Vuex 跟 vue-router 都加入我们的 SSR 中

Vuex

我们简单的建一个 Vuex store :

import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

export default new Vuex.Store({
  state: () => ({
    message: '',
  }),
  mutations: {
    SET_MESSAGE(state, message) {
      state.message = message
    },
  },
})

然後在 App.vue 的 serverPrefetch 加上抓 API 的程序码:

import ky from 'ky-universal'

export default {
  async serverPrefetch() {
    // 这个 API 写在 server.js 里
    const data = await ky.get('http://localhost:3000/api/foo').json()
    this.$store.commit('SET_MESSAGE', data.message)
  },

  computed: {
    message() {
      return this.$store.state.message
    },
  },
}

再来修改 app.js 让它也回传 store ,之後也要像这样把 router 传出来:

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

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

再来就是重点了,在 entry-server.js 中,我们要把 store 的状态存进 contextstate 中:

import { createApp } from './app'

export default (context) => {
  const { app, store } = createApp()
  // 这个是 vue-server-renderer 的一个 hook ,会在 render 完时呼叫
  context.rendered = () => {
    // 把 state 存进 context 中
    context.state = store.state
  }
  return app
}

接下来就是 Vue 的魔法了,你可以启动 server ,看一下产出来的网页原始码,你看到了什麽呢?

<script>window.__INITIAL_STATE__={"message":"Hello world"}</script>

vue-server-renderer 已经帮我们把 state 加到产出来的 html 中了,但是如果你打看网页,应该会发现你看不到这段讯息,因为我们没有在 Client 端把初始的状态加回去 Vuex 中,我们在 entry-client.js 中加上:

if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

就这样, Server 的状态就可以用 Vuex 传到 Client 端,再来是 vue-router

vue-router

为了使用 vue-router ,这边我自己先很简单的拆了两个页面出来,我们直接看 app.js ,这边把 router 加进:

import Vue from 'vue'
import App from './App.vue'
import store from './store'
import { router } from './router'

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

再来是 entry-server.js

import { createApp } from './app'

export default (path, context) => {
  // 因为要能等待 router 做完,所以这边改用 Promise 了
  return new Promise((resolve) => {
    const { app, store, router } = createApp()
    // 把路径给 router ,让它去 render 第一个页面
    router.push(path)
    // 用 onReady 等待 router 通知完成
    router.onReady(() => {
      context.rendered = () => {
        context.state = store.state
      }
      // 用 Promise 回传 app
      resolve(app)
    })
  })
}

server.js 则只是因应 entry-server.js 的改变,把 createApp 前加上 await 跟把路径 / 改成用 /* 而已,就这样,把 Vue 两个很重要的部份也加了上去

Bundle Renderer

如果你真的有操作过一次的话,你中间应该有重启 server 不少次吧,每次要重启服务器真的很麻烦, vue-server-renderer 提供了 Bundle Renderer 就是为了解决这个问题,它能够自动重新载入打包好的 server 端的程序码,不过因为这个功能似乎还没支援 webpack 5 ,就大概讲一下就好了,用这个要改两个部份,一个是把 vue-server-renderer/server-plugin 的 webpack plugin 加入 webpack 的设定中,这样产生出来的就不会是一个 js 档,而是一个 vue-ssr-server-bundle.json

再来是 createRenderer 改成 createBundleRenderer

const renderer = createBundleRenderer('path/to/vue-ssr-server-bundle.json', {template})

而使用时则不用再传入 app 当参数:

const html = await renderer.renderToString(context)

再来只要搭配 webpack 的 watch mode 就可以自动重新载入了

其实我有尝试把 vue-server-renderer 的 plugin 修好,不过似乎还是有问题

这是这系列的技术文章部份的最後一篇了


<<:  【30天Lua重拾笔记34】番外篇: Fengari - 一个JS实现的Lua,运行Lua在浏览器内吧!

>>:  Day 30: 实作个 eslint plugin

铁人赛28天 VScode Live Sass设定

这几天确定真的都没梗,极度没有营养的内容,所以今天把之前liveSass设定贴上来做为用记录,不过现...

Powershell 入门之循环(下)

昨天我们看了 for(foreach) 循环,今天我们来看看 while 循环。 while 的语法...

2.4.4 Design System - Button

很多事情不用说破 放在心里知道就好 有时候给别人留个台阶下 没什麽不好的 之前曾遇过一位夥伴 总会...

Java学习之路05---运算子

架构图 前言 表达式是程序进行算术运算中的表示方式,我们可以简单地把表达式拆解为表达式 = 运算子 ...

【Day 5】Google Apps Script - 变数与函式呼叫与GS档的顺序影响

在专案里,所有的档案都预先被 import 在一起的,可直接呼叫其他 gs档里的变数与函式。gs档...