Vue SSR 指南(三)
根据官网按步骤实现Vue SSR搭建过程
新建一个项目,使用vue-cli3脚手架搭建,vue create ssr-demo根据自己需求选择,项目结构如下:
路由和代码分割
1.router.js
类似于 createApp,我们也需要给每个请求一个新的 router 实例,所以文件导出一个 createRouter 函数
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export function createRouter() {
return new Router({
mode: 'history',
routes: [
{ path: '/', component: () => import('../views/Home.vue') },
{ path: '/about', component: () => import('../views/About.vue') },
],
})
}
2.main.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp() {
const router = createRouter()
const app = new Vue({
// 根实例简单的渲染应用程序组件。
router,
render: (h) => h(App),
})
return { app, router }
}
3.entry-client.js
import { createApp } from './main'
// 客户端特定引导逻辑……
const { app, router } = createApp()
// 这里假定 App.vue 模板中根元素具有 `id="app"`
router.onReady(() => {
app.$mount('#app')
})
4.entry-server.js
import { createApp } from './main'
export default (context) => {
// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
// 以便服务器能够等待所有的内容在渲染前,
// 就已经准备就绪。
return new Promise((resolve, reject) => {
const { app, router } = createApp()
// 设置服务器端 router 的位置
router.push(context.url)
// 等到 router 将可能的异步组件和钩子函数解析完
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
// 匹配不到的路由,执行 reject 函数,并返回 404
if (!matchedComponents.length) {
return reject({ code: 404 })
}
// Promise 应该 resolve 应用程序实例,以便它可以渲染
resolve(app)
}, reject)
})
}
5.重新构建
npm run build:win 或者 npm run build:mac ,生成新的dist
6.index.template.html
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) -->
{{{ meta }}}
<!-- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation) -->
<title>{{ title }}</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
7.server.js
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const express = require('express')
const app = express()
const path = require('path')
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle, {
template: require('fs').readFileSync('./index.template.html', 'utf-8'),
})
// app.use('/js', express.static(path.resolve(__dirname, './dist/js')))
// app.use('/img', express.static(path.resolve(__dirname, './dist/img')))
// app.use('/css', express.static(path.resolve(__dirname, './dist/css')))
app.get('*', (req, res) => {
const context = {
title: 'ssr',
meta: `
<meta charset="utf-8">
`,
url: req.url,
}
renderer.renderToString(context, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(html)
})
})
app.listen(8080, () => {
console.log('已监听 localhost:8080')
})
url 会传给 entry-server.js 此时点击home|about,页面均是从服务端发送过来的
8.图片未加载,引入静态文件
(一)、app.use(express.static(path.resolve(__dirname, ‘./dist’))) 全局引入不可取
修改server.js
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const express = require('express')
const app = express()
const path = require('path')
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle, {
template: require('fs').readFileSync('./index.template.html', 'utf-8'),
})
app.use(express.static(path.resolve(__dirname, './dist')))
app.get('*', (req, res) => {
const context = {
title: 'ssr===== 全局引入demo',
meta: `
<meta charset="utf-8">
`,
url: req.url,
}
renderer.renderToString(context, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(html)
})
})
app.listen(8080, () => {
console.log('已监听 localhost:8080')
})
运行node server.js结果标题并没有改变 ,你在服务端设置模版了(index.template.html),并设置了title为’ssr===== 全局引入demo’,但是他的显示不是这样,他所显示的是public文件夹下的index.html(若是删掉他,运行npm run build:win,它会通过webpack配置自动生成一个index.html ,title为Vue App)
点击about标签,你会发现并没有发送请求.点击浏览器的刷新按钮,他才发送请求.
需要注意的是 静态文件的引入 app.use(express.static(path.resolve(__dirname, ‘./dist’))) 所以连dist中的index.html也一并引入了,此index.html使用了main.js ,所以点击about走的是路由而不是服务端渲染.
测试
在entry-client.js 加入测试
router.onReady(() => {
console.log('entry-client.js');
app.$mount('#app')
})
重新打包,并运行,浏览器控制台会显示 entry-client.js
(二)、要么将dist/index.html 删除 或者将app.use(express.static(path.resolve(__dirname, ‘./dist’)))替换成
app.use('/js', express.static(path.resolve(__dirname, './dist/js')))
app.use('/img', express.static(path.resolve(__dirname, './dist/img')))
app.use('/css', express.static(path.resolve(__dirname, './dist/css')))
也就是说不引入 dist/index.html
这回浏览器的标题变成了ssr===== demo,点击about,也是从服务请求的.