fix(ssr): fix ssr scopeId on component root
This commit is contained in:
parent
978d9522e8
commit
afe13e0584
@ -204,12 +204,15 @@ export function generate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// enter render function
|
// enter render function
|
||||||
if (genScopeId && !ssr) {
|
if (!ssr) {
|
||||||
|
if (genScopeId) {
|
||||||
push(`const render = ${PURE_ANNOTATION}_withId(`)
|
push(`const render = ${PURE_ANNOTATION}_withId(`)
|
||||||
}
|
}
|
||||||
if (!ssr) {
|
|
||||||
push(`function render(_ctx, _cache) {`)
|
push(`function render(_ctx, _cache) {`)
|
||||||
} else {
|
} else {
|
||||||
|
if (genScopeId) {
|
||||||
|
push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
|
||||||
|
}
|
||||||
push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
|
push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
|
||||||
}
|
}
|
||||||
indent()
|
indent()
|
||||||
@ -272,7 +275,7 @@ export function generate(
|
|||||||
deindent()
|
deindent()
|
||||||
push(`}`)
|
push(`}`)
|
||||||
|
|
||||||
if (genScopeId && !ssr) {
|
if (genScopeId) {
|
||||||
push(`)`)
|
push(`)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,14 +6,17 @@ describe('ssr: scopeId', () => {
|
|||||||
test('basic', () => {
|
test('basic', () => {
|
||||||
expect(
|
expect(
|
||||||
compile(`<div><span>hello</span></div>`, {
|
compile(`<div><span>hello</span></div>`, {
|
||||||
scopeId
|
scopeId,
|
||||||
|
mode: 'module'
|
||||||
}).code
|
}).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
|
"import { withScopeId as _withScopeId } from \\"vue\\"
|
||||||
|
import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
|
||||||
|
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
|
||||||
}"
|
})"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -21,17 +24,19 @@ describe('ssr: scopeId', () => {
|
|||||||
// should have no branching inside slot
|
// should have no branching inside slot
|
||||||
expect(
|
expect(
|
||||||
compile(`<foo>foo</foo>`, {
|
compile(`<foo>foo</foo>`, {
|
||||||
scopeId
|
scopeId,
|
||||||
|
mode: 'module'
|
||||||
}).code
|
}).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
|
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, withScopeId as _withScopeId } from \\"vue\\"
|
||||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
|
||||||
|
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
const _component_foo = _resolveComponent(\\"foo\\")
|
const _component_foo = _resolveComponent(\\"foo\\")
|
||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
||||||
default: _withCtx((_, _push, _parent, _scopeId) => {
|
default: _withId((_, _push, _parent, _scopeId) => {
|
||||||
if (_push) {
|
if (_push) {
|
||||||
_push(\`foo\`)
|
_push(\`foo\`)
|
||||||
} else {
|
} else {
|
||||||
@ -42,24 +47,26 @@ describe('ssr: scopeId', () => {
|
|||||||
}),
|
}),
|
||||||
_: 1
|
_: 1
|
||||||
}, _parent))
|
}, _parent))
|
||||||
}"
|
})"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('inside slots (with elements)', () => {
|
test('inside slots (with elements)', () => {
|
||||||
expect(
|
expect(
|
||||||
compile(`<foo><span>hello</span></foo>`, {
|
compile(`<foo><span>hello</span></foo>`, {
|
||||||
scopeId
|
scopeId,
|
||||||
|
mode: 'module'
|
||||||
}).code
|
}).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
|
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
|
||||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
|
||||||
|
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
const _component_foo = _resolveComponent(\\"foo\\")
|
const _component_foo = _resolveComponent(\\"foo\\")
|
||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
||||||
default: _withCtx((_, _push, _parent, _scopeId) => {
|
default: _withId((_, _push, _parent, _scopeId) => {
|
||||||
if (_push) {
|
if (_push) {
|
||||||
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
|
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
|
||||||
} else {
|
} else {
|
||||||
@ -70,29 +77,31 @@ describe('ssr: scopeId', () => {
|
|||||||
}),
|
}),
|
||||||
_: 1
|
_: 1
|
||||||
}, _parent))
|
}, _parent))
|
||||||
}"
|
})"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('nested slots', () => {
|
test('nested slots', () => {
|
||||||
expect(
|
expect(
|
||||||
compile(`<foo><span>hello</span><bar><span/></bar></foo>`, {
|
compile(`<foo><span>hello</span><bar><span/></bar></foo>`, {
|
||||||
scopeId
|
scopeId,
|
||||||
|
mode: 'module'
|
||||||
}).code
|
}).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
|
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
|
||||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
|
||||||
|
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
const _component_foo = _resolveComponent(\\"foo\\")
|
const _component_foo = _resolveComponent(\\"foo\\")
|
||||||
const _component_bar = _resolveComponent(\\"bar\\")
|
const _component_bar = _resolveComponent(\\"bar\\")
|
||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
||||||
default: _withCtx((_, _push, _parent, _scopeId) => {
|
default: _withId((_, _push, _parent, _scopeId) => {
|
||||||
if (_push) {
|
if (_push) {
|
||||||
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
|
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
|
||||||
_push(_ssrRenderComponent(_component_bar, null, {
|
_push(_ssrRenderComponent(_component_bar, null, {
|
||||||
default: _withCtx((_, _push, _parent, _scopeId) => {
|
default: _withId((_, _push, _parent, _scopeId) => {
|
||||||
if (_push) {
|
if (_push) {
|
||||||
_push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
|
_push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
|
||||||
} else {
|
} else {
|
||||||
@ -107,7 +116,7 @@ describe('ssr: scopeId', () => {
|
|||||||
return [
|
return [
|
||||||
_createVNode(\\"span\\", null, \\"hello\\"),
|
_createVNode(\\"span\\", null, \\"hello\\"),
|
||||||
_createVNode(_component_bar, null, {
|
_createVNode(_component_bar, null, {
|
||||||
default: _withCtx(() => [
|
default: _withId(() => [
|
||||||
_createVNode(\\"span\\")
|
_createVNode(\\"span\\")
|
||||||
]),
|
]),
|
||||||
_: 1
|
_: 1
|
||||||
@ -117,7 +126,7 @@ describe('ssr: scopeId', () => {
|
|||||||
}),
|
}),
|
||||||
_: 1
|
_: 1
|
||||||
}, _parent))
|
}, _parent))
|
||||||
}"
|
})"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -150,12 +150,14 @@ export function renderComponentRoot(
|
|||||||
|
|
||||||
// inherit scopeId
|
// inherit scopeId
|
||||||
const scopeId = vnode.scopeId
|
const scopeId = vnode.scopeId
|
||||||
if (scopeId) {
|
|
||||||
root = cloneVNode(root, { [scopeId]: '' })
|
|
||||||
}
|
|
||||||
const treeOwnerId = parent && parent.type.__scopeId
|
const treeOwnerId = parent && parent.type.__scopeId
|
||||||
if (treeOwnerId && treeOwnerId !== scopeId) {
|
const slotScopeId =
|
||||||
root = cloneVNode(root, { [treeOwnerId + '-s']: '' })
|
treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
|
||||||
|
if (scopeId || slotScopeId) {
|
||||||
|
const extras: Data = {}
|
||||||
|
if (scopeId) extras[scopeId] = ''
|
||||||
|
if (slotScopeId) extras[slotScopeId] = ''
|
||||||
|
root = cloneVNode(root, extras)
|
||||||
}
|
}
|
||||||
|
|
||||||
// inherit directives
|
// inherit directives
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { escapeHtml, mockWarn } from '@vue/shared'
|
import { escapeHtml, mockWarn } from '@vue/shared'
|
||||||
import { renderToString } from '../src/renderToString'
|
import { renderToString } from '../src/renderToString'
|
||||||
import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
|
import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
|
||||||
import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
|
import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
|
||||||
|
|
||||||
mockWarn()
|
mockWarn()
|
||||||
@ -222,9 +222,9 @@ describe('ssr: renderToString', () => {
|
|||||||
{ msg: 'hello' },
|
{ msg: 'hello' },
|
||||||
{
|
{
|
||||||
// optimized slot using string push
|
// optimized slot using string push
|
||||||
default: ({ msg }: any, push: any, _p: any) => {
|
default: (({ msg }, push, _p) => {
|
||||||
push(`<span>${msg}</span>`)
|
push(`<span>${msg}</span>`)
|
||||||
},
|
}) as SSRSlot,
|
||||||
// important to avoid slots being normalized
|
// important to avoid slots being normalized
|
||||||
_: 1 as any
|
_: 1 as any
|
||||||
},
|
},
|
||||||
|
62
packages/server-renderer/__tests__/ssrScopeId.spec.ts
Normal file
62
packages/server-renderer/__tests__/ssrScopeId.spec.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { createApp, withScopeId } 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')
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||||
|
push(`<div${ssrRenderAttrs(attrs)}></div>`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
ssrRender: withParentId((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')
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
|
||||||
|
push(`<div${ssrRenderAttrs(attrs)} child></div>`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = {
|
||||||
|
__scopeId: 'wrapper',
|
||||||
|
ssrRender: (ctx: any, push: any, parent: any) => {
|
||||||
|
ssrRenderSlot(ctx.$slots, 'default', {}, null, push, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
ssrRender: withParentId((_: any, push: any, parent: any) => {
|
||||||
|
push(
|
||||||
|
ssrRenderComponent(
|
||||||
|
Wrapper,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
default: withParentId((_: any, push: any, parent: any) => {
|
||||||
|
push(ssrRenderComponent(Child, null, null, parent))
|
||||||
|
}),
|
||||||
|
_: 1
|
||||||
|
} as any,
|
||||||
|
parent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await renderToString(createApp(Comp))
|
||||||
|
expect(result).toBe(`<!--[--><div parent wrapper-s child></div><!--]-->`)
|
||||||
|
})
|
||||||
|
})
|
@ -1,4 +1,4 @@
|
|||||||
import { ComponentInternalInstance, Slot, Slots } from 'vue'
|
import { ComponentInternalInstance, Slots } from 'vue'
|
||||||
import { Props, PushFn, renderVNodeChildren } from '../render'
|
import { Props, PushFn, renderVNodeChildren } from '../render'
|
||||||
|
|
||||||
export type SSRSlots = Record<string, SSRSlot>
|
export type SSRSlots = Record<string, SSRSlot>
|
||||||
@ -21,13 +21,16 @@ export function ssrRenderSlot(
|
|||||||
push(`<!--[-->`)
|
push(`<!--[-->`)
|
||||||
const slotFn = slots[slotName]
|
const slotFn = slots[slotName]
|
||||||
if (slotFn) {
|
if (slotFn) {
|
||||||
if (slotFn.length > 1) {
|
|
||||||
// only ssr-optimized slot fns accept more than 1 arguments
|
|
||||||
const scopeId = parentComponent && parentComponent.type.__scopeId
|
const scopeId = parentComponent && parentComponent.type.__scopeId
|
||||||
slotFn(slotProps, push, parentComponent, scopeId ? ` ${scopeId}-s` : ``)
|
const ret = slotFn(
|
||||||
} else {
|
slotProps,
|
||||||
|
push,
|
||||||
|
parentComponent,
|
||||||
|
scopeId ? ` ${scopeId}-s` : ``
|
||||||
|
)
|
||||||
|
if (Array.isArray(ret)) {
|
||||||
// normal slot
|
// normal slot
|
||||||
renderVNodeChildren(push, (slotFn as Slot)(slotProps), parentComponent)
|
renderVNodeChildren(push, ret, parentComponent)
|
||||||
}
|
}
|
||||||
} else if (fallbackRenderFn) {
|
} else if (fallbackRenderFn) {
|
||||||
fallbackRenderFn()
|
fallbackRenderFn()
|
||||||
|
@ -109,11 +109,23 @@ function renderComponentSubTree(
|
|||||||
|
|
||||||
if (comp.ssrRender) {
|
if (comp.ssrRender) {
|
||||||
// optimized
|
// optimized
|
||||||
|
// resolve fallthrough attrs
|
||||||
|
let attrs =
|
||||||
|
instance.type.inheritAttrs !== false ? instance.attrs : undefined
|
||||||
|
|
||||||
|
// 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] = ''
|
||||||
|
}
|
||||||
|
|
||||||
// set current rendering instance for asset resolution
|
// set current rendering instance for asset resolution
|
||||||
setCurrentRenderingInstance(instance)
|
setCurrentRenderingInstance(instance)
|
||||||
// fallthrough attrs
|
|
||||||
const attrs =
|
|
||||||
instance.type.inheritAttrs !== false ? instance.attrs : undefined
|
|
||||||
comp.ssrRender(instance.proxy, push, instance, attrs)
|
comp.ssrRender(instance.proxy, push, instance, attrs)
|
||||||
setCurrentRenderingInstance(null)
|
setCurrentRenderingInstance(null)
|
||||||
} else if (instance.render) {
|
} else if (instance.render) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user