]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(transition): properly handle transition & transition-group in compiler
authorEvan You <yyx990803@gmail.com>
Fri, 29 Nov 2019 17:42:04 +0000 (12:42 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 29 Nov 2019 17:42:04 +0000 (12:42 -0500)
packages/compiler-core/src/parse.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/utils.ts
packages/compiler-dom/src/index.ts
packages/compiler-dom/src/runtimeHelpers.ts
packages/runtime-dom/src/components/TransitionGroup.ts

index 69c4750a47e64455f58792e1db70e2c78553d572..a18675499fd54fbed89c2ff789e180b5fc02c1b9 100644 (file)
@@ -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. <pre> where whitespace is intact
   isCustomElement?: (tag: string) => boolean
-  // for importing platform-specific components from the runtime.
-  // e.g. <transition> 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)
     ) {
index 88125d321d97685c2b451d8e85852c88bc438329..bfc69ee4f6fbf79af39ec49464242afc73c2c961 100644 (file)
@@ -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,
index f1b7f6d525e2dbbc1bc630817b6f38fca37f9693..2d09bf5681b7e6b3ce5f0d50da352a593a63e614 100644 (file)
@@ -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<DirectiveNode, symbol>()
 
-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) {
index aff8437ead6016e3b07245d6e4b0cffce3b6d0a4..0c9223045205f9042ed0179006dc7e34d66094c3 100644 (file)
@@ -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
index e9b398ecf025a68b447b97638007b91532b0a70b..0c42dfee1478e502f3f808cc6c3d44f54d4595f6 100644 (file)
@@ -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
+      }
     }
   })
 }
index 7b61856eefdf06142288b151b0b31af1612ff5ae..19c595ef69ac21495553bd92e001849542d55302 100644 (file)
@@ -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`
 })
index cc5d8f2a9e77982eb773222d6ff0ebae82fd27f7..9646b3276ad204bab1eeb2ec0ca53dcfc2eea9da 100644 (file)
@@ -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) {