diff --git a/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts b/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts index 5eee77f4d2d..5d5e895ca53 100644 --- a/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts +++ b/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts @@ -151,4 +151,62 @@ describe('resolveAssets', () => { expect(serializeInner(root)).toBe('
hello
') }) }) + + test('resolving from mixins & extends', () => { + const FooBar = () => null + const BarBaz = { mounted: () => null } + + let component1: Component | string + let component2: Component | string + let component3: Component | string + let component4: Component | string + let directive1: Directive + let directive2: Directive + let directive3: Directive + let directive4: Directive + + const Base = { + components: { + FooBar: FooBar + } + } + const Mixin = { + directives: { + BarBaz: BarBaz + } + } + + const Root = { + extends: Base, + mixins: [Mixin], + setup() { + return () => { + component1 = resolveComponent('FooBar')! + directive1 = resolveDirective('BarBaz')! + // camelize + component2 = resolveComponent('Foo-bar')! + directive2 = resolveDirective('Bar-baz')! + // capitalize + component3 = resolveComponent('fooBar')! + directive3 = resolveDirective('barBaz')! + // camelize and capitalize + component4 = resolveComponent('foo-bar')! + directive4 = resolveDirective('bar-baz')! + } + } + } + + const app = createApp(Root) + const root = nodeOps.createElement('div') + app.mount(root) + expect(component1!).toBe(FooBar) + expect(component2!).toBe(FooBar) + expect(component3!).toBe(FooBar) + expect(component4!).toBe(FooBar) + + expect(directive1!).toBe(BarBaz) + expect(directive2!).toBe(BarBaz) + expect(directive3!).toBe(BarBaz) + expect(directive4!).toBe(BarBaz) + }) }) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index c06b691b7bd..0bc928911e5 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -24,7 +24,7 @@ import { Slots, initSlots, InternalSlots } from './componentSlots' import { warn } from './warning' import { ErrorCodes, callWithErrorHandling } from './errorHandling' import { AppContext, createAppContext, AppConfig } from './apiCreateApp' -import { validateDirectiveName } from './directives' +import { Directive, validateDirectiveName } from './directives' import { applyOptions, ComponentOptions } from './componentOptions' import { EmitsOptions, @@ -221,6 +221,17 @@ export interface ComponentInternalInstance { */ renderCache: (Function | VNode)[] + /** + * Resolved component registry, only for components with mixins or extends + * @internal + */ + components: Record | null + /** + * Resolved directive registry, only for components with mixins or extends + * @internal + */ + directives: Record | null + // the rest are only for stateful components --------------------------------- /** @@ -372,6 +383,10 @@ export function createComponentInstance( accessCache: null!, renderCache: [], + // local resovled assets + components: null, + directives: null, + // state ctx: EMPTY_OBJ, data: EMPTY_OBJ, @@ -733,7 +748,8 @@ export function formatComponentName( } name = inferFromRegistry( - (instance.parent.type as ComponentOptions).components + instance.components || + (instance.parent.type as ComponentOptions).components ) || inferFromRegistry(instance.appContext.components) } diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index facca201d9e..d2a4c05944f 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -384,6 +384,9 @@ export function applyOptions( watch: watchOptions, provide: provideOptions, inject: injectOptions, + // assets + components, + directives, // lifecycle beforeMount, mounted, @@ -568,6 +571,32 @@ export function applyOptions( } } + // asset options. + // To reduce memory usage, only components with mixins or extends will have + // resolved asset registry attached to instance. + if (asMixin) { + if (components) { + extend( + instance.components || + (instance.components = extend( + {}, + (instance.type as ComponentOptions).components + ) as Record), + components + ) + } + if (directives) { + extend( + instance.directives || + (instance.directives = extend( + {}, + (instance.type as ComponentOptions).directives + )), + directives + ) + } + } + // lifecycle options if (!asMixin) { callSyncHook('created', options, publicThis, globalMixins) diff --git a/packages/runtime-core/src/helpers/resolveAssets.ts b/packages/runtime-core/src/helpers/resolveAssets.ts index 4a20c747411..59c9f4cc14c 100644 --- a/packages/runtime-core/src/helpers/resolveAssets.ts +++ b/packages/runtime-core/src/helpers/resolveAssets.ts @@ -83,7 +83,8 @@ function resolveAsset( const res = // local registration - resolve((Component as ComponentOptions)[type], name) || + // check instance[type] first for components with mixin or extends. + resolve(instance[type] || (Component as ComponentOptions)[type], name) || // global registration resolve(instance.appContext[type], name) if (__DEV__ && warnMissing && !res) {