From: 宋铄运 Date: Fri, 18 Oct 2019 16:09:04 +0000 (+0800) Subject: feat(core): support dynamic component via (#320) X-Git-Tag: v3.0.0-alpha.0~382 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7f23eaf661555df64464a0f41fc1d77b3d1addde;p=thirdparty%2Fvuejs%2Fcore.git feat(core): support dynamic component via (#320) --- diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 8f803640a8..6bda8342bd 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -7,7 +7,8 @@ import { APPLY_DIRECTIVES, TO_HANDLERS, helperNameMap, - PORTAL + PORTAL, + RESOLVE_DYNAMIC_COMPONENT } from '../../src/runtimeHelpers' import { CallExpression, @@ -47,6 +48,14 @@ function parseWithElementTransform( } } +function parseWithBind(template: string) { + return parseWithElementTransform(template, { + directiveTransforms: { + bind: transformBind + } + }) +} + describe('compiler: element transform', () => { test('import + resolve component', () => { const { root } = parseWithElementTransform(``) @@ -626,14 +635,6 @@ describe('compiler: element transform', () => { }) describe('patchFlag analysis', () => { - function parseWithBind(template: string) { - return parseWithElementTransform(template, { - directiveTransforms: { - bind: transformBind - } - }) - } - test('TEXT', () => { const { node } = parseWithBind(`
foo
`) expect(node.arguments.length).toBe(3) @@ -717,4 +718,31 @@ describe('compiler: element transform', () => { expect(vnodeCall.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH)) }) }) + + describe('dynamic component', () => { + test('static binding', () => { + const { node, root } = parseWithBind(``) + expect(root.helpers).not.toContain(RESOLVE_DYNAMIC_COMPONENT) + expect(node).toMatchObject({ + callee: CREATE_VNODE, + arguments: ['_component_foo'] + }) + }) + + test('dynamic binding', () => { + const { node, root } = parseWithBind(``) + expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT) + expect(node.arguments).toMatchObject([ + { + callee: RESOLVE_DYNAMIC_COMPONENT, + arguments: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'foo' + } + ] + } + ]) + }) + }) }) diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts index 911a7ebbb2..8993d06749 100644 --- a/packages/compiler-core/src/runtimeHelpers.ts +++ b/packages/compiler-core/src/runtimeHelpers.ts @@ -7,6 +7,9 @@ export const OPEN_BLOCK = Symbol(__DEV__ ? `openBlock` : ``) 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` : ``) @@ -30,6 +33,7 @@ export const helperNameMap: any = { [CREATE_BLOCK]: `createBlock`, [CREATE_VNODE]: `createVNode`, [RESOLVE_COMPONENT]: `resolveComponent`, + [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`, [RESOLVE_DIRECTIVE]: `resolveDirective`, [APPLY_DIRECTIVES]: `applyDirectives`, [RENDER_LIST]: `renderList`, diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 74ef7f55b5..88838886dd 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -22,12 +22,13 @@ import { 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' @@ -55,24 +56,55 @@ export const transformElement: NodeTransform = (node, context) => { 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 + 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 + 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 + node.props.filter(p => p !== isProp) + ) patchFlag = propsBuildResult.patchFlag dynamicPropNames = propsBuildResult.dynamicPropNames runtimeDirectives = propsBuildResult.directives diff --git a/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts b/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts index 77d6162129..7431c6011a 100644 --- a/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts +++ b/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts @@ -5,7 +5,8 @@ import { resolveComponent, resolveDirective, Component, - Directive + Directive, + resolveDynamicComponent } from '@vue/runtime-test' describe('resolveAssets', () => { @@ -90,5 +91,30 @@ 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') // + bar = resolveDynamicComponent(dynamicComponents.bar) // , function + baz = resolveDynamicComponent(dynamicComponents.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) + }) }) }) diff --git a/packages/runtime-core/src/helpers/resolveAssets.ts b/packages/runtime-core/src/helpers/resolveAssets.ts index b129d552f6..91283f0f60 100644 --- a/packages/runtime-core/src/helpers/resolveAssets.ts +++ b/packages/runtime-core/src/helpers/resolveAssets.ts @@ -1,13 +1,30 @@ 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) } diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 3e4abf82b1..75ed1765db 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -39,7 +39,11 @@ export { // 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'