Skip to content

Commit afe13e0

Browse files
committed
fix(ssr): fix ssr scopeId on component root
1 parent 978d952 commit afe13e0

File tree

7 files changed

+137
-46
lines changed

7 files changed

+137
-46
lines changed

packages/compiler-core/src/codegen.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,15 @@ export function generate(
204204
}
205205

206206
// enter render function
207-
if (genScopeId && !ssr) {
208-
push(`const render = ${PURE_ANNOTATION}_withId(`)
209-
}
210207
if (!ssr) {
208+
if (genScopeId) {
209+
push(`const render = ${PURE_ANNOTATION}_withId(`)
210+
}
211211
push(`function render(_ctx, _cache) {`)
212212
} else {
213+
if (genScopeId) {
214+
push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
215+
}
213216
push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
214217
}
215218
indent()
@@ -272,7 +275,7 @@ export function generate(
272275
deindent()
273276
push(`}`)
274277

275-
if (genScopeId && !ssr) {
278+
if (genScopeId) {
276279
push(`)`)
277280
}
278281

packages/compiler-ssr/__tests__/ssrScopeId.spec.ts

+33-24
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,37 @@ describe('ssr: scopeId', () => {
66
test('basic', () => {
77
expect(
88
compile(`<div><span>hello</span></div>`, {
9-
scopeId
9+
scopeId,
10+
mode: 'module'
1011
}).code
1112
).toMatchInlineSnapshot(`
12-
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
13+
"import { withScopeId as _withScopeId } from \\"vue\\"
14+
import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
15+
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
1316
14-
return function ssrRender(_ctx, _push, _parent, _attrs) {
17+
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
1518
_push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
16-
}"
19+
})"
1720
`)
1821
})
1922

2023
test('inside slots (only text)', () => {
2124
// should have no branching inside slot
2225
expect(
2326
compile(`<foo>foo</foo>`, {
24-
scopeId
27+
scopeId,
28+
mode: 'module'
2529
}).code
2630
).toMatchInlineSnapshot(`
27-
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
28-
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
31+
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, withScopeId as _withScopeId } from \\"vue\\"
32+
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
33+
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
2934
30-
return function ssrRender(_ctx, _push, _parent, _attrs) {
35+
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
3136
const _component_foo = _resolveComponent(\\"foo\\")
3237
3338
_push(_ssrRenderComponent(_component_foo, _attrs, {
34-
default: _withCtx((_, _push, _parent, _scopeId) => {
39+
default: _withId((_, _push, _parent, _scopeId) => {
3540
if (_push) {
3641
_push(\`foo\`)
3742
} else {
@@ -42,24 +47,26 @@ describe('ssr: scopeId', () => {
4247
}),
4348
_: 1
4449
}, _parent))
45-
}"
50+
})"
4651
`)
4752
})
4853

4954
test('inside slots (with elements)', () => {
5055
expect(
5156
compile(`<foo><span>hello</span></foo>`, {
52-
scopeId
57+
scopeId,
58+
mode: 'module'
5359
}).code
5460
).toMatchInlineSnapshot(`
55-
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
56-
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
61+
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
62+
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
63+
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
5764
58-
return function ssrRender(_ctx, _push, _parent, _attrs) {
65+
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
5966
const _component_foo = _resolveComponent(\\"foo\\")
6067
6168
_push(_ssrRenderComponent(_component_foo, _attrs, {
62-
default: _withCtx((_, _push, _parent, _scopeId) => {
69+
default: _withId((_, _push, _parent, _scopeId) => {
6370
if (_push) {
6471
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
6572
} else {
@@ -70,29 +77,31 @@ describe('ssr: scopeId', () => {
7077
}),
7178
_: 1
7279
}, _parent))
73-
}"
80+
})"
7481
`)
7582
})
7683

7784
test('nested slots', () => {
7885
expect(
7986
compile(`<foo><span>hello</span><bar><span/></bar></foo>`, {
80-
scopeId
87+
scopeId,
88+
mode: 'module'
8189
}).code
8290
).toMatchInlineSnapshot(`
83-
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
84-
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
91+
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
92+
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
93+
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
8594
86-
return function ssrRender(_ctx, _push, _parent, _attrs) {
95+
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
8796
const _component_foo = _resolveComponent(\\"foo\\")
8897
const _component_bar = _resolveComponent(\\"bar\\")
8998
9099
_push(_ssrRenderComponent(_component_foo, _attrs, {
91-
default: _withCtx((_, _push, _parent, _scopeId) => {
100+
default: _withId((_, _push, _parent, _scopeId) => {
92101
if (_push) {
93102
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
94103
_push(_ssrRenderComponent(_component_bar, null, {
95-
default: _withCtx((_, _push, _parent, _scopeId) => {
104+
default: _withId((_, _push, _parent, _scopeId) => {
96105
if (_push) {
97106
_push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
98107
} else {
@@ -107,7 +116,7 @@ describe('ssr: scopeId', () => {
107116
return [
108117
_createVNode(\\"span\\", null, \\"hello\\"),
109118
_createVNode(_component_bar, null, {
110-
default: _withCtx(() => [
119+
default: _withId(() => [
111120
_createVNode(\\"span\\")
112121
]),
113122
_: 1
@@ -117,7 +126,7 @@ describe('ssr: scopeId', () => {
117126
}),
118127
_: 1
119128
}, _parent))
120-
}"
129+
})"
121130
`)
122131
})
123132
})

packages/runtime-core/src/componentRenderUtils.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,14 @@ export function renderComponentRoot(
150150

151151
// inherit scopeId
152152
const scopeId = vnode.scopeId
153-
if (scopeId) {
154-
root = cloneVNode(root, { [scopeId]: '' })
155-
}
156153
const treeOwnerId = parent && parent.type.__scopeId
157-
if (treeOwnerId && treeOwnerId !== scopeId) {
158-
root = cloneVNode(root, { [treeOwnerId + '-s']: '' })
154+
const slotScopeId =
155+
treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
156+
if (scopeId || slotScopeId) {
157+
const extras: Data = {}
158+
if (scopeId) extras[scopeId] = ''
159+
if (slotScopeId) extras[slotScopeId] = ''
160+
root = cloneVNode(root, extras)
159161
}
160162

161163
// inherit directives

packages/server-renderer/__tests__/renderToString.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from 'vue'
1313
import { escapeHtml, mockWarn } from '@vue/shared'
1414
import { renderToString } from '../src/renderToString'
15-
import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
15+
import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
1616
import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
1717

1818
mockWarn()
@@ -222,9 +222,9 @@ describe('ssr: renderToString', () => {
222222
{ msg: 'hello' },
223223
{
224224
// optimized slot using string push
225-
default: ({ msg }: any, push: any, _p: any) => {
225+
default: (({ msg }, push, _p) => {
226226
push(`<span>${msg}</span>`)
227-
},
227+
}) as SSRSlot,
228228
// important to avoid slots being normalized
229229
_: 1 as any
230230
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { createApp, withScopeId } from 'vue'
2+
import { renderToString } from '../src/renderToString'
3+
import { ssrRenderComponent, ssrRenderAttrs, ssrRenderSlot } from '../src'
4+
5+
describe('ssr: scoped id on component root', () => {
6+
test('basic', async () => {
7+
const withParentId = withScopeId('parent')
8+
9+
const Child = {
10+
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
11+
push(`<div${ssrRenderAttrs(attrs)}></div>`)
12+
}
13+
}
14+
15+
const Comp = {
16+
ssrRender: withParentId((ctx: any, push: any, parent: any) => {
17+
push(ssrRenderComponent(Child), null, null, parent)
18+
})
19+
}
20+
21+
const result = await renderToString(createApp(Comp))
22+
expect(result).toBe(`<div parent></div>`)
23+
})
24+
25+
test('inside slot', async () => {
26+
const withParentId = withScopeId('parent')
27+
28+
const Child = {
29+
ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
30+
push(`<div${ssrRenderAttrs(attrs)} child></div>`)
31+
}
32+
}
33+
34+
const Wrapper = {
35+
__scopeId: 'wrapper',
36+
ssrRender: (ctx: any, push: any, parent: any) => {
37+
ssrRenderSlot(ctx.$slots, 'default', {}, null, push, parent)
38+
}
39+
}
40+
41+
const Comp = {
42+
ssrRender: withParentId((_: any, push: any, parent: any) => {
43+
push(
44+
ssrRenderComponent(
45+
Wrapper,
46+
null,
47+
{
48+
default: withParentId((_: any, push: any, parent: any) => {
49+
push(ssrRenderComponent(Child, null, null, parent))
50+
}),
51+
_: 1
52+
} as any,
53+
parent
54+
)
55+
)
56+
})
57+
}
58+
59+
const result = await renderToString(createApp(Comp))
60+
expect(result).toBe(`<!--[--><div parent wrapper-s child></div><!--]-->`)
61+
})
62+
})

packages/server-renderer/src/helpers/ssrRenderSlot.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ComponentInternalInstance, Slot, Slots } from 'vue'
1+
import { ComponentInternalInstance, Slots } from 'vue'
22
import { Props, PushFn, renderVNodeChildren } from '../render'
33

44
export type SSRSlots = Record<string, SSRSlot>
@@ -21,13 +21,16 @@ export function ssrRenderSlot(
2121
push(`<!--[-->`)
2222
const slotFn = slots[slotName]
2323
if (slotFn) {
24-
if (slotFn.length > 1) {
25-
// only ssr-optimized slot fns accept more than 1 arguments
26-
const scopeId = parentComponent && parentComponent.type.__scopeId
27-
slotFn(slotProps, push, parentComponent, scopeId ? ` ${scopeId}-s` : ``)
28-
} else {
24+
const scopeId = parentComponent && parentComponent.type.__scopeId
25+
const ret = slotFn(
26+
slotProps,
27+
push,
28+
parentComponent,
29+
scopeId ? ` ${scopeId}-s` : ``
30+
)
31+
if (Array.isArray(ret)) {
2932
// normal slot
30-
renderVNodeChildren(push, (slotFn as Slot)(slotProps), parentComponent)
33+
renderVNodeChildren(push, ret, parentComponent)
3134
}
3235
} else if (fallbackRenderFn) {
3336
fallbackRenderFn()

packages/server-renderer/src/render.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,23 @@ function renderComponentSubTree(
109109

110110
if (comp.ssrRender) {
111111
// optimized
112+
// resolve fallthrough attrs
113+
let attrs =
114+
instance.type.inheritAttrs !== false ? instance.attrs : undefined
115+
116+
// inherited scopeId
117+
const scopeId = instance.vnode.scopeId
118+
const treeOwnerId = instance.parent && instance.parent.type.__scopeId
119+
const slotScopeId =
120+
treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
121+
if (scopeId || slotScopeId) {
122+
attrs = { ...attrs }
123+
if (scopeId) attrs[scopeId] = ''
124+
if (slotScopeId) attrs[slotScopeId] = ''
125+
}
126+
112127
// set current rendering instance for asset resolution
113128
setCurrentRenderingInstance(instance)
114-
// fallthrough attrs
115-
const attrs =
116-
instance.type.inheritAttrs !== false ? instance.attrs : undefined
117129
comp.ssrRender(instance.proxy, push, instance, attrs)
118130
setCurrentRenderingInstance(null)
119131
} else if (instance.render) {

0 commit comments

Comments
 (0)