为了更好的 SEO 排名 和 性能优化 等要求,往往需要 服务端渲染 的支持。我的公司项目都是基于 vue 的单页面应用(那时候太年轻),那怎么实现服务端渲染呢?
社区早已有了解决方案,只是我还没有用过罢了,借这个机会快速的看下这块的实践,做些技术储备。
vue-ssr api 的介绍
有关 vue ssr 的实现都是依靠 vue-server-renderer 模块提供的功能,可以看如下 Demo:
下面会对 简单的渲染 和 结合 bundle&manifest 文件做的渲染 做说明。
当然你可以看 Vue SSR 官方文档说明,真的是非常详细。
简单渲染
这是个非常简单的例子,主要在没有其他代码的干扰下更可能示范 vue ssr 的本质。只需要 vue + htmlTemplate 就能搞定。
createRenderer
创建一个(模板)渲染器:
1 | const { createRenderer } = require("vue-server-renderer"); |
这个 templateFile 是字符串,而不是 require 的引用。这也好理解,毕竟服务端渲染的本质就是响应返回字符串。
创建一个 Vue 实例
1 | let app = new Vue({ |
renderToString
通过渲染器 renderer ,调用 renderToString ,把 vue 实例注入进去,得到字符串。
当然还可以通过 content 设置一些信息,比如 SEO 的 TDK。
1 | let context = { |
最后你就能访问 url 看到页面内容(注意这并不是客户端 js mount 上去的)
JsonBundle + Manifest
当然真实的项目还更复杂些:
- Html 引用的资源需要“动态”的挂到标签上
- 服务端 router 每次解析不同地址,还需要“配合” vue router ,做对应的模板渲染
- 甚至还需要保持 vuex 的状态;异步请求的处理
详细的项目配置,你可以在上面的 Demo 中看到 看到,下面只是说核心部分。
webpack build
首先我们需要通过 webpack 不同的入口文件 entry 来构建 客户端的 manifest 文件 和 服务端的 server.bundle 文件。
这里就涉及 webpack 的两个 plugin:vue-server-renderer/client-plugin、vue-server-renderer/server-plugin ,分别生成 manifest 和 bundle:
1 | // webpack.client.config |
构建成功就会在 output 目录下生成:vue-ssr-client-manifest.json、vue-ssr-server-bundle.json 。
通过 manifest ,vue 就知道如何往模板上挂在资源文件;同时每次渲染时,vue 也能依靠 bundle 知道取那部分 js 逻辑。
修改服务端路由逻辑
拦截服务端所有的请求,交给 getResponseDataByBundleRender 处理,context 内包含当前请求的 url :
1 | router.get("*", async (ctx, next) => { |
getResponseDataByBundleRender 是一个封装的方法,和上面的简单渲染实现一样,只是额外需要 bundle、manifest 文件:
1 | getResponseDataByBundleRender(content) { |
客户端和服务端功能的衔接
那客户端的请求地址怎么和 vue 的路由模式搭上关系?这就要回到上面的 webpack 配置不同的入口文件:
独立创建一个创建 vue 实例的文件,用于对不同端的 entry 入口文件解耦:
1 | // src\app.js |
server.entry.js 会将 store 注入到 window.__INITIAL_STATE__ 中,并且会根据请求地址,切换 vue 当前路由地址:
1 | export default context => { |
client.entry.js 会加入如下逻辑,使客户端能将 window.__INITIAL_STATE__ 替换成 vue store :
1 | // src\entry-client.js |
这样 vue 的状态机制就得以还原。
之后你就能看到这样的效果:
koa + vue-ssr 的开发模式
在实际开发中,上面这些还远远不够。因为 manifest 和 bundle 的更新都需要重新加载到服务中;为了开发体验还需要 HMR 的开发方式。
Vue 的作者 尤大大 给我们了个例子 vue-hackernews-2.0,参考它,就能很大程度解决上面的问题。
同样对其中的重点做个说明,备注:服务端用的 koa :
一个延迟启动
因为要把 webpack 构建包含到服务中,就要在服务启动前完成构建工作。
提供一个 ready 完成这项工作:
1 | ready(app).then(renderer => { |
在 ready 中完成 vue ssr 需要的 bundle.json 和 manifest.json 构建:
1 | ready(app, cb) { |
拿到 clientManifest 和 bundle 再交给 update() 获取渲染解析器,就回归到 vue ssr api 那部分的运用:
1 | const update = () => { |
准备做完后,就根据 request 地址生成对应的 html 字符串。这些都是在服务启动前搞定了:
1 | ready(app).then(renderer => { |
开启 HMR
首先模拟 webpack-dev-server (内存)静态文件服务,这样每次来次客户端的请求都会被 devMiddleware 拦截,根据请求响应对应的资源文件,包括热更新资源:
1 | let webpackDevMiddlewareWrap = webpackDevMiddleware(clientCompiler, { |
再是创建通过 clientHotMiddleware 创建 socket 连接,时刻向服务端推送信息,告知本地代码更新状态:
1 | app.use(clientHotMiddleware(clientCompiler, { heartbeat: 5000 })); |
这样一个 koa + vue 的 ssr 开发架构就初步成型。
nuxt
2016 年 10 月 25 日,zeit.co 背后的团队对外发布了 Next.js,一个 React 的服务端渲染应用框架。几小时后,与 Next.js 异曲同工,一个基于 Vue.js 的服务端渲染应用框架应运而生,我们称之为:Nuxt.js。
主流 vue ssr 框架了,github 高 star 为使用它提供了足够的保障。
但我展示不打算使用它,因为上面的开发方式以及满足我的基本需求,过多的框架封装会让我迷失技术的原有“味道”,等有时间时再来填这里的坑。
参考
我只是知识点的“加工者”, 更多内容请查阅原文链接 :thought_balloon: , 同时感谢原作者的付出: