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 相关文章推荐
了解了这些才能开始发挥jQuery的威力
Oct 10 Javascript
jquery将一个表单序列化为一个对象的方法
Dec 02 Javascript
node.js中的fs.lchownSync方法使用说明
Dec 16 Javascript
jquery隔行换色效果实现方法
Jan 15 Javascript
如何用jQuery实现ASP.NET GridView折叠伸展效果
Sep 26 Javascript
jQuery监听浏览器窗口大小的变化实例
Feb 07 Javascript
JS简单实现查看文档创建日期、修改日期和文档大小的方法示例
Apr 08 Javascript
35个最好用的Vue开源库(史上最全)
Jan 03 Javascript
简单两步使用node发送qq邮件的方法
Mar 01 Javascript
django js 实现表格动态标序号的实例代码
Jul 12 Javascript
js实现div色块拖动录制
Jan 16 Javascript
Node.js中文件系统fs模块的使用及常用接口
Mar 06 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
支持php4、php5的mysql数据库操作类
2008/01/10 PHP
PHP分页函数代码(简单实用型)
2010/12/02 PHP
php实现图片上传、剪切功能
2016/05/07 PHP
PHP编程实现多维数组按照某个键值排序的方法小结【2种方法】
2017/04/27 PHP
PHP实时统计中文字数和区别
2019/02/28 PHP
jquery.simple.tree插件 更简单,兼容性更好的无限树插件
2010/09/03 Javascript
收集的一些Array及String原型对象的扩展实现代码
2010/12/05 Javascript
js对象数组按属性快速排序
2011/01/31 Javascript
基于jquery DOM写的类似微博发布的效果
2012/10/20 Javascript
js操作IE浏览器弹出浏览文件夹可以返回目录路径
2014/07/14 Javascript
Thinkphp模板没有解析直接原样输出的解决方法
2014/10/31 Javascript
浅谈javascript语法和定时函数
2015/05/03 Javascript
jQuery判断一个元素是否可见的方法
2015/06/05 Javascript
js css+html实现简单的日历
2016/07/14 Javascript
微信小程序 wx.login解密出现乱码的问题解决办法
2017/03/10 Javascript
MvcPager分页控件 适用于Bootstrap
2017/06/03 Javascript
关于Vue背景图打包之后访问路径错误问题的解决
2017/11/03 Javascript
如何让你的JS代码更好看易读
2017/12/01 Javascript
JS计算距当前时间的时间差实例
2017/12/29 Javascript
Node.js npm命令运行node.js脚本的方法
2018/10/10 Javascript
只有 20 行的 JavaScript 模板引擎实例详解
2020/05/11 Javascript
关于angular 8.1使用过程中的一些记录
2020/11/25 Javascript
[02:44]DOTA2英雄基础教程 魅惑魔女
2014/01/07 DOTA
python中循环语句while用法实例
2015/05/16 Python
Python实现扣除个人税后的工资计算器示例
2018/03/26 Python
在python中list作函数形参,防止被实参修改的实现方法
2020/06/05 Python
python开发一款翻译工具
2020/10/10 Python
使用HTML5拍照示例代码
2013/08/06 HTML / CSS
一些PHP的面试题
2015/05/06 面试题
个人综合鉴定材料
2014/05/23 职场文书
员工生日会策划方案
2014/06/14 职场文书
四风批评与自我批评发言稿
2014/10/14 职场文书
群众路线表态发言材料
2014/10/17 职场文书
学习心理学的体会
2014/11/07 职场文书
2015年控辍保学工作总结
2015/05/18 职场文书
2015年新农村建设指导员工作总结
2015/07/24 职场文书