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