Browse Source

fix(runtime-vapor): preserve render context in dev render (#14719)

edison 1 week ago
parent
commit
c8473a4956

+ 48 - 0
packages/runtime-vapor/__tests__/vdomInterop.spec.ts

@@ -20,6 +20,7 @@ import {
   provide,
   ref,
   renderSlot,
+  resolveComponent,
   resolveDynamicComponent,
   shallowRef,
   toDisplayString,
@@ -3024,6 +3025,53 @@ describe('vdomInterop', () => {
       expect(html()).toContain('<div><button>click</button></div>')
     })
 
+    test('preserves render context for setup-returned helpers after async setup resumes', async () => {
+      const duration = 5
+
+      const Resolved = defineVaporComponent({
+        setup() {
+          return template('<div>resolved</div>')()
+        },
+      })
+
+      const VaporAsyncChild = defineVaporComponent({
+        components: {
+          Page: Resolved,
+        },
+        async setup() {
+          await new Promise(resolve => setTimeout(resolve, duration))
+          function resolveLayout(name: string) {
+            const component = resolveComponent(name)
+            return typeof component === 'string' ? null : component
+          }
+          return { resolveLayout }
+        },
+        render(_ctx: any) {
+          const Page = _ctx.resolveLayout('page')
+          return Page ? createComponent(Page, null, null, true) : []
+        },
+      })
+
+      const { html } = define({
+        render() {
+          return h(Suspense as any, null, {
+            default: () => h(VaporAsyncChild as any),
+            fallback: () => h('span', 'loading'),
+          })
+        },
+      }).render()
+
+      expect(html()).toContain('<span>loading</span>')
+
+      await new Promise(resolve => setTimeout(resolve, duration + 1))
+      await nextTick()
+
+      expect(html()).toContain('<div>resolved</div>')
+      expect(
+        'resolveComponent can only be used in render() or setup()',
+      ).not.toHaveBeenWarned()
+    })
+
     test('renders async VDOM child inside VDOM Suspense', async () => {
       const duration = 5
 

+ 26 - 17
packages/runtime-vapor/src/component.ts

@@ -1,5 +1,6 @@
 import {
   type AsyncComponentInternalOptions,
+  type ComponentInternalInstance,
   type ComponentInternalOptions,
   type ComponentObjectPropsOptions,
   type ComponentPropsOptions,
@@ -33,6 +34,7 @@ import {
   queuePostFlushCb,
   registerHMR,
   setCurrentInstance,
+  setCurrentRenderingInstance,
   startMeasure,
   unregisterHMR,
   warn,
@@ -556,23 +558,30 @@ function callRender(
  * dev only
  */
 export function devRender(instance: VaporComponentInstance): void {
-  instance.block =
-    (instance.type.render
-      ? callRender(instance.type.render, instance, instance.setupState!)
-      : callWithErrorHandling(
-          isFunction(instance.type) ? instance.type : instance.type.setup!,
-          instance,
-          ErrorCodes.SETUP_FUNCTION,
-          [
-            instance.props,
-            {
-              slots: instance.slots,
-              attrs: instance.attrs,
-              emit: instance.emit,
-              expose: instance.expose,
-            },
-          ],
-        )) || []
+  const prev = setCurrentRenderingInstance(
+    instance as unknown as ComponentInternalInstance,
+  )
+  try {
+    instance.block =
+      (instance.type.render
+        ? callRender(instance.type.render, instance, instance.setupState!)
+        : callWithErrorHandling(
+            isFunction(instance.type) ? instance.type : instance.type.setup!,
+            instance,
+            ErrorCodes.SETUP_FUNCTION,
+            [
+              instance.props,
+              {
+                slots: instance.slots,
+                attrs: instance.attrs,
+                emit: instance.emit,
+                expose: instance.expose,
+              },
+            ],
+          )) || []
+  } finally {
+    setCurrentRenderingInstance(prev)
+  }
 }
 
 export const emptyContext: GenericAppContext = {