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 相关文章推荐
用正则表达式 动态创建/增加css style script 兼容IE firefox
Mar 10 Javascript
Jquery实现textarea根据文本内容自适应高度
Apr 03 Javascript
使用Meteor配合Node.js编写实时聊天应用的范例
Jun 23 Javascript
学习JavaScript设计模式之策略模式
Jan 12 Javascript
javascript跨域请求包装函数与用法示例
Nov 03 Javascript
清除js缓存的多种方法总结
Dec 09 Javascript
JS中input表单隐藏域及其使用方法
Feb 13 Javascript
vue将对象新增的属性添加到检测序列的方法
Feb 24 Javascript
Angular8基础应用之表单及其验证
Aug 11 Javascript
微信小程序 自定义弹窗实现过程(附代码)
Dec 05 Javascript
vue中 v-for循环的用法详解
Feb 19 Javascript
JavaScript 实现拖拽效果组件功能(兼容移动端)
Nov 11 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
浅谈mysql_query()函数的返回值问题
2016/09/05 PHP
thinkphp多表查询两表有重复相同字段的完美解决方法
2016/09/22 PHP
jquery选择器(常用选择器说明)
2010/09/28 Javascript
JS高级拖动技术 setCapture,releaseCapture
2011/07/31 Javascript
node.js中的fs.mkdirSync方法使用说明
2014/12/17 Javascript
javascript实现滑动解锁功能
2014/12/31 Javascript
jquery实现仿新浪微博带动画效果弹出层代码(可关闭、可拖动)
2015/10/12 Javascript
JQuery导航菜单选择特效
2016/04/11 Javascript
Angularjs---项目搭建图文教程
2016/07/08 Javascript
jquery+ajax实现省市区三级联动效果简单示例
2017/01/04 Javascript
JavaScript实现替换字符串中最后一个字符的方法
2017/03/07 Javascript
详解如何使用webpack在vue项目中写jsx语法
2017/11/08 Javascript
如何使node也支持从url加载一个module详解
2018/06/05 Javascript
解析vue、angular深度作用选择器
2019/09/11 Javascript
vue仿ios列表左划删除
2019/09/26 Javascript
JavaScript简单编程实例学习
2020/02/14 Javascript
[41:08]2014 DOTA2国际邀请赛中国区预选赛 HGT VS NE
2014/05/22 DOTA
Python2.x版本中maketrans()方法的使用介绍
2015/05/19 Python
python使用urllib2提交http post请求的方法
2015/05/26 Python
给你选择Python语言实现机器学习算法的三大理由
2017/11/15 Python
Python动态生成多维数组的方法示例
2018/08/09 Python
Python实现的银行系统模拟程序完整案例
2019/04/12 Python
对django2.0 关联表的必填on_delete参数的含义解析
2019/08/09 Python
使用Jupyter notebooks上传文件夹或大量数据到服务器
2020/04/14 Python
jupyter notebook读取/导出文件/图片实例
2020/04/16 Python
python 使用raw socket进行TCP SYN扫描实例
2020/05/05 Python
HTML5调用手机发短信和打电话功能
2020/04/29 HTML / CSS
科沃斯机器人官网商城:Ecovacs
2016/08/29 全球购物
英国领先的杂志订阅网站:Magazine.co.uk
2018/01/25 全球购物
Audible英国:有声读物,30天免费试用
2019/10/16 全球购物
党员学习中共十八大思想报告
2014/09/12 职场文书
2014年医德医风工作总结
2014/11/13 职场文书
小学音乐教师个人工作总结
2015/02/05 职场文书
质量保证书格式
2015/02/27 职场文书
个人年底工作总结
2015/03/10 职场文书
新闻发布会新闻稿
2015/07/17 职场文书