Преглед изворни кода

fix(component): prioritize registered component over implicit self-reference via filename

ref: #2827
Evan You пре 5 година
родитељ
комит
abd129d845

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

@@ -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

+ 7 - 1
packages/compiler-core/__tests__/codegen.spec.ts

@@ -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]

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

@@ -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', () => {

+ 9 - 2
packages/compiler-core/src/codegen.ts

@@ -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()

+ 11 - 6
packages/compiler-core/src/transforms/transformElement.ts

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

+ 31 - 0
packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts

@@ -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')

+ 17 - 11
packages/runtime-core/src/helpers/resolveAssets.ts

@@ -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(

+ 1 - 0
packages/template-explorer/src/options.ts

@@ -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,