@@ -2,13 +2,13 @@ import {
|
||||
createApp,
|
||||
h,
|
||||
createCommentVNode,
|
||||
withScopeId,
|
||||
resolveComponent,
|
||||
ComponentOptions,
|
||||
ref,
|
||||
defineComponent,
|
||||
createTextVNode,
|
||||
createStaticVNode
|
||||
createStaticVNode,
|
||||
withCtx
|
||||
} from 'vue'
|
||||
import { escapeHtml } from '@vue/shared'
|
||||
import { renderToString } from '../src/renderToString'
|
||||
@@ -634,34 +634,32 @@ function testRender(type: string, render: typeof renderToString) {
|
||||
describe('scopeId', () => {
|
||||
// note: here we are only testing scopeId handling for vdom serialization.
|
||||
// compiled srr render functions will include scopeId directly in strings.
|
||||
const withId = withScopeId('data-v-test')
|
||||
const withChildId = withScopeId('data-v-child')
|
||||
|
||||
test('basic', async () => {
|
||||
expect(
|
||||
await render(
|
||||
withId(() => {
|
||||
return h('div')
|
||||
})()
|
||||
)
|
||||
).toBe(`<div data-v-test></div>`)
|
||||
const Foo = {
|
||||
__scopeId: 'data-v-test',
|
||||
render() {
|
||||
return h('div')
|
||||
}
|
||||
}
|
||||
expect(await render(h(Foo))).toBe(`<div data-v-test></div>`)
|
||||
})
|
||||
|
||||
test('with slots', async () => {
|
||||
const Child = {
|
||||
__scopeId: 'data-v-child',
|
||||
render: withChildId(function(this: any) {
|
||||
render: function(this: any) {
|
||||
return h('div', this.$slots.default())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const Parent = {
|
||||
__scopeId: 'data-v-test',
|
||||
render: withId(() => {
|
||||
render: () => {
|
||||
return h(Child, null, {
|
||||
default: withId(() => h('span', 'slot'))
|
||||
default: withCtx(() => h('span', 'slot'))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
expect(await render(h(Parent))).toBe(
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { createApp, withScopeId } from 'vue'
|
||||
import { createApp, mergeProps, withCtx } from 'vue'
|
||||
import { renderToString } from '../src/renderToString'
|
||||
import { ssrRenderComponent, ssrRenderAttrs, ssrRenderSlot } from '../src'
|
||||
|
||||
describe('ssr: scoped id on component root', () => {
|
||||
test('basic', async () => {
|
||||
const withParentId = withScopeId('parent')
|
||||
|
||||
describe('ssr: scopedId runtime behavior', () => {
|
||||
test('id on component root', async () => {
|
||||
const Child = {
|
||||
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||
push(`<div${ssrRenderAttrs(attrs)}></div>`)
|
||||
@@ -13,19 +11,19 @@ describe('ssr: scoped id on component root', () => {
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
ssrRender: withParentId((ctx: any, push: any, parent: any) => {
|
||||
__scopeId: 'parent',
|
||||
ssrRender: (ctx: any, push: any, parent: any) => {
|
||||
push(ssrRenderComponent(Child), null, null, parent)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const result = await renderToString(createApp(Comp))
|
||||
expect(result).toBe(`<div parent></div>`)
|
||||
})
|
||||
|
||||
test('inside slot', async () => {
|
||||
const withParentId = withScopeId('parent')
|
||||
|
||||
test('id and :slotted on component root', async () => {
|
||||
const Child = {
|
||||
// <div></div>
|
||||
ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
|
||||
push(`<div${ssrRenderAttrs(attrs)} child></div>`)
|
||||
}
|
||||
@@ -34,29 +32,126 @@ describe('ssr: scoped id on component root', () => {
|
||||
const Wrapper = {
|
||||
__scopeId: 'wrapper',
|
||||
ssrRender: (ctx: any, push: any, parent: any) => {
|
||||
ssrRenderSlot(ctx.$slots, 'default', {}, null, push, parent)
|
||||
// <slot/>
|
||||
ssrRenderSlot(
|
||||
ctx.$slots,
|
||||
'default',
|
||||
{},
|
||||
null,
|
||||
push,
|
||||
parent,
|
||||
'wrapper-s'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
ssrRender: withParentId((_: any, push: any, parent: any) => {
|
||||
__scopeId: 'parent',
|
||||
ssrRender: (_: any, push: any, parent: any) => {
|
||||
// <Wrapper><Child/></Wrapper>
|
||||
push(
|
||||
ssrRenderComponent(
|
||||
Wrapper,
|
||||
null,
|
||||
{
|
||||
default: withParentId((_: any, push: any, parent: any) => {
|
||||
push(ssrRenderComponent(Child, null, null, parent))
|
||||
}),
|
||||
default: withCtx(
|
||||
(_: any, push: any, parent: any, scopeId: string) => {
|
||||
push(ssrRenderComponent(Child, null, null, parent, scopeId))
|
||||
}
|
||||
),
|
||||
_: 1
|
||||
} as any,
|
||||
parent
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const result = await renderToString(createApp(Comp))
|
||||
expect(result).toBe(`<!--[--><div parent wrapper-s child></div><!--]-->`)
|
||||
})
|
||||
|
||||
// #2892
|
||||
test(':slotted on forwarded slots', async () => {
|
||||
const Wrapper = {
|
||||
__scopeId: 'wrapper',
|
||||
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||
// <div class="wrapper"><slot/></div>
|
||||
push(
|
||||
`<div${ssrRenderAttrs(
|
||||
mergeProps({ class: 'wrapper' }, attrs)
|
||||
)} wrapper>`
|
||||
)
|
||||
ssrRenderSlot(
|
||||
ctx.$slots,
|
||||
'default',
|
||||
{},
|
||||
null,
|
||||
push,
|
||||
parent,
|
||||
'wrapper-s'
|
||||
)
|
||||
push(`</div>`)
|
||||
}
|
||||
}
|
||||
|
||||
const Slotted = {
|
||||
__scopeId: 'slotted',
|
||||
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||
// <Wrapper><slot/></Wrapper>
|
||||
push(
|
||||
ssrRenderComponent(
|
||||
Wrapper,
|
||||
attrs,
|
||||
{
|
||||
default: withCtx(
|
||||
(_: any, push: any, parent: any, scopeId: string) => {
|
||||
ssrRenderSlot(
|
||||
ctx.$slots,
|
||||
'default',
|
||||
{},
|
||||
null,
|
||||
push,
|
||||
parent,
|
||||
'slotted-s' + scopeId
|
||||
)
|
||||
}
|
||||
),
|
||||
_: 1
|
||||
} as any,
|
||||
parent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const Root = {
|
||||
__scopeId: 'root',
|
||||
// <Slotted><div></div></Slotted>
|
||||
ssrRender: (_: any, push: any, parent: any, attrs: any) => {
|
||||
push(
|
||||
ssrRenderComponent(
|
||||
Slotted,
|
||||
attrs,
|
||||
{
|
||||
default: withCtx(
|
||||
(_: any, push: any, parent: any, scopeId: string) => {
|
||||
push(`<div root${scopeId}></div>`)
|
||||
}
|
||||
),
|
||||
_: 1
|
||||
} as any,
|
||||
parent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const result = await renderToString(createApp(Root))
|
||||
expect(result).toBe(
|
||||
`<div class="wrapper" root slotted wrapper>` +
|
||||
`<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--]-->` +
|
||||
`</div>`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,10 +6,12 @@ export function ssrRenderComponent(
|
||||
comp: Component,
|
||||
props: Props | null = null,
|
||||
children: Slots | SSRSlots | null = null,
|
||||
parentComponent: ComponentInternalInstance | null = null
|
||||
parentComponent: ComponentInternalInstance | null = null,
|
||||
slotScopeId?: string
|
||||
): SSRBuffer | Promise<SSRBuffer> {
|
||||
return renderComponentVNode(
|
||||
createVNode(comp, props, children),
|
||||
parentComponent
|
||||
parentComponent,
|
||||
slotScopeId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ export function ssrRenderSlot(
|
||||
slotProps: Props,
|
||||
fallbackRenderFn: (() => void) | null,
|
||||
push: PushFn,
|
||||
parentComponent: ComponentInternalInstance
|
||||
parentComponent: ComponentInternalInstance,
|
||||
slotScopeId?: string | null
|
||||
) {
|
||||
// template-compiled slots are always rendered as fragments
|
||||
push(`<!--[-->`)
|
||||
const slotFn = slots[slotName]
|
||||
if (slotFn) {
|
||||
const scopeId = parentComponent && parentComponent.type.__scopeId
|
||||
const slotBuffer: SSRBufferItem[] = []
|
||||
const bufferedPush = (item: SSRBufferItem) => {
|
||||
slotBuffer.push(item)
|
||||
@@ -30,7 +30,7 @@ export function ssrRenderSlot(
|
||||
slotProps,
|
||||
bufferedPush,
|
||||
parentComponent,
|
||||
scopeId ? ` ${scopeId}-s` : ``
|
||||
slotScopeId ? ' ' + slotScopeId : ''
|
||||
)
|
||||
if (Array.isArray(ret)) {
|
||||
// normal slot
|
||||
|
||||
@@ -80,7 +80,8 @@ export function createBuffer() {
|
||||
|
||||
export function renderComponentVNode(
|
||||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null = null
|
||||
parentComponent: ComponentInternalInstance | null = null,
|
||||
slotScopeId?: string
|
||||
): SSRBuffer | Promise<SSRBuffer> {
|
||||
const instance = createComponentInstance(vnode, parentComponent, null)
|
||||
const res = setupComponent(instance, true /* isSSR */)
|
||||
@@ -97,14 +98,15 @@ export function renderComponentVNode(
|
||||
warn(`[@vue/server-renderer]: Uncaught error in serverPrefetch:\n`, err)
|
||||
})
|
||||
}
|
||||
return p.then(() => renderComponentSubTree(instance))
|
||||
return p.then(() => renderComponentSubTree(instance, slotScopeId))
|
||||
} else {
|
||||
return renderComponentSubTree(instance)
|
||||
return renderComponentSubTree(instance, slotScopeId)
|
||||
}
|
||||
}
|
||||
|
||||
function renderComponentSubTree(
|
||||
instance: ComponentInternalInstance
|
||||
instance: ComponentInternalInstance,
|
||||
slotScopeId?: string
|
||||
): SSRBuffer | Promise<SSRBuffer> {
|
||||
const comp = instance.type as Component
|
||||
const { getBuffer, push } = createBuffer()
|
||||
@@ -133,13 +135,10 @@ function renderComponentSubTree(
|
||||
|
||||
// inherited scopeId
|
||||
const scopeId = instance.vnode.scopeId
|
||||
const treeOwnerId = instance.parent && instance.parent.type.__scopeId
|
||||
const slotScopeId =
|
||||
treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
|
||||
if (scopeId || slotScopeId) {
|
||||
attrs = { ...attrs }
|
||||
if (scopeId) attrs[scopeId] = ''
|
||||
if (slotScopeId) attrs[slotScopeId] = ''
|
||||
if (slotScopeId) attrs[slotScopeId.trim()] = ''
|
||||
}
|
||||
|
||||
// set current rendering instance for asset resolution
|
||||
|
||||
Reference in New Issue
Block a user