demo
https://github.com/flzCoder/rich
一、优劣分析
优点:
更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面
更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备
权衡点:
开发条件所限(浏览器特定的代码)
涉及构建设置和部署的更多要求(server端)
更多的服务器端负载(每一个请求生成一个vue实例;在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源 )
nuxt.js
如果你倾向于使用提供了平滑开箱即用体验的更高层次解决方案,你应该去尝试使用 Nuxt.js,如果你需要更直接地控制应用程序的结构,Nuxt.js 并不适合这种使用场景。
二、原理分析:
1.核心方法
const { createBundleRenderer } = require('vue-server-renderer')
2.参数:
服务器 bundle: vue-ssr-server-bundle.json
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
客户端构建清单: vue-ssr-client-manifest.json
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
模板index.template.html
3.集成到服务器渲染
renderer 现在具有了服务器和客户端的构建信息,因此它可以自动推断和注入资源预加载 / 数据预取指令(preload / prefetch directive),以及 css 链接 / script 标签到所渲染的 HTML。
app.get('*', (req,res) {
renderer.renderToString(context, (err, html) => {
res.send(html)
})
})
4.简单同构应用已实现
虚拟DOM让服务端渲染vue应用成为可能;
具体实现依赖 vue-server-renderer包的方法和插件,将vue实例渲染为html;
Vue 在浏览器端接管由服务端发送的静态 HTML,使其变为由 Vue 管理的动态 DOM 状态。
三、构建环境搭建:
0.目录结构
1.node应用程序
express
2.简易模板
3.简易vue应用程序
为每个请求创建一个新的根 Vue 实例,避免状态单例;暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例:
vue应用程序通用entry:app.js
4.构建入口文件
entry-client.js
const { app } = createApp()
app.$mount('#app')
entry-server.js
5.构建配置
webpack.client.config.js
webpack.server.config.js
target: 'node',
output.libraryTarget = 'commonjs2';
6.本地开发&线上打包
线上打包
createBundleRenderer 直接调用打包后的文件即可
本地开发
index.html
chokidar监听文件变化,将最新的模板传给createBundleRenderer
vue-ssr-client-manifest.json
使用express中间件webpack-dev-middleware && webpack-hot-middleware/client,在每次打包结束时将最新的vue-ssr-client-manifest.json传给createBundleRenderer
vue-ssr-server-bundle.json
watch监听到文件变化,webpack重新打包,新文件vue-ssr-server-bundle.json传给createBundleRenderer
四、路由相关处理
1.服务器代码使用了一个 *
处理程序,它接受任意 URL。这允许我们将访问的 URL 传递到我们的 Vue 应用程序中,然后对客户端和服务器复用相同的路由配置!
2.路由配置router.js
每个请求一个新的 router 实例,所以文件导出一个 createRouter
函数
应用程序的代码分割或惰性加载,有助于减少浏览器在初始渲染中下载的资源体积,可以极大地改善大体积 bundle 的可交互时间(TTI - time-to-interactive)。
3.路由逻辑
entry-server.js
entry-client.js
挂载 app 之前调用 router.onReady
,因为路由器必须要提前解析路由配置中的异步组件,才能正确地调用组件中可能存在的路由钩子。
五、数据预取及状态
在服务器端渲染(SSR)期间,我们本质上是在渲染我们应用程序的"快照",所以如果应用程序依赖于一些异步数据,那么在开始渲染过程之前,需要先预取和解析好这些数据。
在客户端,在挂载 (mount) 到客户端应用程序之前,需要获取到与服务器端应用程序完全相同的数据 - 否则,客户端应用程序会因为使用与服务器端应用程序不同的状态,然后导致混合失败。
1.数据预取存储容器 (vuex)
配置vuex state、action、mutation
action:获取异步数据
2.路由组件所需数据:通过访问路由,决定了哪些组件需要渲染。约定在该组件放置数据预取逻辑获取所需数据。
路由组件提供asyncData静态方法,dispatch对应action,获取该路由所需数据存入state.items;
各路由以路由名字区分 route.name
3.服务端数据预取 entry-server.js
通过路由获得与 router.getMatchedComponents()
相匹配的组件,如果组件暴露出 asyncData
,我们就调用这个方法。
然后我们需要将解析完成的状态,附加到渲染上下文
当使用 template
时,context.state
将作为 window.__INITIAL_STATE__
状态,自动嵌入到最终的 HTML 中。而在客户端,在挂载到应用程序之前,store 就应该获取到状态:
4.客户端数据预取 entry-client.js
在路由导航之前解析数据
使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。
对比得出
非预渲染的组件,找出两个匹配列表的差异组件然后预取数据。
当路由组件重用(同一路由,但是 params 或 query 已更改,例如,从 user/1
到 user/2
)时,也应该调用 asyncData
函数。我们也可以通过纯客户端 (client-only) 的全局 mixin 来处理这个问题: