diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index 2f57bcd9a01..12e16c59f76 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -184,13 +184,14 @@ export function findDir(
export function findProp(
node: ElementNode,
name: string,
- dynamicOnly: boolean = false
+ dynamicOnly: boolean = false,
+ allowEmpty: boolean = false
): ElementNode['props'][0] | undefined {
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) {
if (dynamicOnly) continue
- if (p.name === name && p.value) {
+ if (p.name === name && (p.value || allowEmpty)) {
return p
}
} else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) {
diff --git a/packages/compiler-ssr/__tests__/ssrPortal.spec.ts b/packages/compiler-ssr/__tests__/ssrPortal.spec.ts
index 7f608f91442..7f0f448a9b2 100644
--- a/packages/compiler-ssr/__tests__/ssrPortal.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrPortal.spec.ts
@@ -9,8 +9,33 @@ describe('ssr compile: portal', () => {
return function ssrRender(_ctx, _push, _parent) {
_ssrRenderPortal(_push, (_push) => {
_push(\`
\`)
- }, _ctx.target, _parent)
+ }, _ctx.target, false, _parent)
}"
`)
})
+
+ test('disabled prop handling', () => {
+ expect(compile(``).code)
+ .toMatchInlineSnapshot(`
+ "const { ssrRenderPortal: _ssrRenderPortal } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _ssrRenderPortal(_push, (_push) => {
+ _push(\`\`)
+ }, _ctx.target, true, _parent)
+ }"
+ `)
+
+ expect(
+ compile(``).code
+ ).toMatchInlineSnapshot(`
+ "const { ssrRenderPortal: _ssrRenderPortal } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ _ssrRenderPortal(_push, (_push) => {
+ _push(\`\`)
+ }, _ctx.target, _ctx.foo, _parent)
+ }"
+ `)
+ })
})
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformPortal.ts b/packages/compiler-ssr/src/transforms/ssrTransformPortal.ts
index 8c7fa063b6c..e67a5852184 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformPortal.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformPortal.ts
@@ -1,11 +1,11 @@
import {
ComponentNode,
findProp,
- JSChildNode,
NodeTypes,
createSimpleExpression,
createFunctionExpression,
- createCallExpression
+ createCallExpression,
+ ExpressionNode
} from '@vue/compiler-dom'
import {
SSRTransformContext,
@@ -27,12 +27,14 @@ export function ssrProcessPortal(
return
}
- let target: JSChildNode
- if (targetProp.type === NodeTypes.ATTRIBUTE && targetProp.value) {
- target = createSimpleExpression(targetProp.value.content, true)
- } else if (targetProp.type === NodeTypes.DIRECTIVE && targetProp.exp) {
- target = targetProp.exp
+ let target: ExpressionNode | undefined
+ if (targetProp.type === NodeTypes.ATTRIBUTE) {
+ target =
+ targetProp.value && createSimpleExpression(targetProp.value.content, true)
} else {
+ target = targetProp.exp
+ }
+ if (!target) {
context.onError(
createSSRCompilerError(
SSRErrorCodes.X_SSR_NO_PORTAL_TARGET,
@@ -42,6 +44,13 @@ export function ssrProcessPortal(
return
}
+ const disabledProp = findProp(node, 'disabled', false, true /* allow empty */)
+ const disabled = disabledProp
+ ? disabledProp.type === NodeTypes.ATTRIBUTE
+ ? `true`
+ : disabledProp.exp || `false`
+ : `false`
+
const contentRenderFn = createFunctionExpression(
[`_push`],
undefined, // Body is added later
@@ -55,6 +64,7 @@ export function ssrProcessPortal(
`_push`,
contentRenderFn,
target,
+ disabled,
`_parent`
])
)
diff --git a/packages/runtime-core/src/components/Portal.ts b/packages/runtime-core/src/components/Portal.ts
index 370b43bb0e6..85ca6ef1f65 100644
--- a/packages/runtime-core/src/components/Portal.ts
+++ b/packages/runtime-core/src/components/Portal.ts
@@ -23,6 +23,9 @@ export const enum PortalMoveTypes {
REORDER // moved in the main view
}
+const isDisabled = (props: VNode['props']): boolean =>
+ props && (props.disabled || props.disabled === '')
+
const movePortal = (
vnode: VNode,
container: RendererElement,
@@ -43,7 +46,7 @@ const movePortal = (
// if this is a re-order and portal is enabled (content is in target)
// do not move children. So the opposite is: only move children if this
// is not a reorder, or the portal is disabled
- if (!isReorder || (props && props.disabled)) {
+ if (!isReorder || isDisabled(props)) {
// Portal has either Array children or no children.
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
for (let i = 0; i < (children as VNode[]).length; i++) {
@@ -83,7 +86,7 @@ export const PortalImpl = {
} = internals
const targetSelector = n2.props && n2.props.target
- const disabled = n2.props && n2.props.disabled
+ const disabled = isDisabled(n2.props)
const { shapeFlag, children } = n2
if (n1 == null) {
if (__DEV__ && isString(targetSelector) && !querySelector) {
@@ -140,7 +143,7 @@ export const PortalImpl = {
const mainAnchor = (n2.anchor = n1.anchor)!
const target = (n2.target = n1.target)!
const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
- const wasDisabled = n1.props && n1.props.disabled
+ const wasDisabled = isDisabled(n1.props)
const currentContainer = wasDisabled ? container : target
const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
diff --git a/packages/server-renderer/__tests__/ssrPortal.spec.ts b/packages/server-renderer/__tests__/ssrPortal.spec.ts
index 45314c2b464..0a095e07207 100644
--- a/packages/server-renderer/__tests__/ssrPortal.spec.ts
+++ b/packages/server-renderer/__tests__/ssrPortal.spec.ts
@@ -17,16 +17,42 @@ describe('ssrRenderPortal', () => {
_push(`content
`)
},
'#target',
+ false,
_parent
)
}
}),
ctx
)
- expect(html).toBe('')
+ expect(html).toBe('')
expect(ctx.portals!['#target']).toBe(`content
`)
})
+ test('portal rendering (compiled + disabled)', async () => {
+ const ctx: SSRContext = {}
+ const html = await renderToString(
+ createApp({
+ data() {
+ return { msg: 'hello' }
+ },
+ ssrRender(_ctx, _push, _parent) {
+ ssrRenderPortal(
+ _push,
+ _push => {
+ _push(`content
`)
+ },
+ '#target',
+ true,
+ _parent
+ )
+ }
+ }),
+ ctx
+ )
+ expect(html).toBe('content
')
+ expect(ctx.portals!['#target']).toBe(``)
+ })
+
test('portal rendering (vnode)', async () => {
const ctx: SSRContext = {}
const html = await renderToString(
@@ -39,10 +65,27 @@ describe('ssrRenderPortal', () => {
),
ctx
)
- expect(html).toBe('')
+ expect(html).toBe('')
expect(ctx.portals!['#target']).toBe('hello')
})
+ test('portal rendering (vnode + disabled)', async () => {
+ const ctx: SSRContext = {}
+ const html = await renderToString(
+ h(
+ Portal,
+ {
+ target: `#target`,
+ disabled: true
+ },
+ h('span', 'hello')
+ ),
+ ctx
+ )
+ expect(html).toBe('hello')
+ expect(ctx.portals!['#target']).toBe(``)
+ })
+
test('multiple portals with same target', async () => {
const ctx: SSRContext = {}
const html = await renderToString(
@@ -58,7 +101,9 @@ describe('ssrRenderPortal', () => {
]),
ctx
)
- expect(html).toBe('')
+ expect(html).toBe(
+ ''
+ )
expect(ctx.portals!['#target']).toBe(
'helloworld'
)
diff --git a/packages/server-renderer/src/helpers/ssrRenderPortal.ts b/packages/server-renderer/src/helpers/ssrRenderPortal.ts
index 3e54d999ac1..2f694cbdf77 100644
--- a/packages/server-renderer/src/helpers/ssrRenderPortal.ts
+++ b/packages/server-renderer/src/helpers/ssrRenderPortal.ts
@@ -1,16 +1,31 @@
import { ComponentInternalInstance, ssrContextKey } from 'vue'
-import { SSRContext, createBuffer, PushFn } from '../renderToString'
+import {
+ SSRContext,
+ createBuffer,
+ PushFn,
+ SSRBufferItem
+} from '../renderToString'
export function ssrRenderPortal(
parentPush: PushFn,
contentRenderFn: (push: PushFn) => void,
target: string,
+ disabled: boolean,
parentComponent: ComponentInternalInstance
) {
- parentPush('')
- const { getBuffer, push } = createBuffer()
- contentRenderFn(push)
- push(``) // portal end anchor
+ parentPush('')
+
+ let portalContent: SSRBufferItem
+
+ if (disabled) {
+ contentRenderFn(parentPush)
+ portalContent = ``
+ } else {
+ const { getBuffer, push } = createBuffer()
+ contentRenderFn(push)
+ push(``) // portal end anchor
+ portalContent = getBuffer()
+ }
const context = parentComponent.appContext.provides[
ssrContextKey as any
@@ -18,8 +33,10 @@ export function ssrRenderPortal(
const portalBuffers =
context.__portalBuffers || (context.__portalBuffers = {})
if (portalBuffers[target]) {
- portalBuffers[target].push(getBuffer())
+ portalBuffers[target].push(portalContent)
} else {
- portalBuffers[target] = [getBuffer()]
+ portalBuffers[target] = [portalContent]
}
+
+ parentPush('')
}
diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts
index 14666678da3..5c84ae7aec7 100644
--- a/packages/server-renderer/src/renderToString.ts
+++ b/packages/server-renderer/src/renderToString.ts
@@ -366,6 +366,7 @@ function renderPortalVNode(
parentComponent: ComponentInternalInstance
) {
const target = vnode.props && vnode.props.target
+ const disabled = vnode.props && vnode.props.disabled
if (!target) {
warn(`[@vue/server-renderer] Portal is missing target prop.`)
return []
@@ -386,6 +387,7 @@ function renderPortalVNode(
)
},
target,
+ disabled || disabled === '',
parentComponent
)
}