0%

概述Vue SSR工作原理以及工程实践

  • 工作原理
    • SSR优劣势
    • 详细构建过程
    • 运行流程概述
  • 实践
    • 工程目录结构
    • 同构中的差异
    • 踩坑

工作原理

SSR优劣势

  • 优势
    • 首屏渲染加快(相当于省去client端fetch + load bundle并且初始化vue的时间)
    • 支持SEO

构建过程

  • 入口文件二:entry-server。需要依次作如下逻辑处理
    • 新建vue、vue-router、vuex实例(注意不能复用实例)
    • app挂载router和store插件
    • router根据当前请求即req.url,根据路由匹配到Components,如果存在asyncData则异步获取所有数据后resolve app实例
    • registerModule并存在state时,将state挂载到context.state下,并由renderer将其序列化
    • renderer可以在HTML中收集并内联组件css,并自动注入首屏关键css(critical css)
    • renderer通过app实例render为HTML string,并返回给client端
  • 入口文件一:entry-client。需要依次作如下逻辑处理
    • 同样的,初始化app实例并挂载router,store插件
    • 当路由onReady后,挂载<div id="app" data-server-rendered="true">...</div>真实DOM,由于此时通过data-server-rendered已知晓由服务端渲染完毕,因此客户端走激活(hydrate)流程而不是重新渲染替换节点#app
    • 另外数据预取逻辑和服务端略有不同,我用的是按需加载策略也就是通过mixin混入beforeMount生命周期去预取数据。为了防止double-fetch数据,这里需要作出调整即在挂载app之后再混入。
  • 然后webpack通过不同入口分别打包client-manifest.json以及server-bundle.json,应用于renderer
  • 其中client-manifest主要用于标记程序依赖chunk,server-bundle中则存储并映射打包后所有的bundle用于渲染。renderer具有程序的所有构建信息,因此它可以自动推断和注入资源prefetch与preload指令,以及css、javascript标签到HTML中。与此同时,为避免client瀑布式请求(waterfull request)以及达到最快的TTI(Time-To Interactive),只有应用程序首屏依赖的chunk会以preload形式注入,暂未使用的异步chunk通过prefetch形式注入。

运行流程概述

  • server端负责渲染首屏的工作比如预取数据,全局state初始化(不包括actions,mutations以及getters),直出首屏完整DOM
  • client端负责hydrate(激活)SPA,比如store replaceState,注册store模块

实践

工程依赖的核心库版本概览

"vue": "^2.5.16",
"vue-router": "^3.0.1",
"vue-server-renderer": "^2.5.16",
"vuex": "^3.0.1"
"vue-loader": "^15.2.4",
"vue-style-loader": "^4.1.0",

工程目录结构

请移步我的github查看(欢迎star)

可执行命令

  • npm run dev
  • npm run serve
  • npm run serve:watch
  • npm run build:client-standalone
  • npm run build:client-analyze

同构中的差异

  • server端和client端渲染不同页面时在各自的生命周期中(server是beforeCreated and created,client更关注mounted and more life cycles)可能需要处理相同的事情,比如网页title和通用头部管理等

用全局Mixin或者Class继承来做。个人更倾向于全局Mixin,因为开发时少写冗余代码,但因此损失一点运行时性能比如内存和计算力

踩坑

  • TypeError: Cannot read property ‘render’ of undefined

最终发现在webpack.base.conf.js中output.filename后面带了hash导致可能在生成server-bundle.json的时候匹配不到该chunk导致json文件构建失败,从而导致vm为undefined,最终抛出以上错误信息。解决方案是把该配置项放在webpack.client.conf.js与webpack.server.conf.js中分别配置

  • loader执行顺序从后到前

也就是说不管是字符串style-loader!css-loader!stylus-loader还是数组形式['style-loader', 'css-loader', 'stylus-loader'],执行顺序都是从stylus-loader -> css-loader -> style-loader

  • style-loader中依赖window,导致ssr失败

用vue-style-loader代替

  • 服务端渲染开发时做hmr有坑

  • 当在ssr工程中引入standalone时需要做一些处理比如

如果是standalone模式则挂载前mixin预取数据的生命周期钩子用于client fetch asyncData,如果是ssr-client模式则采用挂载后再mixin此钩子的策略,为防止client hydrating出现double-fetch的情况