Skip to content

Commit abd129d

Browse files
committed
fix(component): prioritize registered component over implicit self-reference via filename
ref: #2827
1 parent e2469fd commit abd129d

File tree

8 files changed

+78
-21
lines changed

8 files changed

+78
-21
lines changed

packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ return function render(_ctx, _cache) {
6666
const _component_Foo = _resolveComponent(\\"Foo\\")
6767
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
6868
const _component_barbaz = _resolveComponent(\\"barbaz\\")
69+
const _component_Qux = _resolveComponent(\\"Qux\\", true)
6970
const _directive_my_dir_0 = _resolveDirective(\\"my_dir_0\\")
7071
const _directive_my_dir_1 = _resolveDirective(\\"my_dir_1\\")
7172
let _temp0, _temp1, _temp2

packages/compiler-core/__tests__/codegen.spec.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ describe('compiler: codegen', () => {
126126

127127
test('assets + temps', () => {
128128
const root = createRoot({
129-
components: [`Foo`, `bar-baz`, `barbaz`],
129+
components: [`Foo`, `bar-baz`, `barbaz`, `Qux__self`],
130130
directives: [`my_dir_0`, `my_dir_1`],
131131
temps: 3
132132
})
@@ -144,6 +144,12 @@ describe('compiler: codegen', () => {
144144
helperNameMap[RESOLVE_COMPONENT]
145145
}("barbaz")\n`
146146
)
147+
// implicit self reference from SFC filename
148+
expect(code).toMatch(
149+
`const _component_Qux = _${
150+
helperNameMap[RESOLVE_COMPONENT]
151+
}("Qux", true)\n`
152+
)
147153
expect(code).toMatch(
148154
`const _directive_my_dir_0 = _${
149155
helperNameMap[RESOLVE_DIRECTIVE]

packages/compiler-core/__tests__/transforms/transformElement.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('compiler: element transform', () => {
7575
filename: `/foo/bar/Example.vue?vue&type=template`
7676
})
7777
expect(root.helpers).toContain(RESOLVE_COMPONENT)
78-
expect(root.components).toContain(`_self`)
78+
expect(root.components).toContain(`Example__self`)
7979
})
8080

8181
test('static props', () => {

packages/compiler-core/src/codegen.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -434,9 +434,16 @@ function genAssets(
434434
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
435435
)
436436
for (let i = 0; i < assets.length; i++) {
437-
const id = assets[i]
437+
let id = assets[i]
438+
// potential component implicit self-reference inferred from SFC filename
439+
const maybeSelfReference = id.endsWith('__self')
440+
if (maybeSelfReference) {
441+
id = id.slice(0, -6)
442+
}
438443
push(
439-
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
444+
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
445+
maybeSelfReference ? `, true` : ``
446+
})`
440447
)
441448
if (i < assets.length - 1) {
442449
newline()

packages/compiler-core/src/transforms/transformElement.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,17 @@ export function resolveComponentType(
264264
}
265265

266266
// 4. Self referencing component (inferred from filename)
267-
if (!__BROWSER__ && context.selfName) {
268-
if (capitalize(camelize(tag)) === context.selfName) {
269-
context.helper(RESOLVE_COMPONENT)
270-
context.components.add(`_self`)
271-
return toValidAssetId(`_self`, `component`)
272-
}
267+
if (
268+
!__BROWSER__ &&
269+
context.selfName &&
270+
capitalize(camelize(tag)) === context.selfName
271+
) {
272+
context.helper(RESOLVE_COMPONENT)
273+
// codegen.ts has special check for __self postfix when generating
274+
// component imports, which will pass additional `maybeSelfReference` flag
275+
// to `resolveComponent`.
276+
context.components.add(tag + `__self`)
277+
return toValidAssetId(tag, `component`)
273278
}
274279

275280
// 5. user component (resolve)

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

+31
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,37 @@ describe('resolveAssets', () => {
6565
expect(directive4!).toBe(BarBaz)
6666
})
6767

68+
test('maybeSelfReference', async () => {
69+
let component1: Component | string
70+
let component2: Component | string
71+
let component3: Component | string
72+
73+
const Foo = () => null
74+
75+
const Root = {
76+
name: 'Root',
77+
components: {
78+
Foo,
79+
Root: Foo
80+
},
81+
setup() {
82+
return () => {
83+
component1 = resolveComponent('Root', true)
84+
component2 = resolveComponent('Foo', true)
85+
component3 = resolveComponent('Bar', true)
86+
}
87+
}
88+
}
89+
90+
const app = createApp(Root)
91+
const root = nodeOps.createElement('div')
92+
app.mount(root)
93+
94+
expect(component1!).toBe(Root) // explicit self name reference
95+
expect(component2!).toBe(Foo) // successful resolve take higher priority
96+
expect(component3!).toBe(Root) // fallback when resolve fails
97+
})
98+
6899
describe('warning', () => {
69100
test('used outside render() or setup()', () => {
70101
resolveComponent('foo')

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

+17-11
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ const DIRECTIVES = 'directives'
1616
/**
1717
* @private
1818
*/
19-
export function resolveComponent(name: string): ConcreteComponent | string {
20-
return resolveAsset(COMPONENTS, name) || name
19+
export function resolveComponent(
20+
name: string,
21+
maybeSelfReference?: boolean
22+
): ConcreteComponent | string {
23+
return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
2124
}
2225

2326
export const NULL_DYNAMIC_COMPONENT = Symbol()
@@ -48,7 +51,8 @@ export function resolveDirective(name: string): Directive | undefined {
4851
function resolveAsset(
4952
type: typeof COMPONENTS,
5053
name: string,
51-
warnMissing?: boolean
54+
warnMissing?: boolean,
55+
maybeSelfReference?: boolean
5256
): ConcreteComponent | undefined
5357
// overload 2: directives
5458
function resolveAsset(
@@ -59,20 +63,15 @@ function resolveAsset(
5963
function resolveAsset(
6064
type: typeof COMPONENTS | typeof DIRECTIVES,
6165
name: string,
62-
warnMissing = true
66+
warnMissing = true,
67+
maybeSelfReference = false
6368
) {
6469
const instance = currentRenderingInstance || currentInstance
6570
if (instance) {
6671
const Component = instance.type
6772

68-
// self name has highest priority
73+
// explicit self name has highest priority
6974
if (type === COMPONENTS) {
70-
// special self referencing call generated by compiler
71-
// inferred from SFC filename
72-
if (name === `_self`) {
73-
return Component
74-
}
75-
7675
const selfName = getComponentName(Component)
7776
if (
7877
selfName &&
@@ -90,9 +89,16 @@ function resolveAsset(
9089
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
9190
// global registration
9291
resolve(instance.appContext[type], name)
92+
93+
if (!res && maybeSelfReference) {
94+
// fallback to implicit self-reference
95+
return Component
96+
}
97+
9398
if (__DEV__ && warnMissing && !res) {
9499
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
95100
}
101+
96102
return res
97103
} else if (__DEV__) {
98104
warn(

packages/template-explorer/src/options.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const ssrMode = ref(false)
66

77
export const compilerOptions: CompilerOptions = reactive({
88
mode: 'module',
9+
filename: 'Foo.vue',
910
prefixIdentifiers: false,
1011
optimizeImports: false,
1112
hoistStatic: false,

0 commit comments

Comments
 (0)