vue ssr 指南详读


Posted in Javascript onJune 29, 2018

本帖说明

该贴是对vue SSR Guide解读和补充,对于官网文档已有内容会以引用方式体现。由于官网demo在国内无法运行,该贴最后也提供了一个完整的可以运行的demo,帖子中提到的代码均是来自于该demo,供学习交流。

介绍

什么是服务器端渲染(SSR)?

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。

借助vue-server-renderer 将vue实例渲染为浏览器可以识别的html字符串。

为什么使用服务器端渲染(SSR)?

  1. 更好的 SEO
  2. 更快的内容到达时间 (白屏)

vue ssr 指南详读

左侧为浏览器渲染,右侧为服务器渲染,从图中可以看出服务端渲染理论上明显少于浏览器渲染。

基本用法

安装

git clone https://github.com/s249359986/learnssr.git
cd learnssr 
npm install
npm run dev

代码结构

src
├── components
│  ├── Foo.vue
├── views
│  ├── Home.vue
├── App.vue
├── app.js 
├── client-entry.js
├── server-entry.js

代码详解

server.js 是服务端启动入口文件,接收客户端对页面的所有请求。

if (isProd) {
 /**
  生产环境,createRenderer将已经通过webpack打包好的server-bundle.js转化为一个可以操作的renderer对象。
 **/
 renderer = createRenderer(fs.readFileSync(resolve('./dist/server-bundle.js'), 'utf-8'))
 
 /**
 入口模板文件
 **/
 indexHTML = parseIndex(fs.readFileSync(resolve('./dist/index.html'), 'utf-8'))
} else {
 /**
  开发环境,createRenderer将已经通过webpack打包好的server-bundle.js转化为一个可以操作的renderer对象。
 **/
 require('./build/setup-dev-server')(app, {
  bundleUpdated: bundle => {
   renderer = createRenderer(bundle)
  },
  indexUpdated: index => {//index为入口文件及index.html
   indexHTML = parseIndex(index)
  }
 })
}

function createRenderer (bundle) {

 return require('vue-server-renderer').createBundleRenderer(bundle, {
  cache: require('lru-cache')({
   max: 1,//1000,
   maxAge: 2000//1000 * 60 * 15
  }),
  runInNewContext: false
 })
}
/*
读取入口文件
*/
function parseIndex (template) {
 const contentMarker = '<!-- APP -->'
 const i = template.indexOf(contentMarker)
 return {
  head: template.slice(0, i),
  tail: template.slice(i + contentMarker.length)
 }
}

const serve = (path, cache) => express.static(resolve(path), {
 maxAge: cache && isProd ? 60 * 60 * 24 * 30 : 0
})

app.use('/dist', serve('./dist'))

app.get('*', (req, res) => {
 if (!renderer) {
  return res.end('waiting for compilation... refresh in a moment.')
 }

 res.setHeader('Content-Type', 'text/html')
 res.setHeader('Server', serverInfo)

 var s = Date.now()
 const context = { url: req.url }
 /*
  渲染vue实例,context对象上下文
 */
 const renderStream = renderer.renderToStream(context)

 renderStream.once('data', () => {
  res.write(indexHTML.head)
 })

 renderStream.on('data', chunk => {
  res.write(chunk)
 })

 renderStream.on('end', () => {
  if (context.initialState) {
   res.write(
    `<script>window.__INITIAL_STATE__=${
     serialize(context.initialState, { isJSON: true })
    }</script>`
   )
  }
  res.end(indexHTML.tail)
  console.log(`whole request: ${Date.now() - s}ms`)
 })

 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')
  console.error(`error during render : ${req.url}`)
  console.error(err)
 })
})

const port = process.env.PORT || 8080
app.listen(port, () => {
 console.log(`server started at localhost:${port}`)
})

服务端vue实例入口文件,通过上下文对象获取请求的url,映射给对应的组件。

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 => {
/*
增加服务端数据预处理 start

*/
  if (component.asyncData) {
     return component.asyncData({
      store,
      route: router.currentRoute
     })
    }

    /*
    增加服务端数据预处理 end

    */
 })).then(() => {
  isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
  context.initialState = store.state
  return app
 })
}

客户端vue实例入口文件.

/*

第一种方式
 */

Vue.mixin({
 beforeMount () {
  const { asyncData } = this.$options
 console.log('beforeMount',this.$store)
  if (asyncData) {

   // 将获取数据操作分配给 promise
   // 以便在组件中,我们可以在数据准备就绪后
   // 通过运行 `this.dataPromise.then(...)` 来执行其他任务
   this.dataPromise = asyncData({
    store: this.$store,
    route: this.$route
   })
  }
 },
 beforeRouteUpdate (to, from, next) {
 const { asyncData } = this.$options
 console.log('beforeRouteUpdate',this.$store)
 if (asyncData) {
 asyncData({
  store: this.$store,
  route: to
 }).then(next).catch(next)
 } else {
 next()
 }
}
})

