mirror of https://github.com/vuejs/core.git
fix(ssr/watch) flush: sync watchers should work in ssr (#6139)
fix #6013
This commit is contained in:
parent
32b51249bf
commit
0e0976168f
|
@ -40,6 +40,8 @@ import { warn } from './warning'
|
|||
import { DeprecationTypes } from './compat/compatConfig'
|
||||
import { checkCompatEnabled, isCompatEnabled } from './compat/compatConfig'
|
||||
import { ObjectWatchOptionItem } from './componentOptions'
|
||||
import { useSSRContext } from '@vue/runtime-core'
|
||||
import { SSRContext } from '@vue/server-renderer'
|
||||
|
||||
export type WatchEffect = (onCleanup: OnCleanup) => void
|
||||
|
||||
|
@ -280,7 +282,8 @@ function doWatch(
|
|||
}
|
||||
|
||||
// in SSR there is no need to setup an actual effect, and it should be noop
|
||||
// unless it's eager
|
||||
// unless it's eager or sync flush
|
||||
let ssrCleanup: (() => void)[] | undefined
|
||||
if (__SSR__ && isInSSRComponentSetup) {
|
||||
// we will also not call the invalidate callback (+ runner is not set up)
|
||||
onCleanup = NOOP
|
||||
|
@ -293,8 +296,13 @@ function doWatch(
|
|||
onCleanup
|
||||
])
|
||||
}
|
||||
if (flush === 'sync') {
|
||||
const ctx = useSSRContext() as SSRContext
|
||||
ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
|
||||
} else {
|
||||
return NOOP
|
||||
}
|
||||
}
|
||||
|
||||
let oldValue: any = isMultiSource
|
||||
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
|
||||
|
@ -378,12 +386,15 @@ function doWatch(
|
|||
effect.run()
|
||||
}
|
||||
|
||||
return () => {
|
||||
const unwatch = () => {
|
||||
effect.stop()
|
||||
if (instance && instance.scope) {
|
||||
remove(instance.scope.effects!, effect)
|
||||
}
|
||||
}
|
||||
|
||||
if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
|
||||
return unwatch
|
||||
}
|
||||
|
||||
// this.$watch
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { createSSRApp, defineComponent, h, watch, ref } from 'vue'
|
||||
import { SSRContext, renderToString } from '../src'
|
||||
|
||||
describe('ssr: watch', () => {
|
||||
// #6013
|
||||
test('should work w/ flush:sync', async () => {
|
||||
const App = defineComponent(() => {
|
||||
const count = ref(0)
|
||||
let msg = ''
|
||||
watch(
|
||||
count,
|
||||
() => {
|
||||
msg = 'hello world'
|
||||
},
|
||||
{ flush: 'sync' }
|
||||
)
|
||||
count.value = 1
|
||||
expect(msg).toBe('hello world')
|
||||
return () => h('div', null, msg)
|
||||
})
|
||||
|
||||
const app = createSSRApp(App)
|
||||
const ctx: SSRContext = {}
|
||||
const html = await renderToString(app, ctx)
|
||||
|
||||
expect(ctx.__watcherHandles!.length).toBe(1)
|
||||
|
||||
expect(html).toMatch('hello world')
|
||||
})
|
||||
})
|
|
@ -45,7 +45,14 @@ export type Props = Record<string, unknown>
|
|||
export type SSRContext = {
|
||||
[key: string]: any
|
||||
teleports?: Record<string, string>
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
__teleportBuffers?: Record<string, SSRBuffer>
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
__watcherHandles?: (() => void)[]
|
||||
}
|
||||
|
||||
// Each component has a buffer array.
|
||||
|
|
|
@ -76,6 +76,13 @@ export function renderToSimpleStream<T extends SimpleReadable>(
|
|||
Promise.resolve(renderComponentVNode(vnode))
|
||||
.then(buffer => unrollBuffer(buffer, stream))
|
||||
.then(() => resolveTeleports(context))
|
||||
.then(() => {
|
||||
if (context.__watcherHandles) {
|
||||
for (const unwatch of context.__watcherHandles) {
|
||||
unwatch()
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => stream.push(null))
|
||||
.catch(error => {
|
||||
stream.destroy(error)
|
||||
|
|
|
@ -67,6 +67,12 @@ export async function renderToString(
|
|||
|
||||
await resolveTeleports(context)
|
||||
|
||||
if (context.__watcherHandles) {
|
||||
for (const unwatch of context.__watcherHandles) {
|
||||
unwatch()
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue