feat(compiler-vapor): resolve implicitly self-referencing component (#13400)

This commit is contained in:
edison 2025-06-18 08:36:25 +08:00 committed by GitHub
parent 08f9c1d9f6
commit 978c47f751
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 44 additions and 4 deletions

View File

@ -17,6 +17,7 @@ export {
createTransformContext,
traverseNode,
createStructuralDirectiveTransform,
getSelfName,
type NodeTransform,
type StructuralDirectiveTransform,
type DirectiveTransform,

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

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

View 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')
})

View 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')
}

View 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 {

View 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)
}
}