Skip to content

Commit 0cb7f7f

Browse files
committed
fix(runtime-core): fix resolving assets from mixins and extends
fix #1963
1 parent bc64c60 commit 0cb7f7f

File tree

4 files changed

+107
-3
lines changed

4 files changed

+107
-3
lines changed

packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts

+58
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,62 @@ describe('resolveAssets', () => {
151151
expect(serializeInner(root)).toBe('<div>hello</div>')
152152
})
153153
})
154+
155+
test('resolving from mixins & extends', () => {
156+
const FooBar = () => null
157+
const BarBaz = { mounted: () => null }
158+
159+
let component1: Component | string
160+
let component2: Component | string
161+
let component3: Component | string
162+
let component4: Component | string
163+
let directive1: Directive
164+
let directive2: Directive
165+
let directive3: Directive
166+
let directive4: Directive
167+
168+
const Base = {
169+
components: {
170+
FooBar: FooBar
171+
}
172+
}
173+
const Mixin = {
174+
directives: {
175+
BarBaz: BarBaz
176+
}
177+
}
178+
179+
const Root = {
180+
extends: Base,
181+
mixins: [Mixin],
182+
setup() {
183+
return () => {
184+
component1 = resolveComponent('FooBar')!
185+
directive1 = resolveDirective('BarBaz')!
186+
// camelize
187+
component2 = resolveComponent('Foo-bar')!
188+
directive2 = resolveDirective('Bar-baz')!
189+
// capitalize
190+
component3 = resolveComponent('fooBar')!
191+
directive3 = resolveDirective('barBaz')!
192+
// camelize and capitalize
193+
component4 = resolveComponent('foo-bar')!
194+
directive4 = resolveDirective('bar-baz')!
195+
}
196+
}
197+
}
198+
199+
const app = createApp(Root)
200+
const root = nodeOps.createElement('div')
201+
app.mount(root)
202+
expect(component1!).toBe(FooBar)
203+
expect(component2!).toBe(FooBar)
204+
expect(component3!).toBe(FooBar)
205+
expect(component4!).toBe(FooBar)
206+
207+
expect(directive1!).toBe(BarBaz)
208+
expect(directive2!).toBe(BarBaz)
209+
expect(directive3!).toBe(BarBaz)
210+
expect(directive4!).toBe(BarBaz)
211+
})
154212
})

packages/runtime-core/src/component.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { Slots, initSlots, InternalSlots } from './componentSlots'
2424
import { warn } from './warning'
2525
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
2626
import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
27-
import { validateDirectiveName } from './directives'
27+
import { Directive, validateDirectiveName } from './directives'
2828
import { applyOptions, ComponentOptions } from './componentOptions'
2929
import {
3030
EmitsOptions,
@@ -221,6 +221,17 @@ export interface ComponentInternalInstance {
221221
*/
222222
renderCache: (Function | VNode)[]
223223

224+
/**
225+
* Resolved component registry, only for components with mixins or extends
226+
* @internal
227+
*/
228+
components: Record<string, ConcreteComponent> | null
229+
/**
230+
* Resolved directive registry, only for components with mixins or extends
231+
* @internal
232+
*/
233+
directives: Record<string, Directive> | null
234+
224235
// the rest are only for stateful components ---------------------------------
225236

226237
/**
@@ -372,6 +383,10 @@ export function createComponentInstance(
372383
accessCache: null!,
373384
renderCache: [],
374385

386+
// local resovled assets
387+
components: null,
388+
directives: null,
389+
375390
// state
376391
ctx: EMPTY_OBJ,
377392
data: EMPTY_OBJ,
@@ -733,7 +748,8 @@ export function formatComponentName(
733748
}
734749
name =
735750
inferFromRegistry(
736-
(instance.parent.type as ComponentOptions).components
751+
instance.components ||
752+
(instance.parent.type as ComponentOptions).components
737753
) || inferFromRegistry(instance.appContext.components)
738754
}
739755

packages/runtime-core/src/componentOptions.ts

+29
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,9 @@ export function applyOptions(
384384
watch: watchOptions,
385385
provide: provideOptions,
386386
inject: injectOptions,
387+
// assets
388+
components,
389+
directives,
387390
// lifecycle
388391
beforeMount,
389392
mounted,
@@ -568,6 +571,32 @@ export function applyOptions(
568571
}
569572
}
570573

574+
// asset options.
575+
// To reduce memory usage, only components with mixins or extends will have
576+
// resolved asset registry attached to instance.
577+
if (asMixin) {
578+
if (components) {
579+
extend(
580+
instance.components ||
581+
(instance.components = extend(
582+
{},
583+
(instance.type as ComponentOptions).components
584+
) as Record<string, ConcreteComponent>),
585+
components
586+
)
587+
}
588+
if (directives) {
589+
extend(
590+
instance.directives ||
591+
(instance.directives = extend(
592+
{},
593+
(instance.type as ComponentOptions).directives
594+
)),
595+
directives
596+
)
597+
}
598+
}
599+
571600
// lifecycle options
572601
if (!asMixin) {
573602
callSyncHook('created', options, publicThis, globalMixins)

packages/runtime-core/src/helpers/resolveAssets.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ function resolveAsset(
8383

8484
const res =
8585
// local registration
86-
resolve((Component as ComponentOptions)[type], name) ||
86+
// check instance[type] first for components with mixin or extends.
87+
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
8788
// global registration
8889
resolve(instance.appContext[type], name)
8990
if (__DEV__ && warnMissing && !res) {

0 commit comments

Comments
 (0)