Просмотр исходного кода

fix(ssr): properly update currentRenderingInstance state during ssr

fix #2863
Evan You 5 лет назад
Родитель
Сommit
8c3c14a0ff

+ 14 - 3
packages/runtime-core/src/componentRenderContext.ts

@@ -9,11 +9,23 @@ import { closeBlock, openBlock } from './vnode'
 export let currentRenderingInstance: ComponentInternalInstance | null = null
 export let currentRenderingInstance: ComponentInternalInstance | null = null
 export let currentScopeId: string | null = null
 export let currentScopeId: string | null = null
 
 
+/**
+ * Note: rendering calls maybe nested. The function returns the parent rendering
+ * instance if present, which should be restored after the render is done:
+ *
+ * ```js
+ * const prev = setCurrentRenderingInstance(i)
+ * // ...render
+ * setCurrentRenderingInstance(prev)
+ * ```
+ */
 export function setCurrentRenderingInstance(
 export function setCurrentRenderingInstance(
   instance: ComponentInternalInstance | null
   instance: ComponentInternalInstance | null
-) {
+): ComponentInternalInstance | null {
+  const prev = currentRenderingInstance
   currentRenderingInstance = instance
   currentRenderingInstance = instance
   currentScopeId = (instance && instance.type.__scopeId) || null
   currentScopeId = (instance && instance.type.__scopeId) || null
+  return prev
 }
 }
 
 
 /**
 /**
@@ -40,8 +52,7 @@ export function withCtx(
     if (!isRenderingCompiledSlot) {
     if (!isRenderingCompiledSlot) {
       openBlock(true /* null block that disables tracking */)
       openBlock(true /* null block that disables tracking */)
     }
     }
-    const prevInstance = currentRenderingInstance
-    setCurrentRenderingInstance(ctx)
+    const prevInstance = setCurrentRenderingInstance(ctx)
     const res = fn(...args)
     const res = fn(...args)
     setCurrentRenderingInstance(prevInstance)
     setCurrentRenderingInstance(prevInstance)
     if (!isRenderingCompiledSlot) {
     if (!isRenderingCompiledSlot) {

+ 2 - 2
packages/runtime-core/src/componentRenderUtils.ts

@@ -53,7 +53,7 @@ export function renderComponentRoot(
   } = instance
   } = instance
 
 
   let result
   let result
-  setCurrentRenderingInstance(instance)
+  const prev = setCurrentRenderingInstance(instance)
   if (__DEV__) {
   if (__DEV__) {
     accessedAttrs = false
     accessedAttrs = false
   }
   }
@@ -207,7 +207,7 @@ export function renderComponentRoot(
     result = createVNode(Comment)
     result = createVNode(Comment)
   }
   }
 
 
-  setCurrentRenderingInstance(null)
+  setCurrentRenderingInstance(prev)
   return result
   return result
 }
 }
 
 

+ 33 - 1
packages/server-renderer/__tests__/render.spec.ts

@@ -11,7 +11,9 @@ import {
   withCtx,
   withCtx,
   KeepAlive,
   KeepAlive,
   Transition,
   Transition,
-  watchEffect
+  watchEffect,
+  createVNode,
+  resolveDynamicComponent
 } from 'vue'
 } from 'vue'
 import { escapeHtml } from '@vue/shared'
 import { escapeHtml } from '@vue/shared'
 import { renderToString } from '../src/renderToString'
 import { renderToString } from '../src/renderToString'
@@ -19,6 +21,7 @@ import { renderToStream as _renderToStream } from '../src/renderToStream'
 import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
 import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
 import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
 import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
 import { Readable } from 'stream'
 import { Readable } from 'stream'
+import { ssrRenderVNode } from '../src'
 
 
 const promisifyStream = (stream: Readable) => {
 const promisifyStream = (stream: Readable) => {
   return new Promise<string>((resolve, reject) => {
   return new Promise<string>((resolve, reject) => {
@@ -824,5 +827,34 @@ function testRender(type: string, render: typeof renderToString) {
       })
       })
       expect(await render(app)).toBe('<!---->')
       expect(await render(app)).toBe('<!---->')
     })
     })
+
+    // #2863
+    test('assets should be resolved correctly', async () => {
+      expect(
+        await render(
+          createApp({
+            components: {
+              A: {
+                ssrRender(_ctx, _push) {
+                  _push(`<div>A</div>`)
+                }
+              },
+              B: {
+                render: () => h('div', 'B')
+              }
+            },
+            ssrRender(_ctx, _push, _parent) {
+              const A: any = resolveComponent('A')
+              _push(ssrRenderComponent(A, null, null, _parent))
+              ssrRenderVNode(
+                _push,
+                createVNode(resolveDynamicComponent('B'), null, null),
+                _parent
+              )
+            }
+          })
+        )
+      ).toBe(`<div>A</div><div>B</div>`)
+    })
   })
   })
 }
 }

+ 2 - 2
packages/server-renderer/src/render.ts

@@ -139,7 +139,7 @@ function renderComponentSubTree(
       }
       }
 
 
       // set current rendering instance for asset resolution
       // set current rendering instance for asset resolution
-      setCurrentRenderingInstance(instance)
+      const prev = setCurrentRenderingInstance(instance)
       ssrRender(
       ssrRender(
         instance.proxy,
         instance.proxy,
         push,
         push,
@@ -151,7 +151,7 @@ function renderComponentSubTree(
         instance.data,
         instance.data,
         instance.ctx
         instance.ctx
       )
       )
-      setCurrentRenderingInstance(null)
+      setCurrentRenderingInstance(prev)
     } else if (instance.render && instance.render !== NOOP) {
     } else if (instance.render && instance.render !== NOOP) {
       renderVNode(
       renderVNode(
         push,
         push,