项目源码地址 : https://github.com/qifutian/learngit/tree/main/vue-ssr

搭建自己的SSR

  1. mkdir vue-ssr
  2. cd vue-ssr
  3. npm init -y
  4. npm i vue vue-server-renderer
  5. 创建server.js
  6. 使用node运行
  7. 服务端会将vue渲染成字符串

server.js

const Vue = require('vue')
const renderer = require('vue-server-renderer')const app = new({template:`<div id="app"><h1>{{message}}</h1></div>`,data:{message: "ssr text"}
})renderer.renderToString(app,(err,heml)=>{if(err) throw errconsole.log(html)
})

结合到Web服务中

目的:是将渲染结果发送给客户端浏览器

  1. 安装web服务端,之前代码
  2. npm i express
  3. 修改文件,通过nodemon运行
  4. nodemon server.js
const Vue = require('vue')
const renderer = require('vue-server-renderer').createRenderer()
const express = require('express')
const server = express()server.get('/',(req,res)=>{const app = new Vue({template:`<div id="app"><h1>{{message}}</h1></div>`,data:{message: "ssr text"}})renderer.renderToString(app,(err,html)=>{if(err){ return res.status(500).end("Server ERROR")}res.setHeader('Content-Type','text/html;charset=utf8')// res.end(html)res.end(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body>${html}</body></html>`)})
})server.listen(3000,()=>{console.log('server running at port 3000');
})

使用html模板

页面的模板可以存放到一个单独的文件中,对他进行管理和维护
创建index.template.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
</head><body><!--vue-ssr-outlet-->
</body></html>

修改server.js

 const Vue = require('vue')
const fs = require('fs')const renderer = require('vue-server-renderer').createRenderer({template: fs.readFileSync('./index.template.html','utf-8')
})
const express = require('express')
const server = express()server.get('/',(req,res)=>{const app = new Vue({template:`<div id="app"><h1>{{message}}</h1></div>`,data:{message: "ssr text"}})renderer.renderToString(app,(err, html)=>{if(err){ return res.status(500).end("Server ERROR")}res.setHeader('Content-Type','text/html;charset=utf8')res.end(html)})
})server.listen(3000,()=>{console.log('server running at port 3000');
})

在模板中使用外部数据

修改renderToString方法

renderer.renderToString(app,{title:"ssr",meta: `<meta name="description" content="ssr">`},(err, html)=>{if(err){ return res.status(500).end("Server ERROR")}res.setHeader('Content-Type','text/html;charset=utf8')res.end(html)})

Vue SSR 构建配置

基本思路

服务端渲染只是将其处理为纯静态的字符串,将其发送给客户端,对与vue中需要客户端处理的功能未提供,就是对应的事件或者绑定未工作

构建思路

需求

  1. 将生成的字符串提供浏览器解析
  2. 拥有客户端渲染,客户端的动态交互
  3. 组织对应的代码结构
  4. 由webpack对源代码进行打包构建
  5. 使用打包构建的结构通过node启动起来

建议使用官网推荐的结构
https://ssr.vuejs.org/zh/guide/structure.html#%E4%BD%BF%E7%94%A8-webpack-%E7%9A%84%E6%BA%90%E7%A0%81%E7%BB%93%E6%9E%84

  1. 建立src文件夹,创建App.vue
  2. 创建app.js,目的是同构应用的通用启动入口
  3. 创建 entry-client.js,目的是创建应用程序,挂载到dom中
  4. 创建 entry-server.js,目的是服务端的入口, 使用 default export 导出函数,并在每次渲染中重复调用此函数

安装依赖

  1. npm i vue vue-server-renderer express cross-env
  2. 安装 开发依赖 npm i -D webpack webpack-cli webpack-merge webpack-node-externals @babel/core @babel/plugin-transform-runtime @babel/preset-env babel-loader css-loader url- loader file-loader rimraf vue-loader vue-template-compiler friendly-errors- webpack-plugin

cross-env 作用 :通过npm scripts设置扩平台环境变量 webpack-cli: webpack 的命令行工具
webpack-merge: webpack 配置信息合并工具 webpack-node-externals: 排除 webpack 中的
Node 模块 rimraf: 基于 Node 封装的一个跨平台 rm -rf 工具
friendly-errors-webpack-plugin: 友好的 webpack 错误提示 Babel 相关工具 vue-loader
vue-template-compiler 处理
.vue 资源 file-loader 处理字体资源
css-loader 处理 CSS资源
url-loader 处理图片资源

构建配置-webpack配置文件

  1. 初始化webpack 打包配置文件,创建build文件夹及对应的打包文件
  2. webpack.base.config.js是公共配置
  3. webpack.client.config.js 是客户端打包配置
  4. webpack.server.config.js 是服务端打包配置

构建配置-配置构建的命令

运行:

  • npm run build:client
  • npm run build:server

可根据package.json进行配置

{"name": "vue-ssr","version": "1.0.0","description": "","main": "index.js","scripts": {"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js","build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js","build": "rimraf dist && npm run build:client && npm run build:server",},"dependencies": {"axios": "^0.19.2","chokidar": "^3.4.0","cross-env": "^7.0.2","express": "^4.17.1","vue": "^2.6.11","vue-meta": "^2.4.0","vue-router": "^3.3.4","vue-server-renderer": "^2.6.11","vuex": "^3.5.1"},"devDependencies": {"@babel/core": "^7.10.4","@babel/plugin-transform-runtime": "^7.10.4","@babel/preset-env": "^7.10.4","babel-loader": "^8.1.0","css-loader": "^3.6.0","file-loader": "^6.0.0","friendly-errors-webpack-plugin": "^1.7.0","rimraf": "^3.0.2","url-loader": "^4.1.0","vue-loader": "^15.9.3","vue-template-compiler": "^2.6.11","webpack": "^4.43.0","webpack-cli": "^3.3.12","webpack-dev-middleware": "^3.7.2","webpack-hot-middleware": "^2.25.0","webpack-merge": "^5.0.9","webpack-node-externals": "^2.5.0"}
}

构建配置-启动应用

先进行打包工作,在修改server.js

const Vue = require('vue')
const fs = require('fs')const serverBundle = require("./dist/vue-ssr-server-bundle.json")
const clientManifest = require("./dist/vue-ssr-client-manifest.json") // 打包需要的资源清单const template = fs.readFileSync('./index.template.html','utf-8')
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{template,clientManifest
})
const express = require('express')
const server = express()server.get('/',(req,res)=>{// 会找到entry-server.js 找到对应的渲染实例renderer.renderToString({title:"ssr",meta: `<meta name="description" content="ssr">`},(err, html)=>{if(err){ return res.status(500).end("Server ERROR")}res.setHeader('Content-Type','text/html;charset=utf8')res.end(html)})
})server.listen(3000,()=>{console.log('server running at port 3000');
})

使用 node server 运行,浏览器会报错
可以通过node.static处理静态资源,
浏览器的事件正常处理

构建配置-解析渲染流程

问题:

  1. 服务端如何输出内容的流程
  2. 客户端如何加载的流程

服务端流程

  1. 服务端从server.js开始
  2. 从server.get的路由开始
  3. 调用renderer.renderToString方法,将一个vue实例渲染成字符串返回客户端
  4. 渲染的vue实例通过serverBundle加装

客户端加载

  1. 服务端返回打包之后的字符串,会包含对应的引入资源
  2. 通过vue-ser-renderer的createBundleRenderer
  3. vuessrclient-mainfest.json中存放的是对应文件,all是加载的资源,initinitial是将需要资源注入到页面中,async是处理异步资源,modules是原始模块的资源信息

构建开发模式

解决打包问题,通过server启动web服务

实现自动构建,自动重启,自动刷新浏览器等

解决server.js中的renderer是关键内容
开发模式下需要更新,需要根据源代码改变修改打包之后的资源文件
在package.json中增加对应的命令

"scripts": {"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js","build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js","build": "rimraf dist && npm run build:client && npm run build:server","start": "cross-env NODE_ENV=production node server.js","dev": "node server.js"},

在server.js中拿到环境变量执行server.js,判断是生产模式还是开发模式

const Vue = require('vue')
const fs = require('fs')
const express = require('express')let renderer// 根据环境变量执行对应代码
const isProd = process.env.NODE_ENV === "production"if(isProd){const serverBundle = require("./dist/vue-ssr-server-bundle.json")const clientManifest = require("./dist/vue-ssr-client-manifest.json") // 打包需要的资源清单const template = fs.readFileSync('./index.template.html','utf-8')renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{template,clientManifest})
} else {// 开发模式 -> 监视源代码改变,进行打包构建 -> 重新生成 Renderer渲染器}const server = express()server.use('/dist',express.static('./dist'))const render = (req,res)=>{// 会找到entry-server.js 找到对应的渲染实例renderer.renderToString({title:"ssr",meta: `<meta name="description" content="ssr">`},(err, html)=>{if(err){ return res.status(500).end("Server ERROR")}res.setHeader('Content-Type','text/html;charset=utf8')res.end(html)})
}
server.get('/', isProd ? render : (req, res) => {// 等待有了 Renderer 渲染器以后,调用render 进行渲染render()
})server.listen(3000,()=>{console.log('server running at port 3000');
})

提取处理模块

server.js中引入 增加setup-setup-dev-server.js

const fs = require('fs')
const path = require('path')
const chokidar = require('chokidar')
const webpack = require('webpack')
const devMiddleware = require('webpack-dev-middleware')
const hotMiddleware = require('webpack-hot-middleware')const resolve = file => path.resolve(__dirname, file)module.exports = (server, callback) => {let readyconst onReady = new Promise(r => ready = r)// 监视构建 -> 更新 Rendererlet templatelet serverBundlelet clientManifestconst update = () => {if (template && serverBundle && clientManifest) {ready()callback(serverBundle, template, clientManifest)}}// 监视构建 template -> 调用 update -> 更新 Renderer 渲染器const templatePath = path.resolve(__dirname, '../index.template.html')template = fs.readFileSync(templatePath, 'utf-8')update()// fs.watch、fs.watchFilechokidar.watch(templatePath).on('change', () => {template = fs.readFileSync(templatePath, 'utf-8')update()})// 监视构建 serverBundle -> 调用 update -> 更新 Renderer 渲染器const serverConfig = require('./webpack.server.config')const serverCompiler = webpack(serverConfig)const serverDevMiddleware = devMiddleware(serverCompiler, {logLevel: 'silent' // 关闭日志输出,由 FriendlyErrorsWebpackPlugin 处理})serverCompiler.hooks.done.tap('server', () => {serverBundle = JSON.parse(serverDevMiddleware.fileSystem.readFileSync(resolve('../dist/vue-ssr-server-bundle.json'), 'utf-8'))update()})// 监视构建 clientManifest -> 调用 update -> 更新 Renderer 渲染器const clientConfig = require('./webpack.client.config')clientConfig.plugins.push(new webpack.HotModuleReplacementPlugin())clientConfig.entry.app = ['webpack-hot-middleware/client?quiet=true&reload=true', // 和服务端交互处理热更新一个客户端脚本clientConfig.entry.app]clientConfig.output.filename = '[name].js' // 热更新模式下确保一致的 hashconst clientCompiler = webpack(clientConfig)const clientDevMiddleware = devMiddleware(clientCompiler, {publicPath: clientConfig.output.publicPath,logLevel: 'silent' // 关闭日志输出,由 FriendlyErrorsWebpackPlugin 处理})clientCompiler.hooks.done.tap('client', () => {clientManifest = JSON.parse(clientDevMiddleware.fileSystem.readFileSync(resolve('../dist/vue-ssr-client-manifest.json'), 'utf-8'))update()})server.use(hotMiddleware(clientCompiler, {log: false // 关闭它本身的日志输出}))// 重要!!!将 clientDevMiddleware 挂载到 Express 服务中,提供对其内部内存中数据的访问server.use(clientDevMiddleware)return onReady
}

使用 chokidar 第三方库加载监视的变化
是封装了 fs.watch 和fs.watchFile,更加方便使用

chokidar.watch(templatePath).on('change', () => {在此处科员监听对应的文件变化})

服务端监听打包
通过webpack 创建对应的编译器
重新构建,需要先删除之前生成的,创建新的,放到内存中

热更新
使用 webpack-hot-middleware
在打包之后自动刷新浏览器
使用: npm i --save-dev webpack-hot-middleware
在plugins中引入

 const clientConfig = require('./webpack.client.config')clientConfig.plugins.push(new webpack.HotModuleReplacementPlugin())clientConfig.entry.app = ['webpack-hot-middleware/client?quiet=true&reload=true', // 和服务端交互处理热更新一个客户端脚本clientConfig.entry.app]clientConfig.output.filename = '[name].js' // 热更新模式下确保一致的 hashconst clientCompiler = webpack(clientConfig)const clientDevMiddleware = devMiddleware(clientCompiler, {publicPath: clientConfig.output.publicPath,logLevel: 'silent' // 关闭日志输出,由 FriendlyErrorsWebpackPlugin 处理})clientCompiler.hooks.done.tap('client', () => {clientManifest = JSON.parse(clientDevMiddleware.fileSystem.readFileSync(resolve('../dist/vue-ssr-client-manifest.json'), 'utf-8'))update()})server.use(hotMiddleware(clientCompiler, {log: false // 关闭它本身的日志输出}))

编写通用应用的注意项

  1. 服务器上的数据响应
  2. 组件生命周期钩子函数
  3. 访问特定平台的api,例如window和document,服务端可能没有,客户端同样没有对应的node中的fs等
  4. 自定义组件,推荐使用抽象机制,运行在虚拟DOM级别

配置 VueRouter

使用vue-router使用方式和 客户端使用方法基本相同
用法
新建router文件夹,新建index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/pages/Home'Vue.use(VueRouter)export const createRouter = () => {const router = new VueRouter({mode: 'history', // 兼容前后端routes: [{path: '/',name: 'home',component: Home},{path: '/about',name: 'about',component: () => import('@/pages/About')},{path: '/posts',name: 'post-list',component: () => import('@/pages/Posts')},{path: '*',name: 'error404',component: () => import('@/pages/404')}]})return router
}

适配服务端入口

需要在entry-server.js中实现服务端逻辑 官网实现对应代码

// entry-server.js
import { createApp } from './app'export default async context => {// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,// 以便服务器能够等待所有的内容在渲染前,// 就已经准备就绪。const { app, router, store } = createApp()const meta = app.$meta()// 设置服务器端 router 的位置router.push(context.url)context.meta = meta// 等到 router 将可能的异步组件和钩子函数解析完await new Promise(router.onReady.bind(router))context.rendered = () => {// Renderer 会把 context.state 数据对象内联到页面模板中// 最终发送给客户端的页面中会包含一段脚本:window.__INITIAL_STATE__ = context.state// 客户端就要把页面中的 window.__INITIAL_STATE__ 拿出来填充到客户端 store 容器中context.state = store.state}return app
}

服务端适配
修改server.js中的get监听路由

// 服务端路由设置为 *,意味着所有的路由都会进入这里
server.get('*', isProd? render: async (req, res) => {// 等待有了 Renderer 渲染器以后,调用 render 进行渲染await onReadyrender(req, res)}
)

管理页面的head内容

不同页面定义自己的head部分
Vue ssr指南上有专门的head管理
也可以使用vue-meta插件,使用对应的metaInfo

npm i vue-meta
在对应入口文件注册
Vue.use(VueMeta)

数据预取和状态管理

服务端渲染接口数据
基于vuex创建容器

npm i vuex
创建对应的store文件夹下index.js
use方式引入vuex
vuex中请求成功在将数据保存到客户端,解决数据刷新丢失问题
在entry-server.js中,使用context对象设置rendered将数据填充到客户端
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'Vue.use(Vuex)export const createStore = () => {return new Vuex.Store({state: () => ({posts: []}),mutations: {setPosts (state, data) {state.posts = data}},actions: {// 在服务端渲染期间务必让 action 返回一个 Promiseasync getPosts ({ commit }) {// return new Promise()const { data } = await axios.get('https://cnodejs.org/api/v1/topics')commit('setPosts', data.data)}}})
}
context.rendered = () => {// Renderer 会把 context.state 数据对象内联到页面模板中// 最终发送给客户端的页面中会包含一段脚本:window.__INITIAL_STATE__ = context.state// 客户端就要把页面中的 window.__INITIAL_STATE__ 拿出来填充到客户端 store 容器中context.state = store.state}

自定义vue SSR相关推荐

  1. 【服务端渲染】之 Vue SSR

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:内容较多,建议通过左侧导航栏进行阅读 Vue SSR 基本介绍 Vue SSR 是什么 官方文档:https://ssr.vuejs.org/ V ...

  2. Vue SSR 性能优化实践

    齐云雷,微医云服务团队前端工程师,本文是作者在<第二届缤纷前端技术沙龙>分享主题的文字版. 估计大部分读者对标题中的性能优化更感兴趣,可惜我分享的重点其实更多在于实践.实践有深有浅,下面介 ...

  3. 骨架屏 之 Vue SSR(快捷简易版本解决方案)

    一. 骨架屏简介 简单来说, 骨架屏就是填充了背景等特效的真实页面手稿轮廓图. 它可以是精确/粗略的描述了页面各个元素大小,形状,位置占位的一种页面真实数据渲染加载前的排版. 目的是加载页面过程中给用 ...

  4. Vue SSR 渲染 Nuxt3 入门学习

    Vue SSR 渲染 Nuxt3 入门学习 SPA应用:也就是单页应用,这些多是在客户端的应用,不利于进行SEO优化(搜索引擎优化). SSR应用:在服务端进行渲染,渲染完成后返回给客户端,每个页面有 ...

  5. vue SSR 部署详解

    先用vue cli初始化一个项目吧. 输入命令行开始创建项目: vue create my-vue-ssr 记得不要选PWA,不知为何加了这个玩意儿就报错. 后续选router模式记得选 histor ...

  6. 关于Vue ssr的一点探讨

    这很难,里面只是我以比较明显的一个问题,引发对整个ssr的研究.但是我又复习了下Vuex,发现了异步问题.过两天把router也复习了.那异步问题应该就解决了,到时候再出篇稿子.这篇,你可能看不懂,因 ...

  7. 了解 Vue SSR 这一篇足以

    文章目录 1 - 什么是服务器端渲染? 1.1 新建server文件夹 1.2 生成一个node项目 1.3 安装express 1.4 服务端渲染小案例 1.5 运行查看效果 1.6 打开浏览器 1 ...

  8. [vue] SSR解决了什么问题?有做过SSR吗?你是怎么做的?

    [vue] SSR解决了什么问题?有做过SSR吗?你是怎么做的? SSR server side render服务端渲染,解决spa应用缺点的首屏加载速度慢.不利于SEO问题 个人简介 我是歌谣,欢迎 ...

  9. Vue SSR(Vue2 + Koa2 + Webpack4)配置指南

    正如Vue官方所说,SSR配置适合已经熟悉 Vue, webpack 和 Node.js 开发的开发者阅读.请先移步 ssr.vuejs.org 了解手工进行SSR配置的基本内容. 从头搭建一个服务端 ...

最新文章

  1. Spring Boot 注册 Servlet 的三种方法,真是太有用了!
  2. continue语句只用于循环语句中_循环里continue,break,return的作用,你知道吗?
  3. 皮一皮:如此父母...究竟是好还是不好(沉思)...
  4. 如何用杠铃策略,构建你的“反脆弱性”
  5. 【机器学习】分类算法-K-近邻算法
  6. android p wifi一直在扫描_Android再次解读萤石云视频
  7. c语言中 %.2s,C2S是什么意思
  8. VMware安装CentOS之二——最小化安装CentOS
  9. CVPR 9999 Best Paper——《一种加辣椒的番茄炒蛋》
  10. java输入输出高速
  11. 尝试使用Java6API读取java代码
  12. 乔布斯在斯丹佛毕业典礼上的讲话(二)
  13. fpga从入门到放弃(一)基于vivado2018环境开发板Artix 7系列BASYS3(更新中)
  14. postfix+dovecot
  15. Unix Vi命令基本用法
  16. NYOJ 819奶牛 水
  17. 【Shiro第二篇】SpringBoot + Shiro实现用户身份认证功能
  18. java 高效的 httpclient_使用httpclient下载zip的有效方法
  19. 三菱plcascll转换16进制_三菱ASCII码指令
  20. 电脑重装系统后当前安全设置不允许下载该文件

热门文章

  1. Hadoop运行踩坑: Attempting to operate on hdfs namenode as root
  2. Excel如何统计两列数据有多少重复值
  3. 读《富兰克林自传》的一些体会
  4. OSI七层模型每层作用
  5. 机器视觉贴片机控制软件系统源码 机器视觉贴片机控制系统源码2套(全套源程序和图纸)
  6. 虚拟机无法远程连接阿里云服务器的解决办法
  7. 检查自己建的网站能否被外网访问
  8. 【python】python绘制相关性热力图
  9. 笔记本和利用服务器算力直连,顺网云电脑技术突破:实现利用网吧空余算力
  10. 学Android移动开发 第1章 Android基础入门