自定义vue SSR
项目源码地址 : https://github.com/qifutian/learngit/tree/main/vue-ssr
搭建自己的SSR
- mkdir vue-ssr
- cd vue-ssr
- npm init -y
- npm i vue vue-server-renderer
- 创建server.js
- 使用node运行
- 服务端会将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服务中
目的:是将渲染结果发送给客户端浏览器
- 安装web服务端,之前代码
- npm i express
- 修改文件,通过nodemon运行
- 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中需要客户端处理的功能未提供,就是对应的事件或者绑定未工作
构建思路
需求
- 将生成的字符串提供浏览器解析
- 拥有客户端渲染,客户端的动态交互
- 组织对应的代码结构
- 由webpack对源代码进行打包构建
- 使用打包构建的结构通过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
- 建立src文件夹,创建App.vue
- 创建app.js,目的是同构应用的通用启动入口
- 创建 entry-client.js,目的是创建应用程序,挂载到dom中
- 创建 entry-server.js,目的是服务端的入口, 使用 default export 导出函数,并在每次渲染中重复调用此函数
安装依赖
- npm i vue vue-server-renderer express cross-env
- 安装 开发依赖 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配置文件
- 初始化webpack 打包配置文件,创建build文件夹及对应的打包文件
- webpack.base.config.js是公共配置
- webpack.client.config.js 是客户端打包配置
- 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处理静态资源,
浏览器的事件正常处理
构建配置-解析渲染流程
问题:
- 服务端如何输出内容的流程
- 客户端如何加载的流程
服务端流程
- 服务端从server.js开始
- 从server.get的路由开始
- 调用renderer.renderToString方法,将一个vue实例渲染成字符串返回客户端
- 渲染的vue实例通过serverBundle加装
客户端加载
- 服务端返回打包之后的字符串,会包含对应的引入资源
- 通过vue-ser-renderer的createBundleRenderer
- 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 // 关闭它本身的日志输出}))
编写通用应用的注意项
- 服务器上的数据响应
- 组件生命周期钩子函数
- 访问特定平台的api,例如window和document,服务端可能没有,客户端同样没有对应的node中的fs等
- 自定义组件,推荐使用抽象机制,运行在虚拟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相关推荐
- 【服务端渲染】之 Vue SSR
前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:内容较多,建议通过左侧导航栏进行阅读 Vue SSR 基本介绍 Vue SSR 是什么 官方文档:https://ssr.vuejs.org/ V ...
- Vue SSR 性能优化实践
齐云雷,微医云服务团队前端工程师,本文是作者在<第二届缤纷前端技术沙龙>分享主题的文字版. 估计大部分读者对标题中的性能优化更感兴趣,可惜我分享的重点其实更多在于实践.实践有深有浅,下面介 ...
- 骨架屏 之 Vue SSR(快捷简易版本解决方案)
一. 骨架屏简介 简单来说, 骨架屏就是填充了背景等特效的真实页面手稿轮廓图. 它可以是精确/粗略的描述了页面各个元素大小,形状,位置占位的一种页面真实数据渲染加载前的排版. 目的是加载页面过程中给用 ...
- Vue SSR 渲染 Nuxt3 入门学习
Vue SSR 渲染 Nuxt3 入门学习 SPA应用:也就是单页应用,这些多是在客户端的应用,不利于进行SEO优化(搜索引擎优化). SSR应用:在服务端进行渲染,渲染完成后返回给客户端,每个页面有 ...
- vue SSR 部署详解
先用vue cli初始化一个项目吧. 输入命令行开始创建项目: vue create my-vue-ssr 记得不要选PWA,不知为何加了这个玩意儿就报错. 后续选router模式记得选 histor ...
- 关于Vue ssr的一点探讨
这很难,里面只是我以比较明显的一个问题,引发对整个ssr的研究.但是我又复习了下Vuex,发现了异步问题.过两天把router也复习了.那异步问题应该就解决了,到时候再出篇稿子.这篇,你可能看不懂,因 ...
- 了解 Vue SSR 这一篇足以
文章目录 1 - 什么是服务器端渲染? 1.1 新建server文件夹 1.2 生成一个node项目 1.3 安装express 1.4 服务端渲染小案例 1.5 运行查看效果 1.6 打开浏览器 1 ...
- [vue] SSR解决了什么问题?有做过SSR吗?你是怎么做的?
[vue] SSR解决了什么问题?有做过SSR吗?你是怎么做的? SSR server side render服务端渲染,解决spa应用缺点的首屏加载速度慢.不利于SEO问题 个人简介 我是歌谣,欢迎 ...
- Vue SSR(Vue2 + Koa2 + Webpack4)配置指南
正如Vue官方所说,SSR配置适合已经熟悉 Vue, webpack 和 Node.js 开发的开发者阅读.请先移步 ssr.vuejs.org 了解手工进行SSR配置的基本内容. 从头搭建一个服务端 ...
最新文章
- Spring Boot 注册 Servlet 的三种方法,真是太有用了!
- continue语句只用于循环语句中_循环里continue,break,return的作用,你知道吗?
- 皮一皮:如此父母...究竟是好还是不好(沉思)...
- 如何用杠铃策略,构建你的“反脆弱性”
- 【机器学习】分类算法-K-近邻算法
- android p wifi一直在扫描_Android再次解读萤石云视频
- c语言中 %.2s,C2S是什么意思
- VMware安装CentOS之二——最小化安装CentOS
- CVPR 9999 Best Paper——《一种加辣椒的番茄炒蛋》
- java输入输出高速
- 尝试使用Java6API读取java代码
- 乔布斯在斯丹佛毕业典礼上的讲话(二)
- fpga从入门到放弃(一)基于vivado2018环境开发板Artix 7系列BASYS3(更新中)
- postfix+dovecot
- Unix Vi命令基本用法
- NYOJ 819奶牛 水
- 【Shiro第二篇】SpringBoot + Shiro实现用户身份认证功能
- java 高效的 httpclient_使用httpclient下载zip的有效方法
- 三菱plcascll转换16进制_三菱ASCII码指令
- 电脑重装系统后当前安全设置不允许下载该文件