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 相关文章推荐
JS的递增/递减运算符和带操作的赋值运算符的等价式
Dec 08 Javascript
JavaScript 面向对象编程(2) 定义类
May 18 Javascript
js 返回时间戳所对应的具体时间
Jul 20 Javascript
jquery-syntax动态语法着色示例代码
May 14 Javascript
再探JavaScript作用域
Sep 24 Javascript
js中匿名函数的创建与调用方法分析
Dec 19 Javascript
15个jquery常用方法、小技巧分享
Jan 13 Javascript
JS模拟按钮点击功能的方法
Dec 22 Javascript
轻松实现Bootstrap图片轮播
Apr 20 Javascript
jQuery链式调用与show知识浅析
May 11 Javascript
小程序实现授权登陆的解决方案
Dec 02 Javascript
详解Vue.js 响应接口
Jul 04 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实现的MySQL数据浏览器
2007/03/11 PHP
利用PHP函数计算中英文字符串长度的方法
2014/11/11 PHP
php自定义加密与解密程序实例
2014/12/31 PHP
全面解析PHP验证码的实现原理 附php验证码小案例
2016/08/17 PHP
PHP实现RSA签名生成订单功能【支付宝示例】
2017/06/06 PHP
PHP 的Opcache加速的使用方法
2017/12/29 PHP
prototype class详解
2006/09/07 Javascript
IE JS编程需注意的内存释放问题
2009/06/23 Javascript
Prototype的Class.create函数解析
2011/09/22 Javascript
利用毫秒减值计算时长的js代码
2013/09/22 Javascript
jquery事件与函数的使用介绍
2013/09/29 Javascript
JS控制图片翻转示例代码(兼容firefox,ie,chrome)
2013/12/19 Javascript
javascript面向对象程序设计(一)
2015/01/29 Javascript
JS制作简单的三级联动
2015/03/18 Javascript
使用nodejs爬取前程无忧前端技能排行
2017/05/06 NodeJs
Node.js pipe实现源码解析
2017/08/12 Javascript
JS弹窗 JS弹出DIV并使整个页面背景变暗功能的实现代码
2018/04/21 Javascript
js图片无缝滚动插件使用详解
2020/05/26 Javascript
node使用request请求的方法
2019/12/20 Javascript
javascript 数组(list)添加/删除的实现
2020/12/17 Javascript
[01:16:37]【全国守擂赛】第三周决赛 Dark Knight vs. 一个弱队
2020/05/04 DOTA
Python中的defaultdict模块和namedtuple模块的简单入门指南
2015/04/01 Python
Python实现计算最小编辑距离
2016/03/17 Python
Python及PyCharm下载与安装教程
2017/11/18 Python
浅析python参数的知识点
2018/12/10 Python
对python中的argv和argc使用详解
2018/12/15 Python
python定时任务 sched模块用法实例
2019/11/04 Python
numpy:np.newaxis 实现将行向量转换成列向量
2019/11/30 Python
HTML5 localStorage使用总结
2017/02/22 HTML / CSS
加拿大女装网上购物:Reitmans
2016/10/20 全球购物
可口可乐唇膏:Lip Smackers
2019/08/27 全球购物
品学兼优的大学生自我评价
2013/09/20 职场文书
服务生自我鉴定
2014/01/22 职场文书
清明节网上祭英烈活动总结
2014/04/30 职场文书
委托书怎样写
2014/08/30 职场文书
中层干部考核评语
2015/01/04 职场文书