From 584c04fa99fb21617d781708563a4d8bb610dc33 Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 28 May 2025 15:11:19 +0800 Subject: [PATCH] feat(compiler-vapor): resolve implicitly self-referencing component --- packages/compiler-core/src/index.ts | 1 + packages/compiler-core/src/transform.ts | 8 ++++++-- .../__snapshots__/transformElement.spec.ts.snap | 10 ++++++++++ .../transforms/transformElement.spec.ts | 3 ++- packages/compiler-vapor/src/generators/block.ts | 16 +++++++++++++++- packages/compiler-vapor/src/transform.ts | 3 +++ .../src/transforms/transformElement.ts | 7 +++++++ 7 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 36ed73eab92..e54b0c3a498 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -17,6 +17,7 @@ export { createTransformContext, traverseNode, createStructuralDirectiveTransform, + getSelfName, type NodeTransform, type StructuralDirectiveTransform, type DirectiveTransform, diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index aeb96cc2b4a..7d35ec9f700 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -123,6 +123,11 @@ export interface TransformContext filters?: Set } +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, diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap index 69527c0b100..e5ee0bdde4d 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -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) diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts index adaad182cf3..3b306386cb1 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -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(``, { filename: `/foo/bar/Example.vue?vue&type=template`, }) expect(code).toMatchSnapshot() + expect(code).toContain('_resolveComponent("Example", true)') expect(helpers).toContain('resolveComponent') }) diff --git a/packages/compiler-vapor/src/generators/block.ts b/packages/compiler-vapor/src/generators/block.ts index b161b8f45d1..09d05bf3e91 100644 --- a/packages/compiler-vapor/src/generators/block.ts +++ b/packages/compiler-vapor/src/generators/block.ts @@ -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') } diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 76563899d2b..287bf671af3 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -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 export class TransformContext { + selfName: string | null = null parent: TransformContext | null = null root: TransformContext index: number = 0 @@ -92,6 +94,7 @@ export class TransformContext { ) { this.options = extend({}, defaultOptions, options) this.root = this as TransformContext + if (options.filename) this.selfName = getSelfName(options.filename) } enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void { diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index dceb3fd6121..8ecd0205fbc 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -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) } }