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事件,让页面显示错误信息。

 

posted @ 2017-12-25 15:24  沧海一粒  阅读(1477)  评论(0编辑  收藏  举报