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 DOM编程艺术笔记
Nov 15 Javascript
jQuery实现页面滚动时层智能浮动定位实例探讨
Mar 29 Javascript
Js base64 加密解密介绍
Oct 11 Javascript
通过onmouseover选项卡实现img图片的变化
Feb 12 Javascript
IE 下Enter提交表单存在重复提交问题的解决方法
May 04 Javascript
JavaScript必知必会(六) delete in instanceof
Jun 08 Javascript
AngularJS出现$http异步后台无法获取请求参数问题的解决方法
Nov 03 Javascript
node.js利用mongoose获取mongodb数据的格式化问题详解
Oct 06 Javascript
vue 动态改变静态图片以及请求网络图片的实现方法
Feb 07 Javascript
微信小程序onLaunch异步,首页onLoad先执行?
Sep 20 Javascript
微信小程序功能之全屏滚动效果的实现代码
Nov 22 Javascript
浅谈vue后台管理系统权限控制思考与实践
Dec 19 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
PHP产生不重复随机数的5个方法总结
2014/11/12 PHP
thinkphp3.2嵌入百度编辑器ueditor的实例代码
2017/07/13 PHP
PHPStorm2020.1永久激活及下载更新至2020(推荐)
2020/09/25 PHP
Nigma vs Alliance BO5 第一场2.14
2021/03/10 DOTA
javascript之ESC(第二类混淆)
2007/05/06 Javascript
Jquery 的扩展方法总结
2011/10/01 Javascript
如何在一个页面显示多个百度地图
2013/04/07 Javascript
JS下载文件|无刷新下载文件示例代码
2014/04/17 Javascript
js实现网页随机切换背景图片的方法
2014/11/01 Javascript
jquery动态添加删除(tr/td)
2015/02/09 Javascript
jQuery简单实现QQ空间点赞已经取消点赞
2015/04/02 Javascript
JS+CSS实现下拉列表框美化效果(3款)
2015/08/15 Javascript
JS实现选项卡实例详解
2015/11/17 Javascript
js实现文字垂直滚动和鼠标悬停效果
2015/12/31 Javascript
JS键盘版计算器的制作方法
2016/12/03 Javascript
JS实现的抛物线运动效果示例
2018/01/30 Javascript
移动端H5页面返回并刷新页面(BFcache)的方法
2018/11/06 Javascript
JavaScript实现动态添加、移除元素或属性的方法分析
2019/01/03 Javascript
vue实现条件叠加搜索的解决方法
2019/05/28 Javascript
vue遍历对象中的数组取值示例
2019/11/07 Javascript
微信小程序wx.navigateTo方法里的events参数使用详情及场景
2020/01/07 Javascript
Vue中通过属性绑定为元素绑定style行内样式的实例代码
2020/04/30 Javascript
使用python 获取进程pid号的方法
2014/03/10 Python
Python注释详解
2016/06/01 Python
Python文件操作之合并文本文件内容示例代码
2017/09/19 Python
用tensorflow构建线性回归模型的示例代码
2018/03/05 Python
django认证系统实现自定义权限管理的方法
2018/07/16 Python
Python Django 简单分页的实现代码解析
2019/08/21 Python
Python @property使用方法解析
2019/09/17 Python
python日志模块logbook使用方法
2019/09/19 Python
惠普墨西哥官方商店:HP墨西哥
2016/12/01 全球购物
诗普兰迪官方网站:Splendid
2018/09/18 全球购物
中文系学生自荐信范文
2013/11/13 职场文书
教师个人自我评价范文
2014/04/13 职场文书
幼儿园中班区域活动总结
2014/07/09 职场文书
Python爬虫数据的分类及json数据使用小结
2021/03/29 Python