ssrComputed.spec.ts 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. import { vi } from 'vitest'
  2. import { createSSRApp, defineComponent, h, computed, reactive } from 'vue'
  3. import { renderToString } from '../src/renderToString'
  4. // #5208 reported memory leak of keeping computed alive during SSR
  5. // so we made computed properties created during SSR non-reactive in
  6. // https://github.com/vuejs/core/commit/f4f0966b33863ac0fca6a20cf9e8ddfbb311ae87
  7. // However, the default caching leads to #5300 which is tested below.
  8. // In Vue 2, computed properties are simple getters during SSR - this can be
  9. // inefficient if an expensive computed is accessed multiple times during render,
  10. // but because of potential mutations, we cannot cache it until we enter the
  11. // render phase (where no mutations can happen anymore)
  12. test('computed reactivity during SSR', async () => {
  13. const store = {
  14. // initial state could be hydrated
  15. state: reactive({ items: null }) as any,
  16. // pretend to fetch some data from an api
  17. async fetchData() {
  18. this.state.items = ['hello', 'world']
  19. }
  20. }
  21. const getterSpy = vi.fn()
  22. const App = defineComponent(async () => {
  23. const msg = computed(() => {
  24. getterSpy()
  25. return store.state.items?.join(' ')
  26. })
  27. // If msg value is falsy then we are either in ssr context or on the client
  28. // and the initial state was not modified/hydrated.
  29. // In both cases we need to fetch data.
  30. if (!msg.value) await store.fetchData()
  31. expect(msg.value).toBe('hello world')
  32. return () => h('div', null, msg.value + msg.value + msg.value)
  33. })
  34. const app = createSSRApp(App)
  35. const html = await renderToString(app)
  36. expect(html).toMatch('hello world')
  37. // should only be called twice since access should be cached
  38. // during the render phase
  39. expect(getterSpy).toHaveBeenCalledTimes(2)
  40. })