vue ssr+koa2构建服务端渲染的示例代码


Posted in Javascript onMarch 23, 2020

之前做了活动投放页面在百度、360等渠道投放,采用 koa2 + 模版引擎的方式。发现几个问题

  • 相较于框架开发页面效率较低,维护性差
  • 兼容性问题,在页面中添加埋点后发现有些用户的数据拿不到,排查后发现通过各个渠道过来的用户的设备中仍然包含大量低版本的浏览器。

 服务端渲染

 服务端渲染和单页面渲染区别

查看下面两张图,可以看到如果是服务端渲染,那么在浏览器中拿到的直接是完整的 html 结构。而单页面是一些 script 标签引入的js文件,最终将虚拟dom去挂在到 #app 容器上。

vue ssr+koa2构建服务端渲染的示例代码 

vue ssr+koa2构建服务端渲染的示例代码 

@vue/cli 4 来构建项目结构

下面代码使用最精简的实例完整代码会放到 github 上

step1 安装最新的脚手架初始化项目

yarn global add @vue/cli

step2 添加服务端文件

启动一个 web 服务下方代码中 http://localhost:9000 就是我们最终要访问到地址

const Koa = require('koa')
const path = require('path')

const resolve = file => path.resolve(__dirname, file)
const app = new Koa()
const router = require('./router')
const port = 9000
app.listen(port, () => {
 console.log(`server started at localhost:${port}`)
})
module.exports = app

这里只是启动了服务,我们需要在去读取服务端和客户端到文件,下面代码就是服务端渲染的关键步骤

const fs = require('fs')
const path = require('path')
const send = require('koa-send')
const Router = require('koa-router')
const router = new Router()
// 获取当前文件的绝对路径
const resolve = file => path.resolve(__dirname, file)
const { createBundleRenderer } = require('vue-server-renderer')
const bundle = require('../dist/vue-ssr-server-bundle.json')
const clientManifest = require('../dist/vue-ssr-client-manifest.json')
// 创建一个 BunleRender 实例用于 renderer.renderToString 将 bundle 渲染为字符串
const renderer = createBundleRenderer(bundle, {
 runInNewContext: false,
 template: fs.readFileSync(resolve('../src/index.temp.html'), 'utf-8'),
 clientManifest: clientManifest
})

const handleRequest = async ctx => {
 ctx.res.setHeader('Content-Type', 'text/html')
 // 在 2.5.0+ 版本中,此 callback 回调函数是可选项。在不传递 callback 时,此方法返回一个 Promise 对象,在其 resolve 后返回最终渲染的 HTML。
 ctx.body = await renderer.renderToString(Object.assign({}, ctx.state.deliver, { url }))
}
router.get('/home',handleRequest)
module.exports = router

vue-server-render 提供一个名为 createBundleRenderer 的 API 使用方法如下

const { createBundleRenderer } = require('vue-server-renderer')

const renderer = createBundleRenderer(serverBundle, {
 runInNewContext: false, // 推荐
 template, // (可选)页面模板
 clientManifest // (可选)客户端构建 manifest
})

通过上面的 createBundleRenderer 方法生产 render 对象最终将 bunlde 渲染为字符串,将最终的 html 返回给客户端。

bundleRenderer.renderToString([context, callback]): ?Promise<string>

step3 添加 entry-client.js,entry-server.js 入口文件

在 src 中除了这两个入口文件,其他的文件都是在客户端和服务端公用的。来看下这两个入口文件中分别干了什么。

大体的流程就是:服务端创建 vue 实例,将页面中的异步请求的数据拿到存储在容器中 --> 客户端接收到服务端发送的 html 以激活模式进行挂载,自动给根元素 #app 上添加 data-server-rendered="true" 特殊属性

main.js

import Vue from 'vue'
import App from './App.vue'
...
export function createApp() {
 // ...
 const app = new Vue({
 router,
 store,
 render: h => h(App)
 })
 return { app, router, store }
}

entry-server.js

import { createApp } from './main.js'
export default context => {
 // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
 // 以便服务器能够等待所有的内容在渲染前,
 // 就已经准备就绪。
 return new Promise((resolve, reject) => {
 const { app, router, store } = createApp()
 // 设置服务器端 router 的位置
 router.push(context.url)

 // 等到 router 将可能的异步组件和钩子函数解析完
 router.onReady(() => {
  const matchedComponents = router.getMatchedComponents()

  // 匹配不到的路由,执行 reject 函数,并返回 404
  if (!matchedComponents.length) {
  return reject({ code: 404 })
  }
  Promise.all(
  matchedComponents.map(component => {
   if (component.asyncData) {
   return component.asyncData({
    store,
    context,
    route: router.currentRoute
   })
   }
  })
  ).then(() => {
  // 在所有预取钩子(preFetch hook) resolve 后,
  // 我们的 store 现在已经填充入渲染应用程序所需的状态。
  // 当我们将状态附加到上下文,
  // 并且 `template` 选项用于 renderer 时,
  // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
  // 否则会导致客户端和服务端数据不统一造成渲染错误
  context.state = store.state
  resolve(app)
  }).catch(reject)
 }, reject)
 })
}

entry-client.js

