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) {