diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 33cfe51dd7e..37c7c7b5f49 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -70,6 +70,14 @@ describe('compiler: element transform', () => { expect(root.components).toContain(`Foo`) }) + test('resolve implcitly self-referencing component', () => { + const { root } = parseWithElementTransform(``, { + filename: `/foo/bar/Example.vue?vue&type=template` + }) + expect(root.helpers).toContain(RESOLVE_COMPONENT) + expect(root.components).toContain(`_self`) + }) + test('static props', () => { const { node } = parseWithElementTransform(`
`) expect(node).toMatchObject({ diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index 02ee406838d..2850da196b1 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -128,6 +128,12 @@ interface SharedTransformCodegenOptions { * Indicates that transforms and codegen should try to output valid TS code */ isTS?: boolean + /** + * Filename for source map generation. + * Also used for self-recursive reference in templates + * @default 'template.vue.html' + */ + filename?: string } export interface TransformOptions extends SharedTransformCodegenOptions { @@ -218,11 +224,6 @@ export interface CodegenOptions extends SharedTransformCodegenOptions { * @default false */ sourceMap?: boolean - /** - * Filename for source map generation. - * @default 'template.vue.html' - */ - filename?: string /** * SFC scoped styles ID */ diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index b21bb73a8c9..474f491595c 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -24,7 +24,9 @@ import { NOOP, PatchFlags, PatchFlagNames, - EMPTY_OBJ + EMPTY_OBJ, + capitalize, + camelize } from '@vue/shared' import { defaultOnError } from './errors' import { @@ -79,7 +81,9 @@ export interface ImportItem { path: string } -export interface TransformContext extends Required { +export interface TransformContext + extends Required> { + selfName: string | null root: RootNode helpers: Set components: Set @@ -112,6 +116,7 @@ export interface TransformContext extends Required { export function createTransformContext( root: RootNode, { + filename = '', prefixIdentifiers = false, hoistStatic = false, cacheHandlers = false, @@ -130,8 +135,10 @@ export function createTransformContext( onError = defaultOnError }: TransformOptions ): TransformContext { + const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/) const context: TransformContext = { // options + selfName: nameMatch && capitalize(camelize(nameMatch[1])), prefixIdentifiers, hoistStatic, cacheHandlers, diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index ca1ae9d1be3..53b148b42bb 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -263,7 +263,16 @@ export function resolveComponentType( } } - // 4. user component (resolve) + // 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`) + } + } + + // 5. user component (resolve) context.helper(RESOLVE_COMPONENT) context.components.add(tag) return toValidAssetId(tag, `component`) diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index 1df52721ac7..9a065a457e8 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -33,7 +33,7 @@ export interface SFCBlock { export interface SFCTemplateBlock extends SFCBlock { type: 'template' - functional?: boolean + ast: ElementNode } export interface SFCScriptBlock extends SFCBlock { @@ -79,7 +79,7 @@ export function parse( source: string, { sourceMap = true, - filename = 'component.vue', + filename = 'anonymous.vue', sourceRoot = '', pad = false, compiler = CompilerDOM @@ -143,31 +143,32 @@ export function parse( switch (node.tag) { case 'template': if (!descriptor.template) { - descriptor.template = createBlock( + const templateBlock = (descriptor.template = createBlock( node, source, false - ) as SFCTemplateBlock + ) as SFCTemplateBlock) + templateBlock.ast = node } else { errors.push(createDuplicateBlockError(node)) } break case 'script': - const block = createBlock(node, source, pad) as SFCScriptBlock - const isSetup = !!block.attrs.setup + const scriptBlock = createBlock(node, source, pad) as SFCScriptBlock + const isSetup = !!scriptBlock.attrs.setup if (isSetup && !descriptor.scriptSetup) { - descriptor.scriptSetup = block + descriptor.scriptSetup = scriptBlock break } if (!isSetup && !descriptor.script) { - descriptor.script = block + descriptor.script = scriptBlock break } errors.push(createDuplicateBlockError(node, isSetup)) break case 'style': - const style = createBlock(node, source, pad) as SFCStyleBlock - if (style.attrs.vars) { + const styleBlock = createBlock(node, source, pad) as SFCStyleBlock + if (styleBlock.attrs.vars) { errors.push( new SyntaxError( `