APPLY_DIRECTIVES,
TO_HANDLERS,
helperNameMap,
- PORTAL
+ PORTAL,
+ RESOLVE_DYNAMIC_COMPONENT
} from '../../src/runtimeHelpers'
import {
CallExpression,
}
}
+function parseWithBind(template: string) {
+ return parseWithElementTransform(template, {
+ directiveTransforms: {
+ bind: transformBind
+ }
+ })
+}
+
describe('compiler: element transform', () => {
test('import + resolve component', () => {
const { root } = parseWithElementTransform(`<Foo/>`)
})
describe('patchFlag analysis', () => {
- function parseWithBind(template: string) {
- return parseWithElementTransform(template, {
- directiveTransforms: {
- bind: transformBind
- }
- })
- }
-
test('TEXT', () => {
const { node } = parseWithBind(`<div>foo</div>`)
expect(node.arguments.length).toBe(3)
expect(vnodeCall.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
})
})
+
+ describe('dynamic component', () => {
+ test('static binding', () => {
+ const { node, root } = parseWithBind(`<component is="foo" />`)
+ expect(root.helpers).not.toContain(RESOLVE_DYNAMIC_COMPONENT)
+ expect(node).toMatchObject({
+ callee: CREATE_VNODE,
+ arguments: ['_component_foo']
+ })
+ })
+
+ test('dynamic binding', () => {
+ const { node, root } = parseWithBind(`<component :is="foo" />`)
+ expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
+ expect(node.arguments).toMatchObject([
+ {
+ callee: RESOLVE_DYNAMIC_COMPONENT,
+ arguments: [
+ {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'foo'
+ }
+ ]
+ }
+ ])
+ })
+ })
})
export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``)
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
+export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
+ __DEV__ ? `resolveDynamicComponent` : ``
+)
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
export const APPLY_DIRECTIVES = Symbol(__DEV__ ? `applyDirectives` : ``)
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
[CREATE_BLOCK]: `createBlock`,
[CREATE_VNODE]: `createVNode`,
[RESOLVE_COMPONENT]: `resolveComponent`,
+ [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
[RESOLVE_DIRECTIVE]: `resolveDirective`,
[APPLY_DIRECTIVES]: `applyDirectives`,
[RENDER_LIST]: `renderList`,
APPLY_DIRECTIVES,
RESOLVE_DIRECTIVE,
RESOLVE_COMPONENT,
+ RESOLVE_DYNAMIC_COMPONENT,
MERGE_PROPS,
TO_HANDLERS,
PORTAL,
SUSPENSE
} from '../runtimeHelpers'
-import { getInnerRange, isVSlot, toValidAssetId } from '../utils'
+import { getInnerRange, isVSlot, toValidAssetId, findProp } from '../utils'
import { buildSlots } from './vSlot'
import { isStaticNode } from './hoistStatic'
let patchFlag: number = 0
let runtimeDirectives: DirectiveNode[] | undefined
let dynamicPropNames: string[] | undefined
+ let dynamicComponent: string | CallExpression | undefined
- if (isComponent) {
+ // handle dynamic component
+ const isProp = findProp(node, 'is')
+ if (node.tag === 'component') {
+ if (isProp) {
+ // static <component is="foo" />
+ if (isProp.type === NodeTypes.ATTRIBUTE) {
+ const tag = isProp.value && isProp.value.content
+ if (tag) {
+ context.helper(RESOLVE_COMPONENT)
+ context.components.add(tag)
+ dynamicComponent = toValidAssetId(tag, `component`)
+ }
+ }
+ // dynamic <component :is="asdf" />
+ else if (isProp.exp) {
+ dynamicComponent = createCallExpression(
+ context.helper(RESOLVE_DYNAMIC_COMPONENT),
+ [isProp.exp]
+ )
+ }
+ }
+ }
+
+ if (isComponent && !dynamicComponent) {
context.helper(RESOLVE_COMPONENT)
context.components.add(node.tag)
}
const args: CallExpression['arguments'] = [
- isComponent
- ? toValidAssetId(node.tag, `component`)
- : node.tagType === ElementTypes.PORTAL
- ? context.helper(PORTAL)
- : node.tagType === ElementTypes.SUSPENSE
- ? context.helper(SUSPENSE)
- : `"${node.tag}"`
+ dynamicComponent
+ ? dynamicComponent
+ : isComponent
+ ? toValidAssetId(node.tag, `component`)
+ : node.tagType === ElementTypes.PORTAL
+ ? context.helper(PORTAL)
+ : node.tagType === ElementTypes.SUSPENSE
+ ? context.helper(SUSPENSE)
+ : `"${node.tag}"`
]
// props
if (hasProps) {
- const propsBuildResult = buildProps(node, context)
+ const propsBuildResult = buildProps(
+ node,
+ context,
+ // skip reserved "is" prop <component is>
+ node.props.filter(p => p !== isProp)
+ )
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
runtimeDirectives = propsBuildResult.directives
resolveComponent,
resolveDirective,
Component,
- Directive
+ Directive,
+ resolveDynamicComponent
} from '@vue/runtime-test'
describe('resolveAssets', () => {
expect('Failed to resolve component: foo').toHaveBeenWarned()
expect('Failed to resolve directive: bar').toHaveBeenWarned()
})
+
+ test('resolve dynamic component', () => {
+ const app = createApp()
+ const dynamicComponents = {
+ foo: () => 'foo',
+ bar: () => 'bar',
+ baz: { render: () => 'baz' }
+ }
+ let foo, bar, baz // dynamic components
+ const Root = {
+ components: { foo: dynamicComponents.foo },
+ setup() {
+ return () => {
+ foo = resolveDynamicComponent('foo') // <component is="foo"/>
+ bar = resolveDynamicComponent(dynamicComponents.bar) // <component :is="bar"/>, function
+ baz = resolveDynamicComponent(dynamicComponents.baz) // <component :is="baz"/>, object
+ }
+ }
+ }
+ const root = nodeOps.createElement('div')
+ app.mount(Root, root)
+ expect(foo).toBe(dynamicComponents.foo)
+ expect(bar).toBe(dynamicComponents.bar)
+ expect(baz).toBe(dynamicComponents.baz)
+ })
})
})
import { currentRenderingInstance } from '../componentRenderUtils'
import { currentInstance, Component } from '../component'
import { Directive } from '../directives'
-import { camelize, capitalize } from '@vue/shared'
+import {
+ camelize,
+ capitalize,
+ isString,
+ isObject,
+ isFunction
+} from '@vue/shared'
import { warn } from '../warning'
export function resolveComponent(name: string): Component | undefined {
return resolveAsset('components', name)
}
+export function resolveDynamicComponent(
+ component: unknown
+): Component | undefined {
+ if (!component) return
+ if (isString(component)) {
+ return resolveAsset('components', component)
+ } else if (isFunction(component) || isObject(component)) {
+ return component
+ }
+}
+
export function resolveDirective(name: string): Directive | undefined {
return resolveAsset('directives', name)
}
// Internal, for compiler generated code
// should sync with '@vue/compiler-core/src/runtimeConstants.ts'
export { applyDirectives } from './directives'
-export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
+export {
+ resolveComponent,
+ resolveDirective,
+ resolveDynamicComponent
+} from './helpers/resolveAssets'
export { renderList } from './helpers/renderList'
export { toString } from './helpers/toString'
export { toHandlers } from './helpers/toHandlers'