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学习笔记(十八) 获得页面中的元素代码
Jun 20 Javascript
escape函数解决js中ajax传递中文出现乱码问题
Oct 30 Javascript
JavaScript中用字面量创建对象介绍
Dec 31 Javascript
Vue.js Ajax动态参数与列表显示实现方法
Oct 20 Javascript
vuejs指令详解
Feb 07 Javascript
原生JS轮播图插件
Feb 09 Javascript
了解VUE的render函数的使用
Jun 08 Javascript
微信小程序开发之实现自定义Toast弹框
Jun 08 Javascript
利用C/C++编写node.js原生模块的方法教程
Jul 07 Javascript
Vue 动态设置路由参数的案例分析
Apr 24 Javascript
微信小程序wx.getUserInfo授权获取用户信息(头像、昵称)的实现
Aug 19 Javascript
解决ant Design中Select设置initialValue时的大坑
Oct 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程序员常见的40个陋习,你中了几个?
2014/11/20 PHP
基于JQuery的Pager分页器实现代码
2010/07/17 Javascript
jQuery ReferenceError: $ is not defined 错误的处理办法
2013/05/10 Javascript
JavaScript变量声明详解
2014/11/27 Javascript
基于javascript实现的搜索时自动提示功能
2014/12/26 Javascript
jQuery简单几行代码实现tab切换
2015/03/10 Javascript
JavaScript基本语法_动力节点Java学院整理
2017/06/26 Javascript
vue iview组件表格 render函数的使用方法详解
2018/03/15 Javascript
微信小程序中使用自定义图标(阿里icon)的方法
2018/08/20 Javascript
JavaScript文本特效实例小结【3个示例】
2018/12/22 Javascript
AntV F2和vue-cli构建移动端可视化视图过程详解
2019/10/08 Javascript
javascript设计模式 ? 建造者模式原理与应用实例分析
2020/04/10 Javascript
Vue的props父传子的示例代码
2020/05/20 Javascript
vue 项目引入echarts 添加点击事件操作
2020/09/09 Javascript
pymssql ntext字段调用问题解决方法
2008/12/17 Python
Python中的包和模块实例
2014/11/22 Python
python 队列详解及实例代码
2016/10/18 Python
详解python中字典的循环遍历的两种方式
2017/02/07 Python
树莓派4B+opencv4+python 打开摄像头的实现方法
2019/10/18 Python
Python计算公交发车时间的完整代码
2020/02/12 Python
Python itertools.product方法代码实例
2020/03/27 Python
英国最大的在线运动补充剂商店:Discount Supplements
2017/06/03 全球购物
Blue Nile蓝色尼罗河香港官网:世界最大在线钻石珠宝销售商
2020/05/07 全球购物
如何写你的创业计划书
2014/01/07 职场文书
违反校纪校规检讨书
2014/02/15 职场文书
庆元旦迎新年广播稿
2014/02/18 职场文书
幼儿教师师德演讲稿
2014/05/06 职场文书
医学求职自荐信
2014/06/21 职场文书
设备技术员岗位职责
2015/04/11 职场文书
公积金具结保证书
2015/05/11 职场文书
银行培训心得体会范文
2016/01/09 职场文书
2016年6.5世界环境日宣传活动总结
2016/04/01 职场文书
描写九月优美句子(39条)
2019/09/11 职场文书
JS代码编译器Monaco使用方法
2021/06/11 Javascript
centos8安装nginx1.9.1的详细过程
2021/08/02 Servers
【海涛dota解说】海涛小满开黑4v5被破两路翻盘潮汐第一视角解说
2022/04/01 DOTA