import { createApp, h, Suspense } from 'vue'
import { renderToString } from '../src/renderToString'
-import { ssrRenderSuspense } from '../src/helpers/ssrRenderSuspense'
-import { ssrRenderComponent } from '../src'
-import { mockError } from '@vue/shared'
+import { mockWarn } from '@vue/shared'
describe('SSR Suspense', () => {
- mockError()
+ mockWarn()
const ResolvingAsync = {
async setup() {
}
}
- describe('compiled', () => {
- test('basic', async () => {
- const app = createApp({
- ssrRender(_ctx, _push) {
- _push(
- ssrRenderSuspense({
- default: _push => {
- _push('<div>async</div>')
- }
- })
- )
- }
- })
-
- expect(await renderToString(app)).toBe(`<!--[--><div>async</div><!--]-->`)
- })
-
- test('with async component', async () => {
- const app = createApp({
- ssrRender(_ctx, _push) {
- _push(
- ssrRenderSuspense({
- default: _push => {
- _push(ssrRenderComponent(ResolvingAsync))
- }
- })
- )
- }
- })
-
- expect(await renderToString(app)).toBe(`<!--[--><div>async</div><!--]-->`)
- })
-
- test('fallback', async () => {
- const app = createApp({
- ssrRender(_ctx, _push) {
- _push(
- ssrRenderSuspense({
- default: _push => {
- _push(ssrRenderComponent(RejectingAsync))
- },
- fallback: _push => {
- _push('<div>fallback</div>')
- }
- })
- )
- }
- })
-
- expect(await renderToString(app)).toBe(
- `<!--[--><div>fallback</div><!--]-->`
- )
- expect('Uncaught error in async setup').toHaveBeenWarned()
- })
- })
-
- describe('vnode', () => {
- test('content', async () => {
- const Comp = {
- render() {
- return h(Suspense, null, {
- default: h(ResolvingAsync),
- fallback: h('div', 'fallback')
- })
- }
+ test('content', async () => {
+ const Comp = {
+ render() {
+ return h(Suspense, null, {
+ default: h(ResolvingAsync),
+ fallback: h('div', 'fallback')
+ })
}
+ }
- expect(await renderToString(createApp(Comp))).toBe(`<div>async</div>`)
- })
+ expect(await renderToString(createApp(Comp))).toBe(`<div>async</div>`)
+ })
- test('fallback', async () => {
- const Comp = {
- render() {
- return h(Suspense, null, {
- default: h(RejectingAsync),
- fallback: h('div', 'fallback')
- })
- }
+ test('reject', async () => {
+ const Comp = {
+ render() {
+ return h(Suspense, null, {
+ default: h(RejectingAsync),
+ fallback: h('div', 'fallback')
+ })
}
+ }
- expect(await renderToString(createApp(Comp))).toBe(`<div>fallback</div>`)
- expect('Uncaught error in async setup').toHaveBeenWarned()
- })
+ expect(await renderToString(createApp(Comp))).toBe(`<!---->`)
+ expect('Uncaught error in async setup').toHaveBeenWarned()
+ expect('missing template').toHaveBeenWarned()
+ })
- test('2 components', async () => {
- const Comp = {
- render() {
- return h(Suspense, null, {
- default: h('div', [h(ResolvingAsync), h(ResolvingAsync)]),
- fallback: h('div', 'fallback')
- })
- }
+ test('2 components', async () => {
+ const Comp = {
+ render() {
+ return h(Suspense, null, {
+ default: h('div', [h(ResolvingAsync), h(ResolvingAsync)]),
+ fallback: h('div', 'fallback')
+ })
}
+ }
- expect(await renderToString(createApp(Comp))).toBe(
- `<div><div>async</div><div>async</div></div>`
- )
- })
+ expect(await renderToString(createApp(Comp))).toBe(
+ `<div><div>async</div><div>async</div></div>`
+ )
+ })
- test('resolving component + rejecting component', async () => {
- const Comp = {
- render() {
- return h(Suspense, null, {
- default: h('div', [h(ResolvingAsync), h(RejectingAsync)]),
- fallback: h('div', 'fallback')
- })
- }
+ test('resolving component + rejecting component', async () => {
+ const Comp = {
+ render() {
+ return h(Suspense, null, {
+ default: h('div', [h(ResolvingAsync), h(RejectingAsync)]),
+ fallback: h('div', 'fallback')
+ })
}
+ }
- expect(await renderToString(createApp(Comp))).toBe(`<div>fallback</div>`)
- expect('Uncaught error in async setup').toHaveBeenWarned()
- })
+ expect(await renderToString(createApp(Comp))).toBe(
+ `<div><div>async</div><!----></div>`
+ )
+ expect('Uncaught error in async setup').toHaveBeenWarned()
+ expect('missing template or render function').toHaveBeenWarned()
+ })
- test('failing suspense in passing suspense', async () => {
- const Comp = {
- render() {
- return h(Suspense, null, {
- default: h('div', [
- h(ResolvingAsync),
- h(Suspense, null, {
- default: h('div', [h(RejectingAsync)]),
- fallback: h('div', 'fallback 2')
- })
- ]),
- fallback: h('div', 'fallback 1')
- })
- }
+ test('failing suspense in passing suspense', async () => {
+ const Comp = {
+ render() {
+ return h(Suspense, null, {
+ default: h('div', [
+ h(ResolvingAsync),
+ h(Suspense, null, {
+ default: h('div', [h(RejectingAsync)]),
+ fallback: h('div', 'fallback 2')
+ })
+ ]),
+ fallback: h('div', 'fallback 1')
+ })
}
+ }
- expect(await renderToString(createApp(Comp))).toBe(
- `<div><div>async</div><div>fallback 2</div></div>`
- )
- expect('Uncaught error in async setup').toHaveBeenWarned()
- })
+ expect(await renderToString(createApp(Comp))).toBe(
+ `<div><div>async</div><div><!----></div></div>`
+ )
+ expect('Uncaught error in async setup').toHaveBeenWarned()
+ expect('missing template').toHaveBeenWarned()
+ })
- test('passing suspense in failing suspense', async () => {
- const Comp = {
- render() {
- return h(Suspense, null, {
- default: h('div', [
- h(RejectingAsync),
- h(Suspense, null, {
- default: h('div', [h(ResolvingAsync)]),
- fallback: h('div', 'fallback 2')
- })
- ]),
- fallback: h('div', 'fallback 1')
- })
- }
+ test('passing suspense in failing suspense', async () => {
+ const Comp = {
+ render() {
+ return h(Suspense, null, {
+ default: h('div', [
+ h(RejectingAsync),
+ h(Suspense, null, {
+ default: h('div', [h(ResolvingAsync)]),
+ fallback: h('div', 'fallback 2')
+ })
+ ]),
+ fallback: h('div', 'fallback 1')
+ })
}
+ }
- expect(await renderToString(createApp(Comp))).toBe(
- `<div>fallback 1</div>`
- )
- expect('Uncaught error in async setup').toHaveBeenWarned()
- })
+ expect(await renderToString(createApp(Comp))).toBe(
+ `<div><!----><div><div>async</div></div></div>`
+ )
+ expect('Uncaught error in async setup').toHaveBeenWarned()
+ expect('missing template').toHaveBeenWarned()
})
})
ssrUtils,
Slots,
createApp,
- ssrContextKey
+ ssrContextKey,
+ warn
} from 'vue'
import {
ShapeFlags,
)
}
-export const AsyncSetupErrorMarker = Symbol('Vue async setup error')
-
function renderComponentVNode(
vnode: VNode,
parentComponent: ComponentInternalInstance | null = null
if (isPromise(res)) {
return res
.catch(err => {
- // normalize async setup rejection
- if (!(err instanceof Error)) {
- err = new Error(String(err))
- }
- err[AsyncSetupErrorMarker] = true
- console.error(
- `[@vue/server-renderer]: Uncaught error in async setup:\n`,
- err
- )
- // rethrow for suspense
- throw err
+ warn(`[@vue/server-renderer]: Uncaught error in async setup:\n`, err)
})
.then(() => renderComponentSubTree(instance))
} else {
} else if (instance.render) {
renderVNode(push, renderComponentRoot(instance), instance)
} else {
- throw new Error(
+ warn(
`Component ${
comp.name ? `${comp.name} ` : ``
} is missing template or render function.`
)
+ push(`<!---->`)
}
}
return getBuffer()
err.loc.start.offset,
err.loc.end.offset
)
- console.error(codeFrame ? `${message}\n${codeFrame}` : message)
+ warn(codeFrame ? `${message}\n${codeFrame}` : message)
} else {
throw err
}
} else if (shapeFlag & ShapeFlags.PORTAL) {
renderPortalVNode(vnode, parentComponent)
} else if (shapeFlag & ShapeFlags.SUSPENSE) {
- push(renderSuspenseVNode(vnode, parentComponent))
+ renderVNode(
+ push,
+ normalizeSuspenseChildren(vnode).content,
+ parentComponent
+ )
} else {
- console.error(
+ warn(
'[@vue/server-renderer] Invalid VNode type:',
type,
`(${typeof type})`
) {
const target = vnode.props && vnode.props.target
if (!target) {
- console.error(`[@vue/server-renderer] Portal is missing target prop.`)
+ warn(`[@vue/server-renderer] Portal is missing target prop.`)
return []
}
if (!isString(target)) {
- console.error(
+ warn(
`[@vue/server-renderer] Portal target must be a query selector string.`
)
return []
}
}
}
-
-async function renderSuspenseVNode(
- vnode: VNode,
- parentComponent: ComponentInternalInstance
-): Promise<ResolvedSSRBuffer> {
- const { content, fallback } = normalizeSuspenseChildren(vnode)
- try {
- const { push, getBuffer } = createBuffer()
- renderVNode(push, content, parentComponent)
- // await here so error can be caught
- return await getBuffer()
- } catch (e) {
- if (e[AsyncSetupErrorMarker]) {
- const { push, getBuffer } = createBuffer()
- renderVNode(push, fallback, parentComponent)
- return getBuffer()
- } else {
- throw e
- }
- }
-}