From abd129d845951737c335a80a8af6cf7b0df2f74d Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 26 Mar 2021 10:04:29 -0400 Subject: [PATCH] fix(component): prioritize registered component over implicit self-reference via filename ref: #2827 --- .../__snapshots__/codegen.spec.ts.snap | 1 + .../compiler-core/__tests__/codegen.spec.ts | 8 ++++- .../transforms/transformElement.spec.ts | 2 +- packages/compiler-core/src/codegen.ts | 11 +++++-- .../src/transforms/transformElement.ts | 17 ++++++---- .../__tests__/helpers/resolveAssets.spec.ts | 31 +++++++++++++++++++ .../runtime-core/src/helpers/resolveAssets.ts | 28 ++++++++++------- packages/template-explorer/src/options.ts | 1 + 8 files changed, 78 insertions(+), 21 deletions(-) diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index 6975881ee74..74d0ab70d38 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/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 diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index 809c209f80b..75001f94aff 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/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] diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 6f49aa70f63..c07bb0e5533 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/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', () => { diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index cd572313286..3acb8cf5423 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/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() diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 0283d3eec5e..5c34c6d2eee 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/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) diff --git a/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts b/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts index 5d5e895ca53..44434a256d5 100644 --- a/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts +++ b/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') diff --git a/packages/runtime-core/src/helpers/resolveAssets.ts b/packages/runtime-core/src/helpers/resolveAssets.ts index 1d6a96bf999..e867cc51d57 100644 --- a/packages/runtime-core/src/helpers/resolveAssets.ts +++ b/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( diff --git a/packages/template-explorer/src/options.ts b/packages/template-explorer/src/options.ts index dab7f8adf8a..e8669bc7e2e 100644 --- a/packages/template-explorer/src/options.ts +++ b/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,