]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-ssr): compile portal (#775)
authorDmitry Sharshakov <d3dx12.xx@gmail.com>
Wed, 26 Feb 2020 19:59:53 +0000 (22:59 +0300)
committerGitHub <noreply@github.com>
Wed, 26 Feb 2020 19:59:53 +0000 (14:59 -0500)
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
packages/compiler-ssr/src/errors.ts
packages/compiler-ssr/src/runtimeHelpers.ts
packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
packages/server-renderer/__tests__/ssrRenderPortal.spec.ts [new file with mode: 0644]
packages/server-renderer/src/helpers/ssrRenderPortal.ts [new file with mode: 0644]
packages/server-renderer/src/index.ts
packages/server-renderer/src/renderToString.ts

index 0cca3cefee215e09ed95eac7f30fc7b3ceed3f73..8efe67a5c9a88c9cfe710517253935316a3dbc42 100644 (file)
@@ -309,5 +309,18 @@ describe('ssr: components', () => {
         }"
       `)
     })
+
+    test('portal rendering', () => {
+      expect(compile(`<portal :target="target"><div/></portal>`).code)
+        .toMatchInlineSnapshot(`
+        "const { ssrRenderPortal: _ssrRenderPortal } = require(\\"@vue/server-renderer\\")
+
+        return function ssrRender(_ctx, _push, _parent) {
+          _ssrRenderPortal((_push) => {
+            _push(\`<div></div>\`)
+          }, _ctx.target, _parent)
+        }"
+      `)
+    })
   })
 })
