diff --git a/packages/runtime-vapor/__tests__/resolveAssets.spec.ts b/packages/runtime-vapor/__tests__/resolveAssets.spec.ts new file mode 100644 index 000000000..9c8e2e082 --- /dev/null +++ b/packages/runtime-vapor/__tests__/resolveAssets.spec.ts @@ -0,0 +1,75 @@ +import { + type Component, + createComponent, + createTextNode, + ref, + resolveComponent, + setText, + template, + watchEffect, +} from '../src' +import { describe, expect } from 'vitest' +import { makeRender } from './_utils' + +const define = makeRender() + +describe('resolveAssets', () => { + test('should work', async () => { + const child = { + name: 'FooBaz', + setup() { + return (() => { + const n2 = createTextNode() + return n2 + })() + }, + } + const { render } = define({ + name: 'parent', + setup() { + return (() => { + createComponent(child) + expect(resolveComponent('foo-baz')).toBeDefined() + expect(resolveComponent('foo-baz')).toBeDefined() + expect(resolveComponent('FooBaz')).toBeDefined() + })() + }, + }) + render() + }) + test('maybe self ref', async () => { + let comp: Component | null = null + const rootCompObj = { + name: 'Root', + setup() { + comp = resolveComponent('Root', true, true) + }, + } + const root = define(rootCompObj) + root.render() + expect(comp).toMatchObject(rootCompObj) + }) + describe('warning', () => { + test('outside render() or setup()', () => { + resolveComponent('foo') + expect( + 'resolveComponent can only be used in render() or setup().', + ).toHaveBeenWarned() + }) + test('not exists', () => { + const root = { + setup() { + resolveComponent('not-exists-component') + return (() => { + const n = createTextNode() + })() + }, + } + const { render } = define(root) + render() + expect( + 'Failed to resolve component: not-exists-component', + ).toHaveBeenWarned() + }) + }) +}) diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts index cf282706b..dad4625ef 100644 --- a/packages/runtime-vapor/src/apiCreateComponent.ts +++ b/packages/runtime-vapor/src/apiCreateComponent.ts @@ -26,6 +26,11 @@ export function createComponent( // register sub-component with current component for lifecycle management current.comps.add(instance) + current.components[ + instance.component.name ?? + instance.component.__name ?? + instance.component.__file! + ] = instance.component return instance } diff --git a/packages/runtime-vapor/src/apiCreateVaporApp.ts b/packages/runtime-vapor/src/apiCreateVaporApp.ts index 7a5392424..70b493554 100644 --- a/packages/runtime-vapor/src/apiCreateVaporApp.ts +++ b/packages/runtime-vapor/src/apiCreateVaporApp.ts @@ -102,6 +102,7 @@ export function createAppContext(): AppContext { warnHandler: undefined, }, provides: Object.create(null), + components: {}, } } @@ -137,6 +138,7 @@ export interface AppContext { app: App // for devtools config: AppConfig provides: Record + components: Record } /** diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index d435fc561..a9668680c 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -45,6 +45,12 @@ export type SetupContext = E extends any } : never +export const getComponentName = (component: Component) => { + return isFunction(component) + ? component.displayName || component.name + : component.name +} + export function createSetupContext( instance: ComponentInternalInstance, ): SetupContext { @@ -157,6 +163,7 @@ export interface ComponentInternalInstance { scope: EffectScope component: Component comps: Set + components: Record dirs: Map rawProps: NormalizedRawProps @@ -283,6 +290,7 @@ export function createComponentInstance( provides: parent ? parent.provides : Object.create(_appContext.provides), component, comps: new Set(), + components: {}, dirs: new Map(), // resolved props and emits options diff --git a/packages/runtime-vapor/src/helpers/resolveAssets.ts b/packages/runtime-vapor/src/helpers/resolveAssets.ts index 35ae0bb9a..f08864d80 100644 --- a/packages/runtime-vapor/src/helpers/resolveAssets.ts +++ b/packages/runtime-vapor/src/helpers/resolveAssets.ts @@ -1,5 +1,57 @@ -export function resolveComponent() { - // TODO +import { camelize, capitalize } from '@vue/shared' +import { + type ComponentInternalInstance, + currentInstance, + getComponentName, +} from '../component' +import { warn } from '../warning' + +const resolve = (registry: Record | undefined, name: string) => { + return ( + registry && + (registry[name] || + registry[camelize(name)] || + registry[capitalize(camelize(name))]) + ) +} + +export function resolveComponent( + name: string, + warnMissing = true, + maybeSelfReference = false, +) { + const instance = currentInstance + if (!instance?.component) { + if (__DEV__) { + warn(`resolveComponent ` + `can only be used in render() or setup().`) + } + return + } + const component = instance.component + const selfName = getComponentName(component) + if ( + selfName && + (selfName === name || + selfName === camelize(name) || + selfName === capitalize(camelize(name))) + ) { + return component + } + const registry = + instance.components ?? (component as ComponentInternalInstance).component + const res = + resolve(registry, name) || resolve(instance.appContext.components, name) + if (!res && maybeSelfReference) { + return component + } + if (__DEV__ && warnMissing && !res) { + const extra = + `\nIf this is a native custom element, make sure to exclude it from ` + + `component resolution via compilerOptions.isCustomElement.` + warn(`Failed to resolve component: ${name}${extra}`) + } + return res + // // TODO } export function resolveDirective() {