Skip to content

Commit

Permalink
fix(component): prioritize registered component over implicit self-re…
Browse files Browse the repository at this point in the history
…ference via filename

ref: #2827
  • Loading branch information
yyx990803 committed Mar 26, 2021
1 parent e2469fd commit abd129d
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion packages/compiler-core/__tests__/codegen.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
Expand All @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
11 changes: 9 additions & 2 deletions packages/compiler-core/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
17 changes: 11 additions & 6 deletions packages/compiler-core/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 31 additions & 0 deletions packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
28 changes: 17 additions & 11 deletions packages/runtime-core/src/helpers/resolveAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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(
Expand All @@ -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 &&
Expand All @@ -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(
Expand Down
1 change: 1 addition & 0 deletions packages/template-explorer/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit abd129d

Please sign in to comment.