Skip to content

Commit 30d5d93

Browse files
edison1105yyx990803
authored andcommitted
fix(ssr): avoid computed being accidentally cached before server render (#9688)
close #5300
1 parent 8d74ca0 commit 30d5d93

File tree

3 files changed

+50
-3
lines changed

3 files changed

+50
-3
lines changed

Diff for: packages/server-renderer/__tests__/render.spec.ts

+46-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import {
1717
renderSlot,
1818
onErrorCaptured,
1919
onServerPrefetch,
20-
getCurrentInstance
20+
getCurrentInstance,
21+
reactive,
22+
computed,
23+
createSSRApp
2124
} from 'vue'
2225
import { escapeHtml } from '@vue/shared'
2326
import { renderToString } from '../src/renderToString'
@@ -1140,5 +1143,47 @@ function testRender(type: string, render: typeof renderToString) {
11401143
expect(renderError).toBe(null)
11411144
expect((capturedError as unknown as Error).message).toBe('An error')
11421145
})
1146+
1147+
test('computed reactivity during SSR with onServerPrefetch', async () => {
1148+
const store = {
1149+
// initial state could be hydrated
1150+
state: reactive({ items: null as null | string[] }),
1151+
1152+
// pretend to fetch some data from an api
1153+
async fetchData() {
1154+
this.state.items = ['hello', 'world']
1155+
}
1156+
}
1157+
1158+
const getterSpy = vi.fn()
1159+
1160+
const App = defineComponent(() => {
1161+
const msg = computed(() => {
1162+
getterSpy()
1163+
return store.state.items?.join(' ')
1164+
})
1165+
1166+
// If msg value is falsy then we are either in ssr context or on the client
1167+
// and the initial state was not modified/hydrated.
1168+
// In both cases we need to fetch data.
1169+
onServerPrefetch(() => store.fetchData())
1170+
1171+
// simulate the read from a composable (e.g. filtering a list of results)
1172+
msg.value
1173+
1174+
return () => h('div', null, msg.value)
1175+
})
1176+
1177+
const app = createSSRApp(App)
1178+
1179+
// in real world serve this html and append store state for hydration on client
1180+
const html = await renderToString(app)
1181+
1182+
expect(html).toMatch('hello world')
1183+
1184+
// should only be called twice since access should be cached
1185+
// during the render phase
1186+
expect(getterSpy).toHaveBeenCalledTimes(2)
1187+
})
11431188
})
11441189
}

Diff for: packages/server-renderer/__tests__/ssrComputed.spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ test('computed reactivity during SSR', async () => {
3333
// In both cases we need to fetch data.
3434
if (!msg.value) await store.fetchData()
3535

36-
expect(msg.value).toBe('hello world')
3736
return () => h('div', null, msg.value + msg.value + msg.value)
3837
})
3938

Diff for: packages/server-renderer/src/render.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ function renderComponentSubTree(
144144
// perf: enable caching of computed getters during render
145145
// since there cannot be state mutations during render.
146146
for (const e of instance.scope.effects) {
147-
if (e.computed) e.computed._cacheable = true
147+
if (e.computed) {
148+
e.computed._dirty = true
149+
e.computed._cacheable = true
150+
}
148151
}
149152

150153
const ssrRender = instance.ssrRender || comp.ssrRender

0 commit comments

Comments
 (0)