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 相关文章推荐
document对象execCommand的command参数介绍
Aug 01 Javascript
jquery+ajax每秒向后台发送请求数据然后返回页面的代码
Jan 17 Javascript
jQuery获取当前对象标签名称的方法
Feb 07 Javascript
JavaScript 动态加载脚本和样式的方法
Apr 13 Javascript
js获取数组的最后一个元素
Apr 14 Javascript
JavaScript中的Array 对象(数组对象)
Jun 02 Javascript
javascript创建含数字字母的随机字符串方法总结
Aug 01 Javascript
jQuery实现的右下角广告窗体跟随效果示例
Sep 16 Javascript
vue实现单选和多选功能
Aug 11 Javascript
Vue 页面状态保持页面间数据传输的一种方法(推荐)
Nov 01 Javascript
JS+CSS3实现的简易钟表效果示例
Apr 13 Javascript
微信小程序用户授权弹窗 拒绝时引导用户重新授权实现
Jul 29 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代码
2013/03/24 PHP
PHP实现微信申请退款功能
2018/10/01 PHP
ThinkPHP5.1框架页面跳转及修改跳转页面模版示例
2019/05/06 PHP
jQuery easyui datagrid动态查询数据实例讲解
2013/02/26 Javascript
JS的千分位算法实现思路
2013/07/31 Javascript
nodejs导出excel的方法
2015/06/30 NodeJs
Bootstrap入门书籍之(零)Bootstrap简介
2016/02/17 Javascript
使用jQuery制作浮动工具栏的实例分享
2016/05/13 Javascript
基于JS递归函数细化认识及实用实例(推荐)
2017/08/07 Javascript
JS+jQuery实现注册信息的验证功能
2017/09/26 jQuery
浅谈Vue Element中Select下拉框选取值的问题
2018/03/01 Javascript
JS 实现微信扫一扫功能
2018/09/14 Javascript
vue如何安装使用Quill富文本编辑器
2018/09/21 Javascript
Vue匿名插槽与作用域插槽的合并和覆盖行为
2019/04/22 Javascript
JS的时间格式化和时间戳转换函数示例详解
2020/07/27 Javascript
js实现盒子移动动画效果
2020/08/09 Javascript
Python获取Linux系统下的本机IP地址代码分享
2014/11/07 Python
django批量导入xml数据
2016/10/16 Python
python利用dir函数查看类中所有成员函数示例代码
2017/09/08 Python
pycharm创建一个python包方法图解
2019/04/10 Python
基于Django ORM、一对一、一对多、多对多的全面讲解
2019/07/26 Python
Python实现多线程/多进程的TCP服务器
2019/09/03 Python
详解anaconda安装步骤
2020/11/23 Python
CSS3媒体查询Media Queries基础学习教程
2016/02/29 HTML / CSS
Nixon手表英国官网:美国尼克松手表品牌
2020/02/10 全球购物
PHP如何对用户密码进行加密
2014/07/31 面试题
C#如何允许一个类被继承但是避免这个类的方法被重载?
2015/02/24 面试题
乡镇综治宣传月活动总结
2014/07/02 职场文书
个人房屋买卖协议书(范本)
2014/10/04 职场文书
幼儿园大班教师个人总结
2015/02/05 职场文书
电气工程师岗位职责
2015/02/12 职场文书
入党培养人考察意见
2015/06/08 职场文书
房屋转让协议书(标准范本)
2016/03/21 职场文书
Windows环境下实现批量执行Sql文件
2021/10/05 SQL Server
Go语言入门exec的基本使用
2022/05/20 Golang
解决ubuntu安装软件时,status-code=409报错的问题
2022/12/24 Servers