这篇程序码在 https://github.com/DanSnow/ironman-2020/tree/master/static-site-generator/packages/vue-ssr
接续上篇的内容,我们来把 Vuex 跟 vue-router 都加入我们的 SSR 中
我们简单的建一个 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 的状态存进 context
的 state
中:
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 ,这边我自己先很简单的拆了两个页面出来,我们直接看 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 两个很重要的部份也加了上去
如果你真的有操作过一次的话,你中间应该有重启 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在浏览器内吧!
这几天确定真的都没梗,极度没有营养的内容,所以今天把之前liveSass设定贴上来做为用记录,不过现...
昨天我们看了 for(foreach) 循环,今天我们来看看 while 循环。 while 的语法...
很多事情不用说破 放在心里知道就好 有时候给别人留个台阶下 没什麽不好的 之前曾遇过一位夥伴 总会...
架构图 前言 表达式是程序进行算术运算中的表示方式,我们可以简单地把表达式拆解为表达式 = 运算子 ...
在专案里,所有的档案都预先被 import 在一起的,可直接呼叫其他 gs档里的变数与函式。gs档...