vue ssr(server side rendering)
1 解决问题:
a.)解决单页面应用的SEO问题
b.) 客户端的网络比较慢,初次加载耗时较多
c.) 客户端运行在老的或者直接没有javascript引擎上
2. 实现原理
客户端请求服务器,服务器根据请求地址获得匹配的组件,再调用匹配到的组件返回Promise来将需要的数据拿到。最后再通过 <script>window.__initial_state=data</script> 将其写入网页,最后将服务端渲染好的网页返回客户端。
客户端利用vuex将写入的__initial_state__替换为当前的全局状态树,再用这个状态树去检查服务端渲染好的数据有没有问题。遇到没被服务端渲染的组件,再去发一步请求拿数据。VUE2使用单向数据流,用了它,就可以通过SSR返回唯一一个全局状态,并确认某个组件是否已经SSR了。
由于vue2使用了虚拟DOM,因此对浏览器环境和服务端环境要分开渲染,要创建两个对应的入口文件。
1)浏览器入口文件 client-entry.js, 使用$mount直接挂载
1 import 'es6-promise/auto'; 2 3 import { app, store } from './app'; 4 5 store.replaceState(window.__INITIAL_STATE__); 6 7 app.$mount('#app');
/*
* 在 client-entry.js 文件中引入了app.js, 判断如果在服务端渲染时已经写入状态,则将vuex的状态进行替换,使得服务端渲染的html和vuex管理的数据是同步的。
* 然后将vue实例挂载到html指定的节点中。*/
2) server-entry文件
import { app, router, store } from './app'; const isDev = process.env.NODE_ENV !== 'production'; export default context => { const s = isDev && Date.now(); router.push(context.url); const matchedComponents = router.getMatchedComponents(); if (!matchedComponents.length) { return Promise.reject({ code: '404' }); } return Promise.all(matchedComponents.map(component => { if (component.preFetch) { return component.preFetch(store); } })).then(() => { return app; }); };
// 处理所有的get请求 app.get('*', (req, res) => { // 等待编译 if (!renderer) { return res.end('waiting for compilation... refresh in a moment.'); } var s = Date.now(); const context = { url: req.url }; // 渲染我们的Vue实例作为流 const renderStream = renderer.renderToStream(context); // 当块第一次被渲染时 renderStream.once('data', () => { // 将预先的HTML写入响应 res.write(indexHTML.head); }); // 每当新的块被渲染 renderStream.on('data', chunk => { // 将块写入响应 res.write(chunk); }); // 当所有的块被渲染完成 renderStream.on('end', () => { // 当vuex初始状态存在 if (context.initialState) { // 将vuex初始状态以script的方式写入到页面中 res.write( `<script>window.__INITIAL_STATE__=${ serialize(context.initialState, { isJSON: true }) }</script>` ); } // 将结尾的HTML写入响应 res.end(indexHTML.tail); }); // 当渲染时发生错误 renderStream.on('error', err => { if (err && err.code === '404') { res.status(404).end('404 | Page Not Found'); return; } res.status(500).end('Internal Error 500'); }); })
在 server-entry 文件中服务端会传递一个context对象,里面包含当前用户请求的url,vue-router 会跳转到当前请求的url中,通过 router.getMatchedComponents( ) 来获得当前匹配组件,则去调用当前匹配到的组件里的 preFetch 钩子,并传递store(Vuex下的状态),会返回一个 Promise 对象,并在then方法中将现有的vuex state 赋值给context,给服务端渲染使用,最后返回vue实例,将虚拟DOM渲染成网页。服务端会将vuex初始状态也生成到页面中。 如果 vue-router 没有匹配到请求的url,直接返回 Promise中的reject方法,传入404,这时候会走到下方renderStream的error事件,让页面显示错误信息。