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 相关文章推荐
javascript onmouseout 解决办法
Jul 17 Javascript
chrome原生方法之数组
Nov 30 Javascript
JS完成代码前最好对其做5件事
Apr 07 Javascript
JavaScript中的this关键字介绍与使用实例
Jun 21 Javascript
javascript在子页面中函数无法调试问题解决方法
Jan 17 Javascript
JS中setTimeout的巧妙用法前端函数节流
Mar 24 Javascript
浅谈javascript中的加减时间
Jul 12 Javascript
js转换对象为xml
Feb 17 Javascript
vue 下列表侧滑操作实例代码详解
Jul 24 Javascript
vuex state中的数组变化监听实例
Nov 06 Javascript
基于JS实现操作成功之后自动跳转页面
Sep 25 Javascript
详解JavaScript之Array.reduce源码解读
Nov 01 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
风味层面去分析咖啡油脂
2021/03/03 咖啡文化
php 文件上传后缀名与文件类型对照表(几乎涵盖所有文件)
2010/05/16 PHP
php数组函数序列之asort() - 对数组的元素值进行升序排序,保持索引关系
2011/11/02 PHP
PHP分页效率终结版(推荐)
2013/07/01 PHP
php设置session值和cookies的学习示例
2014/03/21 PHP
浅谈Eclipse PDT调试PHP程序
2014/06/09 PHP
php将图片保存入mysql数据库失败的解决方法
2014/12/27 PHP
php数组函数array_key_exists()小结
2015/12/10 PHP
PHP那些琐碎的知识点(整理)
2017/05/20 PHP
tbody元素支持嵌套的注意方法
2007/03/24 Javascript
js onpropertychange输入框 事件获取属性
2009/03/26 Javascript
ff下JQuery无法监听input的keyup事件的解决方法
2013/12/12 Javascript
jQuery使用post方法提交数据实例
2015/03/25 Javascript
jquery+css实现的红色线条横向二级菜单效果
2015/08/22 Javascript
设置jquery UI 控件的大小方法
2016/12/12 Javascript
[00:55]2015国际邀请赛中国区预选赛5月23日——28日约战上海
2015/05/25 DOTA
[47:43]完美世界DOTA2联赛PWL S3 Magama vs GXR 第二场 12.19
2020/12/24 DOTA
Django中传递参数到URLconf的视图函数中的方法
2015/07/18 Python
Python 12306抢火车票脚本 Python京东抢手机脚本
2018/02/06 Python
Python中pow()和math.pow()函数用法示例
2018/02/11 Python
python2.7 json 转换日期的处理的示例
2018/03/07 Python
Python socket实现的简单通信功能示例
2018/08/21 Python
Python大数据之使用lxml库解析html网页文件示例
2019/11/16 Python
Python+numpy实现矩阵的行列扩展方式
2019/11/29 Python
python函数不定长参数使用方法解析
2019/12/14 Python
Django中Aggregation聚合的基本使用方法
2020/07/09 Python
python编写扎金花小程序的实例代码
2021/02/23 Python
全球知名鞋履品牌授权零售商:Journeys
2016/09/17 全球购物
英国二手iPhone、音乐、电影和游戏商店:musicMagpie
2018/10/26 全球购物
机械工程系毕业生求职信
2013/09/27 职场文书
数学系个人求职信范文
2014/01/30 职场文书
21岁生日感言
2014/02/27 职场文书
诚信贷款承诺书
2014/05/30 职场文书
2014小学二年级班主任工作总结
2014/12/05 职场文书
安全保证书
2015/01/16 职场文书
2015年乡镇平安建设工作总结
2015/05/13 职场文书