SSR概念: 将一个vue组件在服务器端渲染为HTML字符串并发送到浏览器,最后再将这些静态标记“激活”为可交换的应用程序的过程称为服务端渲染。
传统asp.net php jsp web渲染技术:c向服务端发请求,服务端进行查库拼接HTML等操作,然后返回给客户端。
spa: 用户发送请求到后台,返回的是一个普通的HTML的空结构,没有内容;空结构有了后再执行js,才会有ajax等操作发送到后端,才能把数据内容拿回来显示;至少要两次请求才能拿到;
首屏渲染速度慢(两次);空结构内容对SEO不友好;
SSR:输入URL首次请求服务端,在web服务端用vue模板拼接,解析HTML,这里会进行查库等异步操作,完成之后返回到客户端;
与传统不同点: 除了首屏渲染所需HTML字符串外,还有与客户端相关代码,比如spa相关的代码;是在首屏渲染完成之后才开始下载的;静态页面过来后会激活转换成spa,这时操作不在刷新页面就是一个spa页面了;
缺点:
开发条件受限: 有些代码只能在客户端执行,Mounted不在服务端执行(beforeCreate 和 created)在服务端执行;只有在先关代码转换成spa挂载的时候才能用;
构建部署要求多,服务端渲染器用node去渲染,了解服务端的渲染koa,express;如果后端用Java、PHP则需要写中间层的node服务器,构建部署要了解;
服务端压力变大,每次请求都是创建一个新的vue的实例;负载能承受;
// router.js
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
//导出工厂函数
export function createRouter() {
return new Router({
mode: 'history',
routes: [
// 客户端没有编译器,这里要写成渲染函数
{ path: "/", component: { render: h => h('div', 'index page') } },
{ path: "/detail", component: { render: h => h('div', 'detail page') } }
]
});
}
// 主文件
import Vue from "vue";
import App from "./App.vue";
import { createRouter } from "./router";
// 导出Vue实例工厂函数,为每次请求创建独立实例 // 上下文用于给vue实例传递参数
export function createApp(context) {
const router = createRouter();
const app = new Vue({
router,
context,
render: h => h(App)
});
return { app, router };
}
// app.server.js
import { createApp } from "./main";
// 返回一个函数,接收请求上下文,返回创建的vue实例 export default context => {
// 这里返回一个Promise,确保路由或组件准备就绪 return new Promise((resolve, reject) => {
const { app, router } = createApp(context); // 跳转到首屏的地址
router.push(context.url);
// 路由就绪,返回结果
router.onReady(() => {
resolve(app);
}, reject);
});
};
// app.client.js
import { createApp } from "./main";
// 创建vue、router实例
const { app, router } = createApp(); // 路由就绪,执行挂载
router.onReady(() => {
app.$mount("#app");
});
// vue.config.js
npm install webpack-node-externals lodash.merge -D
module.exports = {
css: {
extract: false
},
outputDir: './dist/'+target,
configureWebpack: () => ({
// 将 entry 指向应用程序的 server / client 文件
entry: `./src/entry-${target}.js`,
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
// target设置为node使webpack以Node适用的方式处理动态导入,
// 并且还会在编译Vue组件时告知`vue-loader`输出面向服务器代码。
target: TARGET_NODE ? "node" : "web",
// 是否模拟node全局变量
node: TARGET_NODE ? undefined : false,
output: {
// 此处使用Node⻛格导出模块
libraryTarget: TARGET_NODE ? "commonjs2" : undefined
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的打包文件。
externals: TARGET_NODE ? nodeExternals({
// 不要外置化webpack需要处理的依赖模块。
// 可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 还应该将修改`global`(例如polyfill)的依赖模块列入白名单
whitelist: [/\.css$/]
}): undefined,
optimization: {
splitChunks: undefined
},
// 这是将服务器的整个输出构建为单个 JSON 文件的插件。
// 服务端默认文件名为 `vue-ssr-server-bundle.json`
// 客户端默认文件名为 `vue-ssr-client-manifest.json`。
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : newVueSSRClientPlugin()]
}),
chainWebpack: config => { // cli4项目添加
if (TARGET_NODE) {
config.optimization.delete('splitChunks')
}
config.module
.rule("vue")
.use("vue-loader")
.tap(options => {
merge(options, {
optimizeSSR: false
});
});
}
};
// entry-server.js
import { createApp } from "./app";
export default context => {
return new Promise((resolve, reject) => {
// 拿出store和router实例
const { app, router, store } = createApp(context); router.push(context.url);
router.onReady(() => {
// 获取匹配的路由组件数组
const matchedComponents = router.getMatchedComponents();
// 若无匹配则抛出异常
if (!matchedComponents.length) {
return reject({ code: 404 });
}
// 对所有匹配的路由组件调用可能存在的`asyncData()`
Promise.all(
matchedComponents.map(Component => {
if (Component.asyncData) {
return Component.asyncData({
store,
route: router.currentRoute,
});
}
}),
).then(() => {
// 所有预取钩子 resolve 后,
// store 已经填充入渲染应用所需状态
// 将状态附加到上下文,且 `template` 选项用于 renderer 时,
// 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
context.state = store.state;
resolve(app);
})
.catch(reject);
}, reject);
});
};
// 导出store
const { app, router, store } = createApp();
// 当使用 template 时,context.state 将作为 window.__INITIAL_STATE__ 状态自动嵌入到 最终的 HTML // 在客户端挂载到应用程序之前,store 就应该获取到状态:
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__);
}