fix(component): prioritize registered component over implicit self-reference via filename
ref: #2827
This commit is contained in:
parent
e2469fd014
commit
abd129d845
@ -66,6 +66,7 @@ return function render(_ctx, _cache) {
|
||||
const _component_Foo = _resolveComponent(\\"Foo\\")
|
||||
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
|
||||
const _component_barbaz = _resolveComponent(\\"barbaz\\")
|
||||
const _component_Qux = _resolveComponent(\\"Qux\\", true)
|
||||
const _directive_my_dir_0 = _resolveDirective(\\"my_dir_0\\")
|
||||
const _directive_my_dir_1 = _resolveDirective(\\"my_dir_1\\")
|
||||
let _temp0, _temp1, _temp2
|
||||
|
@ -126,7 +126,7 @@ describe('compiler: codegen', () => {
|
||||
|
||||
test('assets + temps', () => {
|
||||
const root = createRoot({
|
||||
components: [`Foo`, `bar-baz`, `barbaz`],
|
||||
components: [`Foo`, `bar-baz`, `barbaz`, `Qux__self`],
|
||||
directives: [`my_dir_0`, `my_dir_1`],
|
||||
temps: 3
|
||||
})
|
||||
@ -144,6 +144,12 @@ describe('compiler: codegen', () => {
|
||||
helperNameMap[RESOLVE_COMPONENT]
|
||||
}("barbaz")\n`
|
||||
)
|
||||
// implicit self reference from SFC filename
|
||||
expect(code).toMatch(
|
||||
`const _component_Qux = _${
|
||||
helperNameMap[RESOLVE_COMPONENT]
|
||||
}("Qux", true)\n`
|
||||
)
|
||||
expect(code).toMatch(
|
||||
`const _directive_my_dir_0 = _${
|
||||
helperNameMap[RESOLVE_DIRECTIVE]
|
||||
|
@ -75,7 +75,7 @@ describe('compiler: element transform', () => {
|
||||
filename: `/foo/bar/Example.vue?vue&type=template`
|
||||
})
|
||||
expect(root.helpers).toContain(RESOLVE_COMPONENT)
|
||||
expect(root.components).toContain(`_self`)
|
||||
expect(root.components).toContain(`Example__self`)
|
||||
})
|
||||
|
||||
test('static props', () => {
|
||||
|
@ -434,9 +434,16 @@ function genAssets(
|
||||
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
|
||||
)
|
||||
for (let i = 0; i < assets.length; i++) {
|
||||
const id = assets[i]
|
||||
let id = assets[i]
|
||||
// potential component implicit self-reference inferred from SFC filename
|
||||
const maybeSelfReference = id.endsWith('__self')
|
||||
if (maybeSelfReference) {
|
||||
id = id.slice(0, -6)
|
||||
}
|
||||
push(
|
||||
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
|
||||
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
|
||||
maybeSelfReference ? `, true` : ``
|
||||
})`
|
||||
)
|
||||
if (i < assets.length - 1) {
|
||||
newline()
|
||||
|
@ -264,12 +264,17 @@ export function resolveComponentType(
|
||||
}
|
||||
|
||||
// 4. Self referencing component (inferred from filename)
|
||||
if (!__BROWSER__ && context.selfName) {
|
||||
if (capitalize(camelize(tag)) === context.selfName) {
|
||||
context.helper(RESOLVE_COMPONENT)
|
||||
context.components.add(`_self`)
|
||||
return toValidAssetId(`_self`, `component`)
|
||||
}
|
||||
if (
|
||||
!__BROWSER__ &&
|
||||
context.selfName &&
|
||||
capitalize(camelize(tag)) === context.selfName
|
||||
) {
|
||||
context.helper(RESOLVE_COMPONENT)
|
||||
// codegen.ts has special check for __self postfix when generating
|
||||
// component imports, which will pass additional `maybeSelfReference` flag
|
||||
// to `resolveComponent`.
|
||||
context.components.add(tag + `__self`)
|
||||
return toValidAssetId(tag, `component`)
|
||||
}
|
||||
|
||||
// 5. user component (resolve)
|
||||
|
@ -65,6 +65,37 @@ describe('resolveAssets', () => {
|
||||
expect(directive4!).toBe(BarBaz)
|
||||
})
|
||||
|
||||
test('maybeSelfReference', async () => {
|
||||
let component1: Component | string
|
||||
let component2: Component | string
|
||||
let component3: Component | string
|
||||
|
||||
const Foo = () => null
|
||||
|
||||
const Root = {
|
||||
name: 'Root',
|
||||
components: {
|
||||
Foo,
|
||||
Root: Foo
|
||||
},
|
||||
setup() {
|
||||
return () => {
|
||||
component1 = resolveComponent('Root', true)
|
||||
component2 = resolveComponent('Foo', true)
|
||||
component3 = resolveComponent('Bar', true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const app = createApp(Root)
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
|
||||
expect(component1!).toBe(Root) // explicit self name reference
|
||||
expect(component2!).toBe(Foo) // successful resolve take higher priority
|
||||
expect(component3!).toBe(Root) // fallback when resolve fails
|
||||
})
|
||||
|
||||
describe('warning', () => {
|
||||
test('used outside render() or setup()', () => {
|
||||
resolveComponent('foo')
|
||||
|
@ -16,8 +16,11 @@ const DIRECTIVES = 'directives'
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function resolveComponent(name: string): ConcreteComponent | string {
|
||||
return resolveAsset(COMPONENTS, name) || name
|
||||
export function resolveComponent(
|
||||
name: string,
|
||||
maybeSelfReference?: boolean
|
||||
): ConcreteComponent | string {
|
||||
return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
|
||||
}
|
||||
|
||||
export const NULL_DYNAMIC_COMPONENT = Symbol()
|
||||
@ -48,7 +51,8 @@ export function resolveDirective(name: string): Directive | undefined {
|
||||
function resolveAsset(
|
||||
type: typeof COMPONENTS,
|
||||
name: string,
|
||||
warnMissing?: boolean
|
||||
warnMissing?: boolean,
|
||||
maybeSelfReference?: boolean
|
||||
): ConcreteComponent | undefined
|
||||
// overload 2: directives
|
||||
function resolveAsset(
|
||||
@ -59,20 +63,15 @@ function resolveAsset(
|
||||
function resolveAsset(
|
||||
type: typeof COMPONENTS | typeof DIRECTIVES,
|
||||
name: string,
|
||||
warnMissing = true
|
||||
warnMissing = true,
|
||||
maybeSelfReference = false
|
||||
) {
|
||||
const instance = currentRenderingInstance || currentInstance
|
||||
if (instance) {
|
||||
const Component = instance.type
|
||||
|
||||
// self name has highest priority
|
||||
// explicit self name has highest priority
|
||||
if (type === COMPONENTS) {
|
||||
// special self referencing call generated by compiler
|
||||
// inferred from SFC filename
|
||||
if (name === `_self`) {
|
||||
return Component
|
||||
}
|
||||
|
||||
const selfName = getComponentName(Component)
|
||||
if (
|
||||
selfName &&
|
||||
@ -90,9 +89,16 @@ function resolveAsset(
|
||||
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
|
||||
// global registration
|
||||
resolve(instance.appContext[type], name)
|
||||
|
||||
if (!res && maybeSelfReference) {
|
||||
// fallback to implicit self-reference
|
||||
return Component
|
||||
}
|
||||
|
||||
if (__DEV__ && warnMissing && !res) {
|
||||
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
|
||||
}
|
||||
|
||||
return res
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
|
@ -6,6 +6,7 @@ export const ssrMode = ref(false)
|
||||
|
||||
export const compilerOptions: CompilerOptions = reactive({
|
||||
mode: 'module',
|
||||
filename: 'Foo.vue',
|
||||
prefixIdentifiers: false,
|
||||
optimizeImports: false,
|
||||
hoistStatic: false,
|
||||
|
Loading…
Reference in New Issue
Block a user