]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(portal): SSR support for portal disabled prop
authorEvan You <yyx990803@gmail.com>
Sat, 28 Mar 2020 03:45:50 +0000 (23:45 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 30 Mar 2020 15:24:29 +0000 (11:24 -0400)
packages/compiler-core/src/utils.ts
packages/compiler-ssr/__tests__/ssrPortal.spec.ts
packages/compiler-ssr/src/transforms/ssrTransformPortal.ts
packages/runtime-core/src/components/Portal.ts
packages/server-renderer/__tests__/ssrPortal.spec.ts
packages/server-renderer/src/helpers/ssrRenderPortal.ts
packages/server-renderer/src/renderToString.ts

index 2f57bcd9a01f325205220446aeadc0011a015f96..12e16c59f7689b034d0cea5e8da90931ec75867e 100644 (file)
@@ -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)) {
index 7f608f91442c98d100381b8383e0f97d9b20d731..7f0f448a9b20df3406c4ca9c4ba38647f0837875 100644 (file)
@@ -9,8 +9,33 @@ describe('ssr compile: portal', () => {
       return function ssrRender(_ctx, _push, _parent) {
         _ssrRenderPortal(_push, (_push) => {
           _push(\`<div></div>\`)
-        }, _ctx.target, _parent)
+        }, _ctx.target, false, _parent)
       }"
     `)
   })
+
+  test('disabled prop handling', () => {
+    expect(compile(`<portal :target="target" disabled><div/></portal>`).code)
+      .toMatchInlineSnapshot(`
+    "const { ssrRenderPortal: _ssrRenderPortal } = require(\\"@vue/server-renderer\\")
+
+    return function ssrRender(_ctx, _push, _parent) {
+      _ssrRenderPortal(_push, (_push) => {
+        _push(\`<div></div>\`)
+      }, _ctx.target, true, _parent)
+    }"
+  `)
+
+    expect(
+      compile(`<portal :target="target" :disabled="foo"><div/></portal>`).code
+    ).toMatchInlineSnapshot(`
+    "const { ssrRenderPortal: _ssrRenderPortal } = require(\\"@vue/server-renderer\\")
+
+    return function ssrRender(_ctx, _push, _parent) {
+      _ssrRenderPortal(_push, (_push) => {
+        _push(\`<div></div>\`)
+      }, _ctx.target, _ctx.foo, _parent)
+    }"
+  `)
+  })
 })
index 8c7fa063b6cc6153dd405286b78c8a6f80ab7741..e67a585218490be5d5e6929fa646c30e4c3d7560 100644 (file)
@@ -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`
     ])
   )
index 370b43bb0e62fc127d92efe9f4aa089e79ab67be..85ca6ef1f65c7b155808865e9907111927c20665 100644 (file)
@@ -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
 
index 45314c2b464d2d4aac37499085d0fa5e93c7bee3..0a095e0720736daf4b786d435093f7255a06861d 100644 (file)
@@ -17,16 +17,42 @@ describe('ssrRenderPortal', () => {
               _push(`<div>content</div>`)
             },
             '#target',
+            false,
             _parent
           )
         }
       }),
       ctx
     )
-    expect(html).toBe('<!--portal-->')
+    expect(html).toBe('<!--portal start--><!--portal end-->')
     expect(ctx.portals!['#target']).toBe(`<div>content</div><!---->`)
   })
 
+  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(`<div>content</div>`)
+            },
+            '#target',
+            true,
+            _parent
+          )
+        }
+      }),
+      ctx
+    )
+    expect(html).toBe('<!--portal start--><div>content</div><!--portal end-->')
+    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('<!--portal-->')
+    expect(html).toBe('<!--portal start--><!--portal end-->')
     expect(ctx.portals!['#target']).toBe('<span>hello</span><!---->')
   })
 
+  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('<!--portal start--><span>hello</span><!--portal end-->')
+    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('<div><!--portal--><!--portal--></div>')
+    expect(html).toBe(
+      '<div><!--portal start--><!--portal end--><!--portal start--><!--portal end--></div>'
+    )
     expect(ctx.portals!['#target']).toBe(
       '<span>hello</span><!---->world<!---->'
     )
index 3e54d999ac1eacbc1d5eb70ebda22c28fd2417f8..2f694cbdf771e7e83b36dd4c0f7f7daea57867b1 100644 (file)
@@ -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('<!--portal-->')
-  const { getBuffer, push } = createBuffer()
-  contentRenderFn(push)
-  push(`<!---->`) // portal end anchor
+  parentPush('<!--portal start-->')
+
+  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('<!--portal end-->')
 }
index 14666678da3b78ca59b64432017bb3bae94c59ce..5c84ae7aec70a52c10a3e4e222e19a375e369fbd 100644 (file)
@@ -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
   )
 }