Bladeren bron

feat(compiler-core): support <portal> in template (#203)

terencez 6 jaren geleden
bovenliggende
commit
4547d85a38

+ 48 - 1
packages/compiler-core/__tests__/transforms/transformElement.spec.ts

@@ -6,7 +6,8 @@ import {
   RESOLVE_DIRECTIVE,
   APPLY_DIRECTIVES,
   TO_HANDLERS,
-  helperNameMap
+  helperNameMap,
+  PORTAL
 } from '../../src/runtimeHelpers'
 import {
   CallExpression,
@@ -255,6 +256,52 @@ describe('compiler: element transform', () => {
     ])
   })
 
+  test('should handle <portal> element', () => {
+    const { node } = parseWithElementTransform(
+      `<portal target="#foo"><span /></portal>`
+    )
+    expect(node.callee).toBe(CREATE_VNODE)
+    expect(node.arguments).toMatchObject([
+      PORTAL,
+      createObjectMatcher({
+        target: '#foo'
+      }),
+      [
+        {
+          type: NodeTypes.ELEMENT,
+          tag: 'span',
+          codegenNode: {
+            callee: CREATE_VNODE,
+            arguments: [`"span"`]
+          }
+        }
+      ]
+    ])
+  })
+
+  test('should handle <Portal> element', () => {
+    const { node } = parseWithElementTransform(
+      `<Portal target="#foo"><span /></Portal>`
+    )
+    expect(node.callee).toBe(CREATE_VNODE)
+    expect(node.arguments).toMatchObject([
+      PORTAL,
+      createObjectMatcher({
+        target: '#foo'
+      }),
+      [
+        {
+          type: NodeTypes.ELEMENT,
+          tag: 'span',
+          codegenNode: {
+            callee: CREATE_VNODE,
+            arguments: [`"span"`]
+          }
+        }
+      ]
+    ])
+  })
+
   test('error on v-bind with no argument', () => {
     const onError = jest.fn()
     parseWithElementTransform(`<div v-bind/>`, { onError })

+ 8 - 1
packages/compiler-core/src/ast.ts

@@ -49,7 +49,8 @@ export const enum ElementTypes {
   ELEMENT,
   COMPONENT,
   SLOT,
-  TEMPLATE
+  TEMPLATE,
+  PORTAL
 }
 
 export interface Node {
@@ -99,6 +100,7 @@ export type ElementNode =
   | ComponentNode
   | SlotOutletNode
   | TemplateNode
+  | PortalNode
 
 export interface BaseElementNode extends Node {
   type: NodeTypes.ELEMENT
@@ -134,6 +136,11 @@ export interface TemplateNode extends BaseElementNode {
     | undefined
 }
 
+export interface PortalNode extends BaseElementNode {
+  tagType: ElementTypes.PORTAL
+  codegenNode: ElementCodegenNode | undefined
+}
+
 export interface TextNode extends Node {
   type: NodeTypes.TEXT
   content: string

+ 1 - 0
packages/compiler-core/src/parse.ts

@@ -445,6 +445,7 @@ function parseTag(
 
     if (tag === 'slot') tagType = ElementTypes.SLOT
     else if (tag === 'template') tagType = ElementTypes.TEMPLATE
+    else if (tag === 'portal' || tag === 'Portal') tagType = ElementTypes.PORTAL
   }
 
   return {

+ 9 - 2
packages/compiler-core/src/transforms/transformElement.ts

@@ -23,7 +23,8 @@ import {
   RESOLVE_DIRECTIVE,
   RESOLVE_COMPONENT,
   MERGE_PROPS,
-  TO_HANDLERS
+  TO_HANDLERS,
+  PORTAL
 } from '../runtimeHelpers'
 import { getInnerRange, isVSlot, toValidAssetId } from '../utils'
 import { buildSlots } from './vSlot'
@@ -38,6 +39,7 @@ export const transformElement: NodeTransform = (node, context) => {
     if (
       node.tagType === ElementTypes.ELEMENT ||
       node.tagType === ElementTypes.COMPONENT ||
+      node.tagType === ElementTypes.PORTAL ||
       // <template> with v-if or v-for are ignored during traversal.
       // <template> without v-slot should be treated as a normal element.
       (node.tagType === ElementTypes.TEMPLATE && !node.props.some(isVSlot))
@@ -46,6 +48,7 @@ export const transformElement: NodeTransform = (node, context) => {
       // processed and merged.
       return () => {
         const isComponent = node.tagType === ElementTypes.COMPONENT
+        const isPortal = node.tagType === ElementTypes.PORTAL
         let hasProps = node.props.length > 0
         let patchFlag: number = 0
         let runtimeDirectives: DirectiveNode[] | undefined
@@ -57,7 +60,11 @@ export const transformElement: NodeTransform = (node, context) => {
         }
 
         const args: CallExpression['arguments'] = [
-          isComponent ? toValidAssetId(node.tag, `component`) : `"${node.tag}"`
+          isComponent
+            ? toValidAssetId(node.tag, `component`)
+            : isPortal
+              ? context.helper(PORTAL)
+              : `"${node.tag}"`
         ]
         // props
         if (hasProps) {