TO_HANDLERS,
helperNameMap,
PORTAL,
- RESOLVE_DYNAMIC_COMPONENT
+ RESOLVE_DYNAMIC_COMPONENT,
+ SUSPENSE,
+ KEEP_ALIVE
} from '../../src/runtimeHelpers'
import {
CallExpression,
])
})
- 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> with normal children', () => {
+ function assert(tag: string) {
+ const { root, node } = parseWithElementTransform(
+ `<${tag} target="#foo"><span /></${tag}>`
+ )
+ expect(root.components.length).toBe(0)
+ expect(root.helpers).toContain(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"`]
+ }
}
- }
- ]
- ])
+ ]
+ ])
+ }
+
+ assert(`portal`)
+ assert(`Portal`)
})
- test('should handle <Portal> element', () => {
- const { node } = parseWithElementTransform(
- `<Portal target="#foo"><span /></Portal>`
+ test('should handle <Suspense>', () => {
+ function assert(tag: string, content: string, hasFallback?: boolean) {
+ const { root, node } = parseWithElementTransform(
+ `<${tag}>${content}</${tag}>`
+ )
+ expect(root.components.length).toBe(0)
+ expect(root.helpers).toContain(SUSPENSE)
+ expect(node.callee).toBe(CREATE_VNODE)
+ expect(node.arguments).toMatchObject([
+ SUSPENSE,
+ `null`,
+ hasFallback
+ ? createObjectMatcher({
+ default: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION
+ },
+ fallback: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION
+ },
+ _compiled: `[true]`
+ })
+ : createObjectMatcher({
+ default: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION
+ },
+ _compiled: `[true]`
+ })
+ ])
+ }
+
+ assert(`suspense`, `foo`)
+ assert(`suspense`, `<template #default>foo</template>`)
+ assert(
+ `suspense`,
+ `<template #default>foo</template><template #fallback>fallback</template>`,
+ true
)
- 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 <KeepAlive>', () => {
+ function assert(tag: string) {
+ const { root, node } = parseWithElementTransform(
+ `<${tag}><span /></${tag}>`
+ )
+ expect(root.components.length).toBe(0)
+ expect(root.helpers).toContain(KEEP_ALIVE)
+ expect(node.callee).toBe(CREATE_VNODE)
+ expect(node.arguments).toMatchObject([
+ KEEP_ALIVE,
+ `null`,
+ createObjectMatcher({
+ default: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION
+ },
+ _compiled: `[true]`
+ })
+ ])
+ }
+
+ assert(`keep-alive`)
+ assert(`KeepAlive`)
})
test('error on v-bind with no argument', () => {
ELEMENT,
COMPONENT,
SLOT,
- TEMPLATE,
- PORTAL,
- SUSPENSE
+ TEMPLATE
}
export interface Node {
| ComponentNode
| SlotOutletNode
| TemplateNode
- | PortalNode
- | SuspenseNode
export interface BaseElementNode extends Node {
type: NodeTypes.ELEMENT
codegenNode: ElementCodegenNode | undefined | CacheExpression
}
-export interface PortalNode extends BaseElementNode {
- tagType: ElementTypes.PORTAL
- codegenNode: ElementCodegenNode | undefined | CacheExpression
-}
-
-export interface SuspenseNode extends BaseElementNode {
- tagType: ElementTypes.SUSPENSE
- codegenNode: ElementCodegenNode | undefined | CacheExpression
-}
-
export interface TextNode extends Node {
type: NodeTypes.TEXT
content: string
-import { NO } from '@vue/shared'
+import { NO, makeMap } from '@vue/shared'
import {
ErrorCodes,
createCompilerError,
} from './ast'
import { extend } from '@vue/shared'
+// Portal and Fragment are native types, not components
+const isBuiltInComponent = /*#__PURE__*/ makeMap(
+ `suspense,keep-alive,keepalive,transition`,
+ true
+)
+
export interface ParserOptions {
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
isNativeTag?: (tag: string) => boolean // e.g. loading-indicator in weex
if (!context.inPre && !context.options.isCustomElement(tag)) {
if (context.options.isNativeTag) {
if (!context.options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
- } else {
- if (/^[A-Z]/.test(tag)) tagType = ElementTypes.COMPONENT
+ } else if (isBuiltInComponent(tag) || /^[A-Z]/.test(tag)) {
+ tagType = ElementTypes.COMPONENT
}
- if (tag === 'slot') tagType = ElementTypes.SLOT
- else if (tag === 'template') tagType = ElementTypes.TEMPLATE
- else if (tag === 'portal' || tag === 'Portal') tagType = ElementTypes.PORTAL
- else if (tag === 'suspense' || tag === 'Suspense')
- tagType = ElementTypes.SUSPENSE
+ if (tag === 'slot') {
+ tagType = ElementTypes.SLOT
+ } else if (tag === 'template') {
+ tagType = ElementTypes.TEMPLATE
+ }
}
return {
export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``)
export const PORTAL = Symbol(__DEV__ ? `Portal` : ``)
export const SUSPENSE = Symbol(__DEV__ ? `Suspense` : ``)
+export const KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``)
export const OPEN_BLOCK = Symbol(__DEV__ ? `openBlock` : ``)
export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``)
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
[FRAGMENT]: `Fragment`,
[PORTAL]: `Portal`,
[SUSPENSE]: `Suspense`,
+ [KEEP_ALIVE]: `KeepAlive`,
[OPEN_BLOCK]: `openBlock`,
[CREATE_BLOCK]: `createBlock`,
[CREATE_VNODE]: `createVNode`,
MERGE_PROPS,
TO_HANDLERS,
PORTAL,
- SUSPENSE
+ SUSPENSE,
+ KEEP_ALIVE
} from '../runtimeHelpers'
import { getInnerRange, isVSlot, toValidAssetId, findProp } from '../utils'
import { buildSlots } from './vSlot'
// perform the work on exit, after all child expressions have been
// processed and merged.
return () => {
- const isComponent = node.tagType === ElementTypes.COMPONENT
- let hasProps = node.props.length > 0
+ const { tag, tagType, props } = node
+ const isPortal = tag === 'portal' || tag === 'Portal'
+ const isSuspense = tag === 'suspense' || tag === 'Suspense'
+ const isKeepAlive = tag === 'keep-alive' || tag === 'KeepAlive'
+ const isComponent = tagType === ElementTypes.COMPONENT
+
+ let hasProps = props.length > 0
let patchFlag: number = 0
let runtimeDirectives: DirectiveNode[] | undefined
let dynamicPropNames: string[] | undefined
// handle dynamic component
const isProp = findProp(node, 'is')
- if (node.tag === 'component') {
+ if (tag === 'component') {
if (isProp) {
// static <component is="foo" />
if (isProp.type === NodeTypes.ATTRIBUTE) {
}
}
- if (isComponent && !dynamicComponent) {
+ 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 (isComponent) {
+ // user component w/ resolve
context.helper(RESOLVE_COMPONENT)
- context.components.add(node.tag)
+ context.components.add(tag)
+ nodeType = toValidAssetId(tag, `component`)
+ } else {
+ // plain element
+ nodeType = `"${node.tag}"`
}
- const args: CallExpression['arguments'] = [
- dynamicComponent
- ? dynamicComponent
- : isComponent
- ? toValidAssetId(node.tag, `component`)
- : node.tagType === ElementTypes.PORTAL
- ? context.helper(PORTAL)
- : node.tagType === ElementTypes.SUSPENSE
- ? context.helper(SUSPENSE)
- : `"${node.tag}"`
- ]
+ const args: CallExpression['arguments'] = [nodeType]
// props
if (hasProps) {
const propsBuildResult = buildProps(
if (!hasProps) {
args.push(`null`)
}
- if (isComponent || node.tagType === ElementTypes.SUSPENSE) {
+ // Portal should have normal children instead of slots
+ if (isComponent && !isPortal) {
const { slots, hasDynamicSlots } = buildSlots(node, context)
args.push(slots)
if (hasDynamicSlots) {