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去除字符串的空格增强版(可以去除中间的空格)
Aug 26 Javascript
javascript 获取表单file全路径
Dec 31 Javascript
javascript 多种搜索引擎集成的页面实现代码
Jan 02 Javascript
js实现动态改变字体大小代码
Jan 02 Javascript
Jquery+Ajax+PHP+MySQL实现分类列表管理(上)
Oct 28 Javascript
超漂亮的jQuery图片轮播特效
Nov 24 Javascript
解决JS无法调用Controller问题的方法
Dec 31 Javascript
详细讲解JavaScript中的this绑定
Oct 10 Javascript
vue-resourse将json数据输出实例
Mar 08 Javascript
Three.js利用Detector.js插件如何实现兼容性检测详解
Sep 26 Javascript
element-ui如何防止重复提交的方法步骤
Dec 09 Javascript
vue图片裁剪插件vue-cropper使用方法详解
Dec 16 Vue.js
详解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中与数组相关的函数
2007/03/22 PHP
关于PHP递归算法和应用方法介绍
2013/04/15 PHP
PHP 7的一些引人注目的新特性简单介绍
2015/11/08 PHP
简单谈谈PHP中的include、include_once、require以及require_once语句
2016/04/23 PHP
常用的javascript function代码
2008/05/23 Javascript
关于jQuery $.isNumeric vs. $.isNaN vs. isNaN
2013/04/15 Javascript
关于JavaScript中name的意义冲突示例介绍
2014/05/29 Javascript
JavaScript入门教程之引用类型
2016/05/04 Javascript
js简单实现图片延迟加载的方法
2016/07/19 Javascript
Node.js如何自动审核团队的代码
2016/07/20 Javascript
jquery实现界面无刷新加载登陆注册
2016/07/30 Javascript
针对后台列表table拖拽比较实用的jquery拖动排序
2016/10/10 Javascript
jQuery接受后台传递的List的实例详解
2017/08/02 jQuery
vue-cli配置文件——config篇
2018/01/04 Javascript
vue项目部署到Apache服务器中遇到的问题解决
2018/08/24 Javascript
监控微信小程序中的慢HTTP请求过程详解
2019/07/05 Javascript
使用vue-router切换页面时实现设置过渡动画
2019/10/31 Javascript
vue-quill-editor插入图片路径太长问题解决方法
2021/01/08 Vue.js
[42:36]DOTA2上海特级锦标赛B组败者赛 VG VS Spirit第二局
2016/02/26 DOTA
python3爬取淘宝信息代码分析
2018/02/10 Python
浅谈python函数调用返回两个或多个变量的方法
2019/01/23 Python
应用OpenCV和Python进行SIFT算法的实现详解
2019/08/21 Python
用Python画小女孩放风筝的示例
2019/11/23 Python
在Sublime Editor中配置Python环境的详细教程
2020/05/03 Python
从0到1使用python开发一个半自动答题小程序的实现
2020/05/12 Python
基于python SMTP实现自动发送邮件教程解析
2020/06/02 Python
Scrapy框架介绍之Puppeteer渲染的使用
2020/06/19 Python
详解Python中第三方库Faker
2020/09/25 Python
浅谈html5 响应式布局
2014/12/24 HTML / CSS
电钳专业个人求职信
2014/01/04 职场文书
大学四年个人的自我评价
2014/02/26 职场文书
2014班子成员自我剖析材料思想汇报
2014/10/01 职场文书
卖车协议书范本4篇
2014/10/01 职场文书
2014年检验员工作总结
2014/11/19 职场文书
民事纠纷协议书
2016/03/23 职场文书
PHP基本语法
2021/03/31 PHP