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 有用的脚本函数
May 07 Javascript
JS验证邮件地址格式方法小结
Dec 01 Javascript
JavaScript实现url参数转成json形式
Sep 25 Javascript
bootstrap手风琴制作方法详解
Jan 11 Javascript
javascript 动态生成css代码的两种方法
Mar 17 Javascript
深入理解node.js之path模块
May 03 Javascript
使用ES6语法重构React代码详解
May 09 Javascript
Vue filter介绍及其使用详解
Oct 21 Javascript
微信小程序传值以及获取值方法的详解
Apr 29 Javascript
Vue实现日历小插件
Jun 26 Javascript
微信小程序 wx:for 与 wx:for-items 与 wx:key的正确用法
May 19 Javascript
react合成事件与原生事件的相关理解
May 13 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数据库信息代码
2009/03/12 PHP
PHPExcel读取Excel文件的实现代码
2011/12/06 PHP
smarty中js的调用方法示例
2014/10/27 PHP
PHP中的Streams详细介绍
2014/11/12 PHP
Joomla语言翻译类Jtext用法分析
2016/05/05 PHP
php防止表单重复提交实例讲解
2019/02/11 PHP
编写针对IE的JS代码两种编写方法
2013/01/30 Javascript
javascript(js)的小数点乘法除法问题详解
2014/03/07 Javascript
浅谈 jQuery 事件源码定位问题
2014/06/18 Javascript
js添加事件的通用方法推荐
2016/05/15 Javascript
Angular.js ng-file-upload结合springMVC的使用教程
2017/07/10 Javascript
Angular实现下拉框模糊查询功能示例
2018/01/03 Javascript
在 webpack 中使用 ECharts的实例详解
2018/02/05 Javascript
浅谈webpack4.x 入门(一篇足矣)
2018/09/05 Javascript
vuex实现购物车功能
2020/06/28 Javascript
python操作数据库之sqlite3打开数据库、删除、修改示例
2014/03/13 Python
python获取本机mac地址和ip地址的方法
2015/04/29 Python
Python操作列表之List.insert()方法的使用
2015/05/20 Python
使用Python的package机制如何简化utils包设计详解
2017/12/11 Python
TensorFlow实现简单卷积神经网络
2018/05/24 Python
Python对象属性自动更新操作示例
2018/06/15 Python
python 用for循环实现1~n求和的实例
2019/02/01 Python
在vscode中配置python环境过程解析
2019/09/28 Python
Python+Appium实现自动化测试的使用步骤
2020/03/24 Python
canvas实现图片镜像翻转的2种方式
2020/07/22 HTML / CSS
Spartoo英国:欧洲最大的网上鞋店
2016/09/13 全球购物
办公室文员工作职责
2014/01/31 职场文书
服务口号大全
2014/06/11 职场文书
应聘护士求职信
2014/07/21 职场文书
2014乡镇班子个人对照检查材料思想汇报
2014/09/26 职场文书
写给医生的感谢信
2015/01/22 职场文书
用人单位聘用意向书
2015/05/11 职场文书
幼儿园开学报名通知
2015/07/16 职场文书
管理者们如何制定2019年的工作计划?
2019/07/01 职场文书
Java使用httpRequest+Jsoup爬取红蓝球号码
2021/07/02 Java/Android
JavaWeb 入门篇(3)ServletContext 详解 具体应用
2021/07/16 Java/Android