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 相关文章推荐
js左侧多级菜单动态的解决方案
Feb 01 Javascript
Jquery Validation插件防止重复提交表单的解决方法
Mar 05 Javascript
用方法封装javascript的new操作符(一)
Dec 25 Javascript
jQuery结合CSS制作动态的下拉菜单
Oct 27 Javascript
各式各样的导航条效果css3结合jquery代码实现
Sep 17 Javascript
手机软键盘弹出时影响布局的解决方法
Dec 15 Javascript
Vue动态获取width的方法
Aug 22 Javascript
Vuex 使用及简单实例(计数器)
Aug 29 Javascript
微信小程序登录按钮遮罩浮层效果的实现方法
Dec 16 Javascript
详解javascript void(0)
Jul 13 Javascript
Vue 解决父组件跳转子路由后当前导航active样式消失问题
Jul 21 Javascript
vue将文件/图片批量打包下载zip的教程
Oct 21 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
PHP中for循环语句的几种变型
2006/11/26 PHP
ThinkPHP采用模块和操作分析
2011/04/18 PHP
linux系统上支持php的 iconv()函数的方法
2011/10/01 PHP
Jquery iframe内部出滚动条
2010/02/11 Javascript
js substr支持中文截取函数代码(中文是双字节)
2013/04/17 Javascript
JavaScript中的函数的两种定义方式和函数变量赋值
2014/05/12 Javascript
用js实现before和after伪类的样式修改的示例代码
2017/09/07 Javascript
微信小程序实现下载进度条的方法
2017/12/08 Javascript
简单谈谈CommonsChunkPlugin抽取公共模块
2017/12/31 Javascript
vue-cli3搭建项目的详细步骤
2018/12/05 Javascript
微信小程序iOS下拉白屏晃动问题解决方案
2019/10/12 Javascript
[04:36]DOTA2国际邀请赛 ti3精彩集锦
2013/08/19 DOTA
Python实现Sqlite将字段当做索引进行查询的方法
2016/07/21 Python
Python入门_浅谈字符串的分片与索引、字符串的方法
2017/05/16 Python
详解Python使用tensorflow入门指南
2018/02/09 Python
python3获取当前文件的上一级目录实例
2018/04/26 Python
查看端口并杀进程python脚本代码
2019/12/17 Python
Python socket聊天脚本代码实例
2020/01/02 Python
python实现拼接图片
2020/03/23 Python
Python 3.10 的首个 PEP 诞生,内置类型 zip() 迎来新特性(推荐)
2020/07/03 Python
Python面向对象实现方法总结
2020/08/12 Python
Pytest如何使用skip跳过执行测试
2020/08/13 Python
Python调用ffmpeg开源视频处理库,批量处理视频
2020/11/16 Python
地球上最先进的胡子和头发修剪器:Bevel
2018/01/23 全球购物
英国家用电器折扣网站:Electrical Discount UK
2018/09/17 全球购物
英国最大的在线快递公司之一:ParcelHero
2019/11/04 全球购物
英国书籍、CD、DVD和游戏的第一道德零售商:Awesome Books
2020/02/22 全球购物
用C或者C++语言实现SOCKET通信
2015/02/24 面试题
《最后的姿势》教学反思
2014/02/27 职场文书
巾帼志愿者活动方案
2014/08/17 职场文书
2015自愿离婚协议书范本
2015/01/28 职场文书
教师岗位说明书
2015/09/30 职场文书
感谢信
2019/04/11 职场文书
go语言中切片与内存复制 memcpy 的实现操作
2021/04/27 Golang
Python基础之进程详解
2021/05/21 Python
vue2实现provide inject传递响应式
2021/05/21 Vue.js