From a78760d600042c01cbca73013223ca183800cfcd Mon Sep 17 00:00:00 2001 From: webfansplz <308241863@qq.com> Date: Sun, 19 Jun 2022 15:03:15 +0800 Subject: [PATCH 1/5] fix(watch): should work normal w/ sync flush in ssr --- packages/runtime-core/src/apiWatch.ts | 2 +- .../__tests__/ssrWatch.spec.ts | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 packages/server-renderer/__tests__/ssrWatch.spec.ts diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index a1922f0cd2e..c7164575526 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -286,7 +286,7 @@ function doWatch( onCleanup = NOOP if (!cb) { getter() - } else if (immediate) { + } else if (immediate || flush === 'sync') { callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ getter(), isMultiSource ? [] : undefined, diff --git a/packages/server-renderer/__tests__/ssrWatch.spec.ts b/packages/server-renderer/__tests__/ssrWatch.spec.ts new file mode 100644 index 00000000000..febec9f0d6b --- /dev/null +++ b/packages/server-renderer/__tests__/ssrWatch.spec.ts @@ -0,0 +1,25 @@ +import { createSSRApp, defineComponent, h, watch, ref } from 'vue' +import { renderToString } from '../src/renderToString' + +describe('ssr: watch', () => { + // #6013 + test('should work w/ flush:sync option', async () => { + const App = defineComponent(async () => { + 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 html = await renderToString(app) + expect(html).toMatch('hello world') + }) +}) From 52da95855b866426a3940e5c84b9a1f17770d142 Mon Sep 17 00:00:00 2001 From: webfansplz <308241863@qq.com> Date: Sun, 19 Jun 2022 15:18:18 +0800 Subject: [PATCH 2/5] chore: update --- packages/server-renderer/__tests__/ssrWatch.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-renderer/__tests__/ssrWatch.spec.ts b/packages/server-renderer/__tests__/ssrWatch.spec.ts index febec9f0d6b..7f313327ae3 100644 --- a/packages/server-renderer/__tests__/ssrWatch.spec.ts +++ b/packages/server-renderer/__tests__/ssrWatch.spec.ts @@ -3,7 +3,7 @@ import { renderToString } from '../src/renderToString' describe('ssr: watch', () => { // #6013 - test('should work w/ flush:sync option', async () => { + test('should work w/ flush:sync', async () => { const App = defineComponent(async () => { const count = ref(0) let msg = '' From f3cd5119c490ecc0de009a2c23e6333e2be4e6d6 Mon Sep 17 00:00:00 2001 From: webfansplz <308241863@qq.com> Date: Mon, 20 Jun 2022 13:54:28 +0800 Subject: [PATCH 3/5] chore: update --- packages/runtime-core/src/apiWatch.ts | 8 +++++--- .../__tests__/ssrWatch.spec.ts | 20 ++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index c7164575526..172097e2235 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -280,20 +280,22 @@ 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 if (__SSR__ && isInSSRComponentSetup) { // we will also not call the invalidate callback (+ runner is not set up) onCleanup = NOOP if (!cb) { getter() - } else if (immediate || flush === 'sync') { + } else if (immediate) { callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ getter(), isMultiSource ? [] : undefined, onCleanup ]) } - return NOOP + if (flush !== 'sync') { + return NOOP + } } let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE diff --git a/packages/server-renderer/__tests__/ssrWatch.spec.ts b/packages/server-renderer/__tests__/ssrWatch.spec.ts index 7f313327ae3..34df80c3b4d 100644 --- a/packages/server-renderer/__tests__/ssrWatch.spec.ts +++ b/packages/server-renderer/__tests__/ssrWatch.spec.ts @@ -4,7 +4,7 @@ import { renderToString } from '../src/renderToString' describe('ssr: watch', () => { // #6013 test('should work w/ flush:sync', async () => { - const App = defineComponent(async () => { + const App = defineComponent(() => { const count = ref(0) let msg = '' watch( @@ -22,4 +22,22 @@ describe('ssr: watch', () => { const html = await renderToString(app) expect(html).toMatch('hello world') }) + test('should work w/ flush:sync', async () => { + const App = defineComponent(() => { + const count = ref(0) + let msg = 'abc' + watch( + count, + () => { + msg = 'hello world' + }, + { flush: 'sync' } + ) + expect(msg).toBe('abc') + return () => h('div', null, msg) + }) + const app = createSSRApp(App) + const html = await renderToString(app) + expect(html).toMatch('abc') + }) }) From e2d25ad5e030acff5e9fd9bf0473dc97c8efe06b Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 26 Oct 2022 18:18:24 +0800 Subject: [PATCH 4/5] fix: teardown sync watchers on render complete --- packages/runtime-core/src/apiWatch.ts | 12 +++++++-- .../runtime-core/src/helpers/useSsrContext.ts | 3 ++- .../__tests__/ssrWatch.spec.ts | 27 +++++-------------- packages/server-renderer/src/render.ts | 1 + .../server-renderer/src/renderToStream.ts | 7 +++++ .../server-renderer/src/renderToString.ts | 6 +++++ 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 172097e2235..d499e45c974 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -40,6 +40,7 @@ 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' export type WatchEffect = (onCleanup: OnCleanup) => void @@ -281,6 +282,7 @@ function doWatch( // in SSR there is no need to setup an actual effect, and it should be noop // 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,7 +295,10 @@ function doWatch( onCleanup ]) } - if (flush !== 'sync') { + if (flush === 'sync') { + const ctx = useSSRContext()! + ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = []) + } else { return NOOP } } @@ -373,12 +378,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 diff --git a/packages/runtime-core/src/helpers/useSsrContext.ts b/packages/runtime-core/src/helpers/useSsrContext.ts index 5b75b6f806f..bec9bc1b85c 100644 --- a/packages/runtime-core/src/helpers/useSsrContext.ts +++ b/packages/runtime-core/src/helpers/useSsrContext.ts @@ -1,9 +1,10 @@ +import { SSRContext } from '@vue/server-renderer' import { inject } from '../apiInject' import { warn } from '../warning' export const ssrContextKey = Symbol(__DEV__ ? `ssrContext` : ``) -export const useSSRContext = >() => { +export const useSSRContext = () => { if (!__GLOBAL__) { const ctx = inject(ssrContextKey) if (!ctx) { diff --git a/packages/server-renderer/__tests__/ssrWatch.spec.ts b/packages/server-renderer/__tests__/ssrWatch.spec.ts index 34df80c3b4d..df085332dc0 100644 --- a/packages/server-renderer/__tests__/ssrWatch.spec.ts +++ b/packages/server-renderer/__tests__/ssrWatch.spec.ts @@ -1,5 +1,5 @@ import { createSSRApp, defineComponent, h, watch, ref } from 'vue' -import { renderToString } from '../src/renderToString' +import { SSRContext, renderToString } from '../src' describe('ssr: watch', () => { // #6013 @@ -18,26 +18,13 @@ describe('ssr: watch', () => { expect(msg).toBe('hello world') return () => h('div', null, msg) }) + const app = createSSRApp(App) - const html = await renderToString(app) + const ctx: SSRContext = {} + const html = await renderToString(app, ctx) + + expect(ctx.__watcherHandles!.length).toBe(1) + expect(html).toMatch('hello world') }) - test('should work w/ flush:sync', async () => { - const App = defineComponent(() => { - const count = ref(0) - let msg = 'abc' - watch( - count, - () => { - msg = 'hello world' - }, - { flush: 'sync' } - ) - expect(msg).toBe('abc') - return () => h('div', null, msg) - }) - const app = createSSRApp(App) - const html = await renderToString(app) - expect(html).toMatch('abc') - }) }) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index ae71a9e6275..225b59ed118 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -46,6 +46,7 @@ export type SSRContext = { [key: string]: any teleports?: Record __teleportBuffers?: Record + __watcherHandles?: (() => void)[] } // Each component has a buffer array. diff --git a/packages/server-renderer/src/renderToStream.ts b/packages/server-renderer/src/renderToStream.ts index 79484f9cffc..bdcdb0a026e 100644 --- a/packages/server-renderer/src/renderToStream.ts +++ b/packages/server-renderer/src/renderToStream.ts @@ -76,6 +76,13 @@ export function renderToSimpleStream( 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) diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 2450b0a3acb..daf68467f29 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -67,6 +67,12 @@ export async function renderToString( await resolveTeleports(context) + if (context.__watcherHandles) { + for (const unwatch of context.__watcherHandles) { + unwatch() + } + } + return result } From e93656ad19a9d78b07f21cc3346edc27edf7a13a Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 26 Oct 2022 18:25:09 +0800 Subject: [PATCH 5/5] fix: avoid import to vue/server-renderer in public types --- packages/runtime-core/src/apiWatch.ts | 3 ++- packages/runtime-core/src/helpers/useSsrContext.ts | 3 +-- packages/server-renderer/src/render.ts | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index d499e45c974..ea4bc3f8625 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -41,6 +41,7 @@ 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 @@ -296,7 +297,7 @@ function doWatch( ]) } if (flush === 'sync') { - const ctx = useSSRContext()! + const ctx = useSSRContext() as SSRContext ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = []) } else { return NOOP diff --git a/packages/runtime-core/src/helpers/useSsrContext.ts b/packages/runtime-core/src/helpers/useSsrContext.ts index bec9bc1b85c..5b75b6f806f 100644 --- a/packages/runtime-core/src/helpers/useSsrContext.ts +++ b/packages/runtime-core/src/helpers/useSsrContext.ts @@ -1,10 +1,9 @@ -import { SSRContext } from '@vue/server-renderer' import { inject } from '../apiInject' import { warn } from '../warning' export const ssrContextKey = Symbol(__DEV__ ? `ssrContext` : ``) -export const useSSRContext = () => { +export const useSSRContext = >() => { if (!__GLOBAL__) { const ctx = inject(ssrContextKey) if (!ctx) { diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 225b59ed118..379a5fbedf4 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -45,7 +45,13 @@ export type Props = Record export type SSRContext = { [key: string]: any teleports?: Record + /** + * @internal + */ __teleportBuffers?: Record + /** + * @internal + */ __watcherHandles?: (() => void)[] }