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 相关文章推荐
jQuery示例收集
Nov 05 Javascript
AJAX分页的代码(后台asp.net)
Feb 14 Javascript
jquery简单瀑布流实现原理及ie8下测试代码
Jan 23 Javascript
举例详解Python中smtplib模块处理电子邮件的使用
Jun 24 Javascript
javascript实现简单的页面右下角提示信息框
Jul 31 Javascript
jqueryMobile 动态添加元素,展示刷新视图的实现方法
May 28 Javascript
javascript输出AscII码扩展集中的字符方法
Dec 26 Javascript
Vue.js实现微信过渡动画左右切换效果
Jun 13 Javascript
原生js实现简单的链式操作
Jul 04 Javascript
React中嵌套组件与被嵌套组件的通信过程
Jul 11 Javascript
详解Vue.js使用Swiper.js在iOS
Sep 10 Javascript
mpvue 项目初始化及实现授权登录的实现方法
Jul 20 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开发入门教程之面向对象
2006/12/05 PHP
php之对抗Web扫描器的脚本技巧
2008/10/01 PHP
php实现图片文件与下载文件防盗链的方法
2014/11/03 PHP
Ajax+Jpgraph实现的动态折线图功能示例
2019/02/11 PHP
swoole锁的机制代码实例讲解
2021/03/04 PHP
js 判断脚本加载完毕的代码
2011/07/13 Javascript
jquery模拟按下回车实现代码
2011/09/20 Javascript
php+js实现倒计时功能
2014/06/02 Javascript
zepto.js中tap事件阻止冒泡的实现方法
2015/02/12 Javascript
js/jquery判断浏览器类型的方法小结
2015/05/12 Javascript
关于input全选反选恶心的异常情况
2016/07/24 Javascript
Angular实现预加载延迟模块的示例
2017/10/12 Javascript
JavaScript实现新年倒计时效果
2018/11/17 Javascript
js实现微信聊天效果
2020/08/09 Javascript
一篇文章带你搞懂Vue虚拟Dom与diff算法
2020/08/25 Javascript
[01:20:30]OG vs LGD 2018国际邀请赛淘汰赛BO3 第四场 8.26
2018/08/30 DOTA
基于Python os模块常用命令介绍
2017/11/03 Python
pandas or sql计算前后两行数据间的增值方法
2018/04/20 Python
基于h5py的使用及数据封装代码
2019/12/26 Python
Docker部署Python爬虫项目的方法步骤
2020/01/19 Python
python实现拼图小游戏
2020/02/22 Python
python 使用事件对象asyncio.Event来同步协程的操作
2020/05/04 Python
Python 如何查找特定类型文件
2020/08/17 Python
详解Python中的编码问题(encoding与decode、str与bytes)
2020/09/30 Python
Answear匈牙利:来自全球200多个知名时尚品牌
2017/04/21 全球购物
Net Remoting把服务器端激活两种模式
2014/01/22 面试题
国际贸易专业推荐信
2013/11/15 职场文书
父亲生日宴会答谢词
2014/01/10 职场文书
房屋出租协议书
2014/04/10 职场文书
关于美容院的活动方案
2014/08/14 职场文书
小学推普周活动总结
2015/05/07 职场文书
辩论赛开场白大全(主持人+辩手)
2015/05/29 职场文书
Pytorch 如何加速Dataloader提升数据读取速度
2021/05/28 Python
React中的Context应用场景分析
2021/06/11 Javascript
Python机器学习之底层实现KNN
2021/06/20 Python
【海涛dota解说】DCG联赛第一周 LGD VS DH
2022/04/01 DOTA