]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-vapor): resolve implicitly self-referencing component (#13400)
authoredison <daiwei521@126.com>
Wed, 18 Jun 2025 00:36:25 +0000 (08:36 +0800)
committerGitHub <noreply@github.com>
Wed, 18 Jun 2025 00:36:25 +0000 (08:36 +0800)
packages/compiler-core/src/index.ts
packages/compiler-core/src/transform.ts
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
packages/compiler-vapor/src/generators/block.ts
packages/compiler-vapor/src/transform.ts
packages/compiler-vapor/src/transforms/transformElement.ts

index 36ed73eab928291b88ebb6533eedd3e413e7b102..e54b0c3a498b00c39ce5509b55204d7c5533a625 100644 (file)
@@ -17,6 +17,7 @@ export {
   createTransformContext,
   traverseNode,
   createStructuralDirectiveTransform,
+  getSelfName,
   type NodeTransform,
   type StructuralDirectiveTransform,
   type DirectiveTransform,
index aeb96cc2b4adc1cb0b07a85f6caed251fd4daa2c..7d35ec9f700e68bf21a7fd0d8a403ffe5f84dc27 100644 (file)
@@ -123,6 +123,11 @@ export interface TransformContext
   filters?: Set<string>
 }
 
+export function getSelfName(filename: string): string | null {
+  const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
+  return nameMatch ? capitalize(camelize(nameMatch[1])) : null
+}
+
 export function createTransformContext(
   root: RootNode,
   {
@@ -150,11 +155,10 @@ export function createTransformContext(
     compatConfig,
   }: TransformOptions,
 ): TransformContext {
-  const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
   const context: TransformContext = {
     // options
     filename,
-    selfName: nameMatch && capitalize(camelize(nameMatch[1])),
+    selfName: getSelfName(filename),
     prefixIdentifiers,
     hoistStatic,
     hmr,
index 69527c0b100bb81e9da44a010f4e3e44b1583699..e5ee0bdde4d41dbfbf4e531ab938e158736f3bdc 100644 (file)
@@ -77,6 +77,16 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
 }"
 `;
 
+exports[`compiler: element transform > component > resolve implicitly self-referencing component 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+  const _component_Example__self = _resolveComponent("Example", true)
+  const n0 = _createComponentWithFallback(_component_Example__self, null, null, true)
+  return n0
+}"
+`;
+
 exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
 "
   const n0 = _createComponent(Foo.Example, null, null, true)
index adaad182cf3445dbac55fb012f4fba8b8ab49d4e..3b306386cb12dd41cfe54ea3300118daf55e75b5 100644 (file)
@@ -39,11 +39,12 @@ describe('compiler: element transform', () => {
       })
     })
 
-    test.todo('resolve implicitly self-referencing component', () => {
+    test('resolve implicitly self-referencing component', () => {
       const { code, helpers } = compileWithElementTransform(`<Example/>`, {
         filename: `/foo/bar/Example.vue?vue&type=template`,
       })
       expect(code).toMatchSnapshot()
+      expect(code).toContain('_resolveComponent("Example", true)')
       expect(helpers).toContain('resolveComponent')
     })
 
index b161b8f45d1b6565da791621b55c66c6c42a74f8..09d05bf3e91c3e968689de6d6ca64b72883b5137 100644 (file)
@@ -44,7 +44,21 @@ export function genBlockContent(
   const resetBlock = context.enterBlock(block)
 
   if (root) {
-    genResolveAssets('component', 'resolveComponent')
+    for (let name of context.ir.component) {
+      const id = toValidAssetId(name, 'component')
+      const maybeSelfReference = name.endsWith('__self')
+      if (maybeSelfReference) name = name.slice(0, -6)
+      push(
+        NEWLINE,
+        `const ${id} = `,
+        ...genCall(
+          context.helper('resolveComponent'),
+          JSON.stringify(name),
+          // pass additional `maybeSelfReference` flag
+          maybeSelfReference ? 'true' : undefined,
+        ),
+      )
+    }
     genResolveAssets('directive', 'resolveDirective')
   }
 
index 76563899d2b770b19aae215805cded9c13ba85b5..287bf671af39ade5d2d394a3aa62dd830fb62299 100644 (file)
@@ -11,6 +11,7 @@ import {
   type TemplateChildNode,
   defaultOnError,
   defaultOnWarn,
+  getSelfName,
   isVSlot,
 } from '@vue/compiler-dom'
 import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
@@ -61,6 +62,7 @@ export type StructuralDirectiveTransform = (
 export type TransformOptions = HackOptions<BaseTransformOptions>
 
 export class TransformContext<T extends AllNode = AllNode> {
+  selfName: string | null = null
   parent: TransformContext<RootNode | ElementNode> | null = null
   root: TransformContext<RootNode>
   index: number = 0
@@ -92,6 +94,7 @@ export class TransformContext<T extends AllNode = AllNode> {
   ) {
     this.options = extend({}, defaultOptions, options)
     this.root = this as TransformContext<RootNode>
+    if (options.filename) this.selfName = getSelfName(options.filename)
   }
 
   enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
index dceb3fd6121f33da80dfd7121ede5ddbb9ad05bf..8ecd0205fbcbbf6e48e728d3f6648c7d5ac05d7f 100644 (file)
@@ -119,6 +119,13 @@ function transformComponentElement(
     }
 
     if (asset) {
+      // self referencing component (inferred from filename)
+      if (context.selfName && capitalize(camelize(tag)) === context.selfName) {
+        // generators/block.ts has special check for __self postfix when generating
+        // component imports, which will pass additional `maybeSelfReference` flag
+        // to `resolveComponent`.
+        tag += `__self`
+      }
       context.component.add(tag)
     }
   }