]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(vapor): createDynamicComponent
authorEvan You <evan@vuejs.org>
Fri, 31 Jan 2025 08:55:08 +0000 (16:55 +0800)
committerEvan You <evan@vuejs.org>
Fri, 31 Jan 2025 09:04:56 +0000 (17:04 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
packages/compiler-vapor/src/generators/component.ts
packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts [new file with mode: 0644]
packages/runtime-vapor/src/apiCreateDynamicComponent.ts [new file with mode: 0644]
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/index.ts

index ff61d348d7112d63964d49438c40b5b5e5078d05..e947310e35f38b219d0e8fc0dd4ee2331ea8109f 100644 (file)
@@ -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
 }"
 `;
index 8afc73e4d368a63bf9d69efee9e65bff881b51e1..5421623a509a8394367328a133879f6edf32f311 100644 (file)
@@ -525,7 +525,7 @@ describe('compiler: element transform', () => {
         `<component :is="foo" />`,
       )
       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(`<component :is />`)
       expect(code).toMatchSnapshot()
-      expect(helpers).toContain('resolveDynamicComponent')
+      expect(helpers).toContain('createDynamicComponent')
       expect(ir.block.operation).toMatchObject([
         {
           type: IRNodeTypes.CREATE_COMPONENT_NODE,
index 997414001c882a2791bd5d3fafd19ca81eee8902..cb92aa5968c4e51115858bdc35cc6fe25c730de8 100644 (file)
@@ -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 (file)
index 0000000..2f6cd7b
--- /dev/null
@@ -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<any>(A)
+
+    const { html } = define({
+      setup() {
+        return createDynamicComponent(() => val.value)
+      },
+    }).render()
+
+    expect(html()).toBe('AAA<!--dynamic-component-->')
+
+    val.value = B
+    await nextTick()
+    expect(html()).toBe('BBB<!--dynamic-component-->')
+
+    // fallback
+    val.value = 'foo'
+    await nextTick()
+    expect(html()).toBe('<foo></foo><!--dynamic-component-->')
+  })
+
+  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<!--dynamic-component-->')
+
+    val.value = 'bar'
+    await nextTick()
+    expect(html()).toBe('BBB<!--dynamic-component-->')
+
+    // fallback
+    val.value = 'baz'
+    await nextTick()
+    expect(html()).toBe('<baz></baz><!--dynamic-component-->')
+  })
+})
diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts
new file mode 100644 (file)
index 0000000..3cd65c8
--- /dev/null
@@ -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
+}
index d1daa8660daca75bdd68f0e9bcccf359d55c3194..bd16012700c7600c63afd31d2f38503c380086c6 100644 (file)
@@ -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
index 3e5d4fc2d28fd4c0d7fee1bc72c992d747d2b050..ee56aa9cdae55382d8e63f9d1dfd362e7de5b16f 100644 (file)
@@ -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)) {
index 40b4b82c3e02e90580650a86ce65f64b7615a7b8..e774ffb0f29dc6d2c267ef15e83fb64f564c7ea1 100644 (file)
@@ -29,3 +29,4 @@ export {
   getDefaultValue,
 } from './apiCreateFor'
 export { createTemplateRefSetter } from './apiTemplateRef'
+export { createDynamicComponent } from './apiCreateDynamicComponent'