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 ff61d348d..e947310e3 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -270,19 +270,19 @@ export function render(_ctx) { `; exports[`compiler: element transform > dynamic component > dynamic binding 1`] = ` -"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; +"import { createDynamicComponent as _createDynamicComponent } from 'vue'; export function render(_ctx) { - const n0 = _createComponentWithFallback(_resolveDynamicComponent(_ctx.foo), null, null, true) + const n0 = _createDynamicComponent(() => (_ctx.foo), null, null, true) return n0 }" `; exports[`compiler: element transform > dynamic component > dynamic binding shorthand 1`] = ` -"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; +"import { createDynamicComponent as _createDynamicComponent } from 'vue'; export function render(_ctx) { - const n0 = _createComponentWithFallback(_resolveDynamicComponent(_ctx.is), null, null, true) + const n0 = _createDynamicComponent(() => (_ctx.is), null, null, true) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts index 8afc73e4d..5421623a5 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -525,7 +525,7 @@ describe('compiler: element transform', () => { ``, ) expect(code).toMatchSnapshot() - expect(helpers).toContain('resolveDynamicComponent') + expect(helpers).toContain('createDynamicComponent') expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.CREATE_COMPONENT_NODE, @@ -546,7 +546,7 @@ describe('compiler: element transform', () => { const { code, ir, helpers } = compileWithElementTransform(``) expect(code).toMatchSnapshot() - expect(helpers).toContain('resolveDynamicComponent') + expect(helpers).toContain('createDynamicComponent') expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.CREATE_COMPONENT_NODE, diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 997414001..cb92aa596 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -64,9 +64,11 @@ export function genCreateComponent( ...inlineHandlers, `const n${operation.id} = `, ...genCall( - operation.asset - ? helper('createComponentWithFallback') - : helper('createComponent'), + operation.dynamic && !operation.dynamic.isStatic + ? helper('createDynamicComponent') + : operation.asset + ? helper('createComponentWithFallback') + : helper('createComponent'), tag, rawProps, rawSlots, @@ -78,10 +80,14 @@ export function genCreateComponent( function genTag() { if (operation.dynamic) { - return genCall( - helper('resolveDynamicComponent'), - genExpression(operation.dynamic, context), - ) + if (operation.dynamic.isStatic) { + return genCall( + helper('resolveDynamicComponent'), + genExpression(operation.dynamic, context), + ) + } else { + return ['() => (', ...genExpression(operation.dynamic, context), ')'] + } } else if (operation.asset) { return toValidAssetId(operation.tag, 'component') } else { diff --git a/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts b/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts new file mode 100644 index 000000000..2f6cd7b3f --- /dev/null +++ b/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts @@ -0,0 +1,57 @@ +import { shallowRef } from '@vue/reactivity' +import { nextTick } from '@vue/runtime-dom' +import { createDynamicComponent } from '../src' +import { makeRender } from './_utils' + +const define = makeRender() + +describe('api: createDynamicComponent', () => { + const A = () => document.createTextNode('AAA') + const B = () => document.createTextNode('BBB') + + test('direct value', async () => { + const val = shallowRef(A) + + const { html } = define({ + setup() { + return createDynamicComponent(() => val.value) + }, + }).render() + + expect(html()).toBe('AAA') + + val.value = B + await nextTick() + expect(html()).toBe('BBB') + + // fallback + val.value = 'foo' + await nextTick() + expect(html()).toBe('') + }) + + test('global registration', async () => { + const val = shallowRef('foo') + + const { app, html, mount } = define({ + setup() { + return createDynamicComponent(() => val.value) + }, + }).create() + + app.component('foo', A) + app.component('bar', B) + + mount() + expect(html()).toBe('AAA') + + val.value = 'bar' + await nextTick() + expect(html()).toBe('BBB') + + // fallback + val.value = 'baz' + await nextTick() + expect(html()).toBe('') + }) +}) diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts new file mode 100644 index 000000000..3cd65c837 --- /dev/null +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -0,0 +1,31 @@ +import { resolveDynamicComponent } from '@vue/runtime-dom' +import { DynamicFragment, type Fragment } from './block' +import { createComponentWithFallback } from './component' +import { renderEffect } from './renderEffect' +import type { RawProps } from './componentProps' +import type { RawSlots } from './componentSlots' + +export function createDynamicComponent( + getter: () => any, + rawProps?: RawProps | null, + rawSlots?: RawSlots | null, + isSingleRoot?: boolean, +): Fragment { + const frag = __DEV__ + ? new DynamicFragment('dynamic-component') + : new DynamicFragment() + renderEffect(() => { + const value = getter() + frag.update( + () => + createComponentWithFallback( + resolveDynamicComponent(value) as any, + rawProps, + rawSlots, + isSingleRoot, + ), + value, + ) + }) + return frag +} diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index d1daa8660..bd1601270 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -41,11 +41,11 @@ export class DynamicFragment extends Fragment { document.createTextNode('') } - update(render?: BlockFn): void { - if (render === this.current) { + update(render?: BlockFn, key: any = render): void { + if (key === this.current) { return } - this.current = render + this.current = key pauseTracking() const parent = this.anchor.parentNode diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 3e5d4fc2d..ee56aa9cd 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -444,8 +444,8 @@ export function isVaporComponent( */ export function createComponentWithFallback( comp: VaporComponent | string, - rawProps: RawProps | null | undefined, - rawSlots: RawSlots | null | undefined, + rawProps?: RawProps | null, + rawSlots?: RawSlots | null, isSingleRoot?: boolean, ): HTMLElement | VaporComponentInstance { if (!isString(comp)) { diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 40b4b82c3..e774ffb0f 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -29,3 +29,4 @@ export { getDefaultValue, } from './apiCreateFor' export { createTemplateRefSetter } from './apiTemplateRef' +export { createDynamicComponent } from './apiCreateDynamicComponent'