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'