import { createApp } from './main'
const { app, router, store } = createApp()

if (window.__INITIAL_STATE__) {
 store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
 router.beforeResolve((to, from, next) => {
 const matched = router.getMatchedComponents(to)
 const prevMatched = router.getMatchedComponents(from)
 let diffed = false
 const activated = matched.filter((c, i) => {
  return diffed || (diffed = prevMatched[i] !== c)
 })

 if (!activated.length) {
  return next()
 }

 Promise.all(
  activated.map(component => {
  if (component.asyncData) {
   component.asyncData({
   store,
   route: to
   })
  }
  })
 )
  .then(() => {
  next()
  })
  .catch(next)
 })
 app.$mount('#app')
})

最后

完整代码参考 github地址

顺便贴上这张图

 vue ssr+koa2构建服务端渲染的示例代码

到此这篇关于vue ssr+koa2构建服务端渲染的示例代码的文章就介绍到这了,更多相关vue ssr koa2 服务端渲染内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
IE innerHTML,outerHTML所引起的问题
Jun 04 Javascript
javascript appendChild,innerHTML,join性能比较代码
Aug 29 Javascript
JS判断页面加载状态以及添加遮罩和缓冲动画的代码
Oct 11 Javascript
基于pthread_create,readlink,getpid等函数的学习与总结
Jul 17 Javascript
基于Jquery和CSS3制作数字时钟附源码下载(CSS3篇)
Nov 24 Javascript
js实现小窗口拖拽效果
Dec 03 Javascript
js的OOP继承实现(必看篇)
Feb 18 Javascript
ES6新增的math,Number方法
Aug 06 Javascript
微信小程序使用navigateTo数据传递的实例
Sep 26 Javascript
使用Angular CLI进行Build(构建)和Serve详解
Mar 24 Javascript
通过jquery.cookie.js实现记住用户名、密码登录功能
Jun 20 jQuery
Vue2.2.0+新特性整理及注意事项
Aug 22 Javascript
详解webpack-dev-middleware 源码解读
Mar 23 #Javascript
vscode调试node.js的实现方法
Mar 22 #Javascript
如何优雅地取消 JavaScript 异步任务
Mar 22 #Javascript
Vue-cli3多页面配置详解
Mar 22 #Javascript
redux处理异步action解决方案
Mar 22 #Javascript
JS+CSS实现3D切割轮播图
Mar 21 #Javascript
vue-autoui自匹配webapi的UI控件的实现
Mar 20 #Javascript
You might like
微信公众平台网页授权获取用户基本信息中授权回调域名设置的变动
2014/10/21 PHP
PHP用反撇号执行外部命令
2015/04/14 PHP
PHP网络操作函数汇总
2015/05/18 PHP
PHP基于面向对象封装的分页类示例
2019/03/15 PHP
[原创]后缀就扩展名为js的文件是什么文件
2007/12/06 Javascript
深入理解javascript动态插入技术
2013/11/12 Javascript
JS获取html对象的几种方式介绍
2013/12/05 Javascript
javascript实例--教你实现扑克牌洗牌功能
2014/05/15 Javascript
JS循环遍历JSON数据的方法
2014/07/08 Javascript
jQuery解析XML文件同时动态增加js文件的方法
2015/06/01 Javascript
使用bootstrap3开发响应式网站
2016/05/12 Javascript
详解Vue.js项目API、Router配置拆分实践
2018/03/16 Javascript
Vue请求JSON Server服务器数据的实现方法
2018/11/02 Javascript
React 实现车牌键盘的示例代码
2019/12/20 Javascript
JS实现纵向轮播图(初级版)
2020/01/18 Javascript
vue element ui validate 主动触发错误提示操作
2020/09/21 Javascript
element-plus一个vue3.xUI框架(element-ui的3.x 版初体验)
2020/12/02 Vue.js
Vue基本指令实例图文讲解
2021/02/25 Vue.js
Python数据可视化编程通过Matplotlib创建散点图代码示例
2017/12/09 Python
python使用PIL模块获取图片像素点的方法
2019/01/08 Python
Python中的支持向量机SVM的使用(附实例代码)
2019/06/26 Python
Django模板导入母版继承和自定义返回Html片段过程解析
2019/09/18 Python
浅析PEP572: 海象运算符
2019/10/15 Python
基于Tensorflow读取MNIST数据集时网络超时的解决方式
2020/06/22 Python
Python实现图片查找轮廓、多边形拟合、最小外接矩形代码
2020/07/14 Python
python操作redis数据库的三种方法
2020/09/10 Python
Python lxml库的简单介绍及基本使用讲解
2020/12/22 Python
css3过渡_动力节点Java学院整理
2017/07/11 HTML / CSS
2014年消防工作实施方案
2014/02/20 职场文书
二年级小学生评语
2014/04/21 职场文书
董事长助理工作职责范本
2014/07/01 职场文书
2014离婚协议书范文(3篇)
2014/11/29 职场文书
商场圣诞节活动总结
2015/05/06 职场文书
刮痧观后感
2015/06/05 职场文书
Java移除无效括号的方法实现
2021/08/07 Java/Android
《极主夫道》真人电影正式预告 定档6月3日上映
2022/04/05 日漫