Skip to content

Latest commit

 

History

History
98 lines (79 loc) · 3.92 KB

INSIGHTS.md

File metadata and controls

98 lines (79 loc) · 3.92 KB

SSR state context serialize

fork from: https://github.com/nuxt/nuxt.js/blob/dev/packages/utils/src/serialize.js document: https://github.com/yahoo/serialize-javascript

PreFetch

const { createSSRApp, defineComponent } = require('vue')
const { renderToString } = require('@vue/server-renderer')

const asyncChildren = defineComponent({
  name: 'asyncChildren',
  async setup() {
    // some async error...
    // e.g. axios.fetch...
    // throw new Error('async child error!')
    return Promise.reject('async child error!')
  },
  template: `<div class="async-children">{{ test.abc }}</div>`
})

const app = createSSRApp({
  name: 'App',
  components: {
    asyncChildren
  },
  template:
  `
    <div class="app">
      <suspense>
        <async-children />
      </suspense>
    </div>
  `,
  errorCaptured(error) {
    console.log('-----errorCaptured', error)
  }
})

app.config.errorHandler = (error) => {
  console.log('-----errorHandler', error)
}

renderToString(app).then(res => {
  console.log('----renderToString', res)
}).catch(error => {
  console.log('----renderToString error', error)
})

serverPrefetchasync setup 都不能被用作 “抛错即处理” 的预期目的,因为 renderToString 的实现中忽略了 Promise.reject 返回的结果状态,可参见此处源码,所以只能在 SSR 层面自行实现 validate & preFetch

validate 与路由参数耦合,故可以在路由层面进行验证,配置在路由(meta) & 消费在路由(中间件)。 preFetch 由 自定义 hook(客户端)& (手动 get route component)服务端 分别调用。

此方案实现为:

// https://ssr.vuejs.org/guide/data.html#data-store
// 6. do prefetch
// 6. fetch succeed -> renderPage
// 6. fetch failed -> setError -> renderError
const matchedComponents = router.currentRoute.value.matched
  .map(com => com.components?.default)
  .filter(com => (com as any).prefetch)
if (!matchedComponents.length) {
  throw new Error()
}
await Promise.all(matchedComponents.map(
  (component : any) => component.prefetch({
    store,
    route: router.currentRoute
  })
))

serverPrefetch 是 options API, 尽量不要与 setup API 混搭,所以在 composition-api 中的正确实现应该是: 维持之前的 async setup,当 fetch 发生异常时,直接 setError,而不是期待 async setup 的 error 被捕获。 但是:异步请求 catch 产生的错误信息被捕获了,但是 renderToString 的内容并非预期的错误页面; 主要是因为:错误是在 renderToString 的过程中发生的,而 ssrRender 可不是响应式的。 所以需要做一个兜底渲染,即:发现 renderToString 引起新的 error 时,重新 render 一次。

但这个兜底操作的最终目的是:为了将服务端的网络错误传递给客户端,但是由于服务端和移动端的网络状态本来就是不对应的,比如客户端没有请求成功,客户端应该是需要重新请求的,是这样吗?

所以问题就变成了:

  • 期望服务端和客户端对应,对蜘蛛友好,状态也明显,这种情况下,error 也需要在 ssrContext
  • 期望不对应,各自搞各自的,用 http-code 来标示页面的成功与否状态,但是如果服务端输出的是空,客户端是需要重新拉取的,但现有逻辑下服务端不会重新拉取数据

所以最终的做法是:

  • 进入路由之前校验路由的 meta.validate 若不通过,则会在此处就 setError, 目标页面渲染为空数据,app 渲染为 errorPage
  • setup 返回 Promise,当 Promise 成功时,一切正常,失败时 setError,目标页面渲染为空数据,再次 render,app 渲染为 errorPage,若后续 Issues 修复,则可调整逻辑至:以组件 Promise 返回结果为准决定是否渲染错误页面

Vite