diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index e71810fc958..cb1636fd17e 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -204,12 +204,15 @@ export function generate(
}
// enter render function
- if (genScopeId && !ssr) {
- push(`const render = ${PURE_ANNOTATION}_withId(`)
- }
if (!ssr) {
+ if (genScopeId) {
+ push(`const render = ${PURE_ANNOTATION}_withId(`)
+ }
push(`function render(_ctx, _cache) {`)
} else {
+ if (genScopeId) {
+ push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
+ }
push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
}
indent()
@@ -272,7 +275,7 @@ export function generate(
deindent()
push(`}`)
- if (genScopeId && !ssr) {
+ if (genScopeId) {
push(`)`)
}
diff --git a/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts b/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
index a2a6452a741..03755ab3fb0 100644
--- a/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
@@ -6,14 +6,17 @@ describe('ssr: scopeId', () => {
test('basic', () => {
expect(
compile(`
hello
`, {
- scopeId
+ scopeId,
+ mode: 'module'
}).code
).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(\`hello
\`)
- }"
+ })"
`)
})
@@ -21,17 +24,19 @@ describe('ssr: scopeId', () => {
// should have no branching inside slot
expect(
compile(`foo`, {
- scopeId
+ scopeId,
+ mode: 'module'
}).code
).toMatchInlineSnapshot(`
- "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
- const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
+ "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, withScopeId as _withScopeId } from \\"vue\\"
+ 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\\")
_push(_ssrRenderComponent(_component_foo, _attrs, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
+ default: _withId((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`foo\`)
} else {
@@ -42,24 +47,26 @@ describe('ssr: scopeId', () => {
}),
_: 1
}, _parent))
- }"
+ })"
`)
})
test('inside slots (with elements)', () => {
expect(
compile(`hello`, {
- scopeId
+ scopeId,
+ mode: 'module'
}).code
).toMatchInlineSnapshot(`
- "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
- const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
+ "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
+ 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\\")
_push(_ssrRenderComponent(_component_foo, _attrs, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
+ default: _withId((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`hello\`)
} else {
@@ -70,29 +77,31 @@ describe('ssr: scopeId', () => {
}),
_: 1
}, _parent))
- }"
+ })"
`)
})
test('nested slots', () => {
expect(
compile(`hello`, {
- scopeId
+ scopeId,
+ mode: 'module'
}).code
).toMatchInlineSnapshot(`
- "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
- const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
+ "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
+ 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_bar = _resolveComponent(\\"bar\\")
_push(_ssrRenderComponent(_component_foo, _attrs, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
+ default: _withId((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`hello\`)
_push(_ssrRenderComponent(_component_bar, null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
+ default: _withId((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`\`)
} else {
@@ -107,7 +116,7 @@ describe('ssr: scopeId', () => {
return [
_createVNode(\\"span\\", null, \\"hello\\"),
_createVNode(_component_bar, null, {
- default: _withCtx(() => [
+ default: _withId(() => [
_createVNode(\\"span\\")
]),
_: 1
@@ -117,7 +126,7 @@ describe('ssr: scopeId', () => {
}),
_: 1
}, _parent))
- }"
+ })"
`)
})
})
diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts
index 05813b9c93e..c158f4ad863 100644
--- a/packages/runtime-core/src/componentRenderUtils.ts
+++ b/packages/runtime-core/src/componentRenderUtils.ts
@@ -150,12 +150,14 @@ export function renderComponentRoot(
// inherit scopeId
const scopeId = vnode.scopeId
- if (scopeId) {
- root = cloneVNode(root, { [scopeId]: '' })
- }
const treeOwnerId = parent && parent.type.__scopeId
- if (treeOwnerId && treeOwnerId !== scopeId) {
- root = cloneVNode(root, { [treeOwnerId + '-s']: '' })
+ const slotScopeId =
+ 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
diff --git a/packages/server-renderer/__tests__/renderToString.spec.ts b/packages/server-renderer/__tests__/renderToString.spec.ts
index ab210c621ac..f7985632fb3 100644
--- a/packages/server-renderer/__tests__/renderToString.spec.ts
+++ b/packages/server-renderer/__tests__/renderToString.spec.ts
@@ -12,7 +12,7 @@ import {
} from 'vue'
import { escapeHtml, mockWarn } from '@vue/shared'
import { renderToString } from '../src/renderToString'
-import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
+import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
mockWarn()
@@ -222,9 +222,9 @@ describe('ssr: renderToString', () => {
{ msg: 'hello' },
{
// optimized slot using string push
- default: ({ msg }: any, push: any, _p: any) => {
+ default: (({ msg }, push, _p) => {
push(`${msg}`)
- },
+ }) as SSRSlot,
// important to avoid slots being normalized
_: 1 as any
},
diff --git a/packages/server-renderer/__tests__/ssrScopeId.spec.ts b/packages/server-renderer/__tests__/ssrScopeId.spec.ts
new file mode 100644
index 00000000000..8b58fc66b93
--- /dev/null
+++ b/packages/server-renderer/__tests__/ssrScopeId.spec.ts
@@ -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(``)
+ }
+ }
+
+ 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(``)
+ })
+
+ test('inside slot', async () => {
+ const withParentId = withScopeId('parent')
+
+ const Child = {
+ ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
+ push(``)
+ }
+ }
+
+ 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(``)
+ })
+})
diff --git a/packages/server-renderer/src/helpers/ssrRenderSlot.ts b/packages/server-renderer/src/helpers/ssrRenderSlot.ts
index 8c96322ab0f..6231b189525 100644
--- a/packages/server-renderer/src/helpers/ssrRenderSlot.ts
+++ b/packages/server-renderer/src/helpers/ssrRenderSlot.ts
@@ -1,4 +1,4 @@
-import { ComponentInternalInstance, Slot, Slots } from 'vue'
+import { ComponentInternalInstance, Slots } from 'vue'
import { Props, PushFn, renderVNodeChildren } from '../render'
export type SSRSlots = Record
@@ -21,13 +21,16 @@ export function ssrRenderSlot(
push(``)
const slotFn = slots[slotName]
if (slotFn) {
- if (slotFn.length > 1) {
- // only ssr-optimized slot fns accept more than 1 arguments
- const scopeId = parentComponent && parentComponent.type.__scopeId
- slotFn(slotProps, push, parentComponent, scopeId ? ` ${scopeId}-s` : ``)
- } else {
+ const scopeId = parentComponent && parentComponent.type.__scopeId
+ const ret = slotFn(
+ slotProps,
+ push,
+ parentComponent,
+ scopeId ? ` ${scopeId}-s` : ``
+ )
+ if (Array.isArray(ret)) {
// normal slot
- renderVNodeChildren(push, (slotFn as Slot)(slotProps), parentComponent)
+ renderVNodeChildren(push, ret, parentComponent)
}
} else if (fallbackRenderFn) {
fallbackRenderFn()
diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts
index 1846bffcecc..55af4ff09bd 100644
--- a/packages/server-renderer/src/render.ts
+++ b/packages/server-renderer/src/render.ts
@@ -109,11 +109,23 @@ function renderComponentSubTree(
if (comp.ssrRender) {
// 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
setCurrentRenderingInstance(instance)
- // fallthrough attrs
- const attrs =
- instance.type.inheritAttrs !== false ? instance.attrs : undefined
comp.ssrRender(instance.proxy, push, instance, attrs)
setCurrentRenderingInstance(null)
} else if (instance.render) {