/**
更新客户端store,与服务端store同步
**/
// store.replaceState(window.__INITIAL_STATE__)
if (window.__INITIAL_STATE__) {
 store.replaceState(window.__INITIAL_STATE__)
}
// actually mount to DOM


router.onReady(() => {
/**
挂载实例,客户端激活,所谓激活,指的是 Vue 在浏览器端接管由服务端发送的静态 HTML,使其变为由 Vue 管理的动态 DOM 的过程。注释掉app.$mount('#app') 可以清楚看到<div id="app" data-server-rendered="true"> 客户端通过data-server-rendered="true"知道该html是vue在服务端渲染的,并且不会在做多余的渲染。由于在服务端无法绑定事件,只有通过客户端vue处理。
**/
 app.$mount('#app')
})

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS 动态获取节点代码innerHTML分析 [IE,FF]
Nov 30 Javascript
JS 实现图片直接下载示例代码
Jul 22 Javascript
JS获取网页属性包括宽、高等等
Apr 03 Javascript
jQuery遍历页面所有CheckBox查看是否被选中的方法
Apr 14 Javascript
laypage分页控件使用实例详解
May 19 Javascript
javascript对浅拷贝和深拷贝的详解
Oct 14 Javascript
Bootstrap Table使用方法解析
Oct 19 Javascript
angular forEach方法遍历源码解读
Jan 25 Javascript
jQuery实现鼠标响应式透明度渐变动画效果示例
Feb 13 jQuery
Vue 使用中的小技巧
Apr 26 Javascript
require.js 加载过程与使用方法介绍
Oct 30 Javascript
如何利用vue+vue-router+elementUI实现简易通讯录
May 13 Javascript
jQuery实现获取动态添加的标签对象示例
Jun 28 #jQuery
Vue实现textarea固定输入行数与添加下划线样式的思路详解
Jun 28 #Javascript
详解swipe使用及竖屏页面滚动方法
Jun 28 #Javascript
微信小程序wx.uploadfile 本地文件转base64的实现代码
Jun 28 #Javascript
浅谈vue首屏加载优化
Jun 28 #Javascript
jQuery实现获取选中复选框的值实例详解
Jun 28 #jQuery
Vue SPA单页应用首屏优化实践
Jun 28 #Javascript
You might like
destoon实现公司新闻详细页添加评论功能的方法
2014/07/15 PHP
php实现图片文件与下载文件防盗链的方法
2014/11/03 PHP
PHP从FLV文件获取视频预览图的方法
2015/03/12 PHP
CI框架常用经典操作类总结(路由,伪静态,分页,session,验证码等)
2016/11/21 PHP
浅谈PHP面向对象之访问者模式+组合模式
2017/05/22 PHP
php中钩子(hook)的原理与简单应用demo示例
2019/09/03 PHP
比较简单的一个符合web标准的JS调用flash方法
2007/11/29 Javascript
Javascript解决常见浏览器兼容问题的12种方法
2010/01/04 Javascript
jquery命令汇总,方便使用jquery的朋友
2012/06/26 Javascript
纯js分页代码(简洁实用)
2013/11/05 Javascript
利用JQuery和Servlet实现跨域提交请求示例分享
2014/02/12 Javascript
jQuery遍历对象、数组、集合实例
2014/11/08 Javascript
jQuery调用ajax请求的常见方法汇总
2015/03/24 Javascript
Jquery动态添加输入框的方法
2015/05/29 Javascript
js控制文本框输入的字符类型方法汇总
2015/06/19 Javascript
AngularJS+Node.js实现在线聊天室
2015/08/28 Javascript
iscroll.js滚动加载实例详解
2017/07/18 Javascript
Express + Session 实现登录验证功能
2017/09/08 Javascript
使用vue2实现购物车和地址选配功能
2018/03/29 Javascript
Vuex 在Vue 组件中获得Vuex 状态state的方法
2018/08/27 Javascript
element ui table 增加筛选的方法示例
2018/11/02 Javascript
Vue 401配合Vuex防止多次弹框的案例
2020/11/11 Javascript
Python获取远程文件大小的函数代码分享
2014/05/13 Python
举例讲解Linux系统下Python调用系统Shell的方法
2015/11/07 Python
Python 和 JS 有哪些相同之处
2017/11/23 Python
Python generator生成器和yield表达式详解
2019/08/08 Python
opencv转换颜色空间更改图片背景
2019/08/20 Python
django实现web接口 python3模拟Post请求方式
2019/11/19 Python
python统计字符的个数代码实例
2020/02/07 Python
python pyqtgraph 保存图片到本地的实例
2020/03/14 Python
python 获取域名到期时间的方法步骤
2021/02/10 Python
详解利用css3的var()实现运行时改变scss的变量值
2021/03/02 HTML / CSS
委托书范本
2014/04/02 职场文书
员工自我评价范文
2015/03/11 职场文书
2015年公务员工作总结
2015/04/24 职场文书
javascript中Set、Map、WeakSet、WeakMap区别
2022/12/24 Javascript