index 30431fb0818142f6ca9d7787e443e915c513423d..fdc99160b01d240067cdeda9539d143eda69b131 100644 (file)
@@ -18,10 +18,12 @@ export function createSSRCompilerError(
 
 export const enum SSRErrorCodes {
   X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__,
-  X_SSR_UNSAFE_ATTR_NAME
+  X_SSR_UNSAFE_ATTR_NAME,
+  X_SSR_NO_PORTAL_TARGET
 }
 
 export const SSRErrorMessages: { [code: number]: string } = {
   [SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM]: `Custom directive is missing corresponding SSR transform and will be ignored.`,
-  [SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME]: `Unsafe attribute name for SSR.`
+  [SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME]: `Unsafe attribute name for SSR.`,
+  [SSRErrorCodes.X_SSR_NO_PORTAL_TARGET]: `No target prop on portal element.`
 }
index 2f394514de6232568176f9e85c1362b41d1a0301..01875cf05caf5860f00173dbed313227c7a57ea8 100644 (file)
@@ -13,6 +13,7 @@ export const SSR_LOOSE_EQUAL = Symbol(`ssrLooseEqual`)
 export const SSR_LOOSE_CONTAIN = Symbol(`ssrLooseContain`)
 export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
 export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
+export const SSR_RENDER_PORTAL = Symbol(`ssrRenderPortal`)
 
 export const ssrHelpers = {
   [SSR_INTERPOLATE]: `ssrInterpolate`,
@@ -27,7 +28,8 @@ export const ssrHelpers = {
   [SSR_LOOSE_EQUAL]: `ssrLooseEqual`,
   [SSR_LOOSE_CONTAIN]: `ssrLooseContain`,
   [SSR_RENDER_DYNAMIC_MODEL]: `ssrRenderDynamicModel`,
-  [SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`
+  [SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`,
+  [SSR_RENDER_PORTAL]: `ssrRenderPortal`
 }
 
 // Note: these are helpers imported from @vue/server-renderer
index a9c38c70cfaf7ca5159ff691b1ccde505c50a858..87c6988bfb8a048220ab8987176b67b1cad70a9e 100644 (file)
@@ -31,9 +31,11 @@ import {
   createTransformContext,
   traverseNode,
   ExpressionNode,
-  TemplateNode
+  TemplateNode,
+  findProp,
+  JSChildNode
 } from '@vue/compiler-dom'
-import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
+import { SSR_RENDER_COMPONENT, SSR_RENDER_PORTAL } from '../runtimeHelpers'
 import {
   SSRTransformContext,
   processChildren,
@@ -134,7 +136,34 @@ export function ssrProcessComponent(
     const component = componentTypeMap.get(node)!
 
     if (component === PORTAL) {
-      // TODO
+      const targetProp = findProp(node, 'target')
+      if (!targetProp) 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
+      } else {
+        return
+      }
+
+      const contentRenderFn = createFunctionExpression(
+        [`_push`],
+        undefined, // Body is added later
+        true, // newline
+        false, // isSlot
+        node.loc
+      )
+      contentRenderFn.body = processChildrenAsStatement(node.children, context)
+      context.pushStatement(
+        createCallExpression(context.helper(SSR_RENDER_PORTAL), [
+          contentRenderFn,
+          target,
+          `_parent`
+        ])
+      )
+
       return
     }
 
diff --git a/packages/server-renderer/__tests__/ssrRenderPortal.spec.ts b/packages/server-renderer/__tests__/ssrRenderPortal.spec.ts
new file mode 100644 (file)
index 0000000..70031dd
--- /dev/null
@@ -0,0 +1,29 @@
+import { createApp } from 'vue'
+import { renderToString, SSRContext } from '../src/renderToString'
+import { ssrRenderPortal } from '../src/helpers/ssrRenderPortal'
+
+describe('ssrRenderPortal', () => {
+  test('portal rendering', async () => {
+    const ctx = {
+      portals: {}
+    } as SSRContext
+    await renderToString(
+      createApp({
+        data() {
+          return { msg: 'hello' }
+        },
+        ssrRender(_ctx, _push, _parent) {
+          ssrRenderPortal(
+            _push => {
+              _push(`<div>content</div>`)
+            },
+            '#target',
+            _parent
+          )
+        }
+      }),
+      ctx
+    )
+    expect(ctx.portals!['#target']).toBe(`<div>content</div>`)
+  })
+})
diff --git a/packages/server-renderer/src/helpers/ssrRenderPortal.ts b/packages/server-renderer/src/helpers/ssrRenderPortal.ts
new file mode 100644 (file)
index 0000000..12c2282
--- /dev/null
@@ -0,0 +1,20 @@
+import { ComponentInternalInstance, ssrContextKey } from 'vue'
+import { SSRContext, createBuffer, PushFn } from '../renderToString'
+
+export function ssrRenderPortal(
+  contentRenderFn: (push: PushFn) => void,
+  target: string,
+  parentComponent: ComponentInternalInstance
+) {
+  const { getBuffer, push } = createBuffer()
+
+  contentRenderFn(push)
+
+  const context = parentComponent.appContext.provides[
+    ssrContextKey as any
+  ] as SSRContext
+  const portalBuffers =
+    context.__portalBuffers || (context.__portalBuffers = {})
+
+  portalBuffers[target] = getBuffer()
+}
index 59b2b45f8fba2a6a53b4576a5e4d7c23dd3bae0a..315b3ae18c71dd9e8d3d62da59d9cb6665d637b9 100644 (file)
@@ -13,6 +13,7 @@ export {
 } from './helpers/ssrRenderAttrs'
 export { ssrInterpolate } from './helpers/ssrInterpolate'
 export { ssrRenderList } from './helpers/ssrRenderList'
+export { ssrRenderPortal } from './helpers/ssrRenderPortal'
 
 // v-model helpers
 export {
index 0aac04cd14a38137d649f25806944bc141a3a726..14c041c6561485837235efaa79fd6fc87734e80f 100644 (file)
@@ -45,9 +45,12 @@ const {
 // - A resolved buffer (recursive arrays of strings that can be unrolled
 //   synchronously)
 // - An async buffer (a Promise that resolves to a resolved buffer)
-type SSRBuffer = SSRBufferItem[]
-type SSRBufferItem = string | ResolvedSSRBuffer | Promise<ResolvedSSRBuffer>
-type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
+export type SSRBuffer = SSRBufferItem[]
+export type SSRBufferItem =
+  | string
+  | ResolvedSSRBuffer
+  | Promise<ResolvedSSRBuffer>
+export type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
 
 export type PushFn = (item: SSRBufferItem) => void
 
@@ -62,7 +65,7 @@ export type SSRContext = {
   >
 }
 
-function createBuffer() {
+export function createBuffer() {
   let appendable = false
   let hasAsync = false
   const buffer: SSRBuffer = []