diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 69c4750a..a1867549 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -1,4 +1,4 @@ -import { NO, makeMap, isArray } from '@vue/shared' +import { NO, isArray } from '@vue/shared' import { ErrorCodes, createCompilerError, @@ -8,7 +8,8 @@ import { import { assert, advancePositionWithMutation, - advancePositionWithClone + advancePositionWithClone, + isCoreComponent } from './utils' import { Namespace, @@ -29,22 +30,12 @@ import { } from './ast' import { extend } from '@vue/shared' -// Portal and Fragment are native types, not components -const isBuiltInComponent = /*#__PURE__*/ makeMap( - `suspense,keep-alive,base-transition`, - true -) - export interface ParserOptions { isVoidTag?: (tag: string) => boolean // e.g. img, br, hr isNativeTag?: (tag: string) => boolean // e.g. loading-indicator in weex isPreTag?: (tag: string) => boolean // e.g.
 where whitespace is intact
   isCustomElement?: (tag: string) => boolean
-  // for importing platform-specific components from the runtime.
-  // e.g.  for runtime-dom
-  // However this is only needed if isNativeTag is not specified, since when
-  // isNativeTag is specified anything that is not native is a component.
-  isBuiltInComponent?: (tag: string) => boolean
+  isBuiltInComponent?: (tag: string) => symbol | void
   getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
   getTextMode?: (tag: string, ns: Namespace) => TextModes
   delimiters?: [string, string] // ['{{', '}}']
@@ -483,7 +474,7 @@ function parseTag(
     if (options.isNativeTag) {
       if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
     } else if (
-      isBuiltInComponent(tag) ||
+      isCoreComponent(tag) ||
       (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
       /^[A-Z]/.test(tag)
     ) {
diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts
index 88125d32..bfc69ee4 100644
--- a/packages/compiler-core/src/transform.ts
+++ b/packages/compiler-core/src/transform.ts
@@ -17,7 +17,7 @@ import {
   CacheExpression,
   createCacheExpression
 } from './ast'
-import { isString, isArray } from '@vue/shared'
+import { isString, isArray, NOOP } from '@vue/shared'
 import { CompilerError, defaultOnError } from './errors'
 import {
   TO_STRING,
@@ -68,6 +68,7 @@ export type StructuralDirectiveTransform = (
 export interface TransformOptions {
   nodeTransforms?: NodeTransform[]
   directiveTransforms?: { [name: string]: DirectiveTransform }
+  isBuiltInComponent?: (tag: string) => symbol | void
   prefixIdentifiers?: boolean
   hoistStatic?: boolean
   cacheHandlers?: boolean
@@ -110,6 +111,7 @@ function createTransformContext(
     cacheHandlers = false,
     nodeTransforms = [],
     directiveTransforms = {},
+    isBuiltInComponent = NOOP,
     onError = defaultOnError
   }: TransformOptions
 ): TransformContext {
@@ -132,6 +134,7 @@ function createTransformContext(
     cacheHandlers,
     nodeTransforms,
     directiveTransforms,
+    isBuiltInComponent,
     onError,
     parent: null,
     currentNode: root,
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index f1b7f6d5..2d09bf56 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -15,7 +15,7 @@ import {
   createObjectExpression,
   Property
 } from '../ast'
-import { PatchFlags, PatchFlagNames, isSymbol, hyphenate } from '@vue/shared'
+import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
 import { createCompilerError, ErrorCodes } from '../errors'
 import {
   CREATE_VNODE,
@@ -26,11 +26,15 @@ import {
   MERGE_PROPS,
   TO_HANDLERS,
   PORTAL,
-  SUSPENSE,
-  KEEP_ALIVE,
-  BASE_TRANSITION
+  KEEP_ALIVE
 } from '../runtimeHelpers'
-import { getInnerRange, isVSlot, toValidAssetId, findProp } from '../utils'
+import {
+  getInnerRange,
+  isVSlot,
+  toValidAssetId,
+  findProp,
+  isCoreComponent
+} from '../utils'
 import { buildSlots } from './vSlot'
 import { isStaticNode } from './hoistStatic'
 
@@ -38,9 +42,6 @@ import { isStaticNode } from './hoistStatic'
 // import, which should be used instead of a resolveDirective call.
 const directiveImportMap = new WeakMap()
 
-const isBuiltInType = (tag: string, expected: string): boolean =>
-  tag === expected || tag === hyphenate(expected)
-
 // generate a JavaScript AST for this element's codegen
 export const transformElement: NodeTransform = (node, context) => {
   if (
@@ -57,10 +58,8 @@ export const transformElement: NodeTransform = (node, context) => {
   // processed and merged.
   return function postTransformElement() {
     const { tag, tagType, props } = node
-    const isPortal = isBuiltInType(tag, 'Portal')
-    const isSuspense = isBuiltInType(tag, 'Suspense')
-    const isKeepAlive = isBuiltInType(tag, 'KeepAlive')
-    const isBaseTransition = isBuiltInType(tag, 'BaseTransition')
+    const builtInComponentSymbol =
+      isCoreComponent(tag) || context.isBuiltInComponent(tag)
     const isComponent = tagType === ElementTypes.COMPONENT
 
     let hasProps = props.length > 0
@@ -96,14 +95,8 @@ export const transformElement: NodeTransform = (node, context) => {
     let nodeType
     if (dynamicComponent) {
       nodeType = dynamicComponent
-    } else if (isPortal) {
-      nodeType = context.helper(PORTAL)
-    } else if (isSuspense) {
-      nodeType = context.helper(SUSPENSE)
-    } else if (isKeepAlive) {
-      nodeType = context.helper(KEEP_ALIVE)
-    } else if (isBaseTransition) {
-      nodeType = context.helper(BASE_TRANSITION)
+    } else if (builtInComponentSymbol) {
+      nodeType = context.helper(builtInComponentSymbol)
     } else if (isComponent) {
       // user component w/ resolve
       context.helper(RESOLVE_COMPONENT)
@@ -142,7 +135,11 @@ export const transformElement: NodeTransform = (node, context) => {
       // Portal is not a real component has dedicated handling in the renderer
       // KeepAlive should not track its own deps so that it can be used inside
       // Transition
-      if (isComponent && !isPortal && !isKeepAlive) {
+      if (
+        isComponent &&
+        builtInComponentSymbol !== PORTAL &&
+        builtInComponentSymbol !== KEEP_ALIVE
+      ) {
         const { slots, hasDynamicSlots } = buildSlots(node, context)
         args.push(slots)
         if (hasDynamicSlots) {
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index aff8437e..0c922304 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -27,8 +27,31 @@ import {
 import { parse } from 'acorn'
 import { walk } from 'estree-walker'
 import { TransformContext } from './transform'
-import { OPEN_BLOCK, MERGE_PROPS, RENDER_SLOT } from './runtimeHelpers'
-import { isString, isFunction, isObject } from '@vue/shared'
+import {
+  OPEN_BLOCK,
+  MERGE_PROPS,
+  RENDER_SLOT,
+  PORTAL,
+  SUSPENSE,
+  KEEP_ALIVE,
+  BASE_TRANSITION
+} from './runtimeHelpers'
+import { isString, isFunction, isObject, hyphenate } from '@vue/shared'
+
+export const isBuiltInType = (tag: string, expected: string): boolean =>
+  tag === expected || tag === hyphenate(expected)
+
+export function isCoreComponent(tag: string): symbol | void {
+  if (isBuiltInType(tag, 'Portal')) {
+    return PORTAL
+  } else if (isBuiltInType(tag, 'Suspense')) {
+    return SUSPENSE
+  } else if (isBuiltInType(tag, 'KeepAlive')) {
+    return KEEP_ALIVE
+  } else if (isBuiltInType(tag, 'BaseTransition')) {
+    return BASE_TRANSITION
+  }
+}
 
 // cache node requires
 // lazy require dependencies so that they don't end up in rollup's dep graph
diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts
index e9b398ec..0c42dfee 100644
--- a/packages/compiler-dom/src/index.ts
+++ b/packages/compiler-dom/src/index.ts
@@ -1,4 +1,9 @@
-import { baseCompile, CompilerOptions, CodegenResult } from '@vue/compiler-core'
+import {
+  baseCompile,
+  CompilerOptions,
+  CodegenResult,
+  isBuiltInType
+} from '@vue/compiler-core'
 import { parserOptionsMinimal } from './parserOptionsMinimal'
 import { parserOptionsStandard } from './parserOptionsStandard'
 import { transformStyle } from './transforms/transformStyle'
@@ -8,6 +13,7 @@ import { transformVText } from './transforms/vText'
 import { transformModel } from './transforms/vModel'
 import { transformOn } from './transforms/vOn'
 import { transformShow } from './transforms/vShow'
+import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
 
 export function compile(
   template: string,
@@ -25,6 +31,13 @@ export function compile(
       on: transformOn,
       show: transformShow,
       ...(options.directiveTransforms || {})
+    },
+    isBuiltInComponent: tag => {
+      if (isBuiltInType(tag, `Transition`)) {
+        return TRANSITION
+      } else if (isBuiltInType(tag, `TransitionGroup`)) {
+        return TRANSITION_GROUP
+      }
     }
   })
 }
diff --git a/packages/compiler-dom/src/runtimeHelpers.ts b/packages/compiler-dom/src/runtimeHelpers.ts
index 7b61856e..19c595ef 100644
--- a/packages/compiler-dom/src/runtimeHelpers.ts
+++ b/packages/compiler-dom/src/runtimeHelpers.ts
@@ -11,6 +11,9 @@ export const V_ON_WITH_KEYS = Symbol(__DEV__ ? `vOnKeysGuard` : ``)
 
 export const V_SHOW = Symbol(__DEV__ ? `vShow` : ``)
 
+export const TRANSITION = Symbol(__DEV__ ? `Transition` : ``)
+export const TRANSITION_GROUP = Symbol(__DEV__ ? `TransitionGroup` : ``)
+
 registerRuntimeHelpers({
   [V_MODEL_RADIO]: `vModelRadio`,
   [V_MODEL_CHECKBOX]: `vModelCheckbox`,
@@ -19,5 +22,7 @@ registerRuntimeHelpers({
   [V_MODEL_DYNAMIC]: `vModelDynamic`,
   [V_ON_WITH_MODIFIERS]: `withModifiers`,
   [V_ON_WITH_KEYS]: `withKeys`,
-  [V_SHOW]: `vShow`
+  [V_SHOW]: `vShow`,
+  [TRANSITION]: `Transition`,
+  [TRANSITION_GROUP]: `TransitionGroup`
 })
diff --git a/packages/runtime-dom/src/components/TransitionGroup.ts b/packages/runtime-dom/src/components/TransitionGroup.ts
index cc5d8f2a..9646b327 100644
--- a/packages/runtime-dom/src/components/TransitionGroup.ts
+++ b/packages/runtime-dom/src/components/TransitionGroup.ts
@@ -95,6 +95,11 @@ export const TransitionGroup = {
       prevChildren = children
       children = slots.default ? slots.default() : []
 
+      // handle fragment children case, e.g. v-for
+      if (children.length === 1 && children[0].type === Fragment) {
+        children = children[0].children as VNode[]
+      }
+
       for (let i = 0; i < children.length; i++) {
         const child = children[i]
         if (child.key != null) {