Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up hook for memoization after server side rendering #8066

Open
justin-schroeder opened this issue Apr 11, 2023 · 2 comments
Open

Clean up hook for memoization after server side rendering #8066

justin-schroeder opened this issue Apr 11, 2023 · 2 comments
Labels
✨ feature request New feature or request

Comments

@justin-schroeder
Copy link

justin-schroeder commented Apr 11, 2023

What problem does this feature solve?

For performance it can be helpful to memoize the result of expensive operations within the module scope of an imported script. Lifecycle hooks on the front end (like onUnmounted) allow for this kind of cleanup, but currently there is no lifecycle hook to perform this operation on the server. Take for example:

// expensive.ts

const memo: Record<string, ExpensiveValue> = {}
const memoKeyTotals: Record<string, number> = {}

export function performExpensiveOperation (info: string): [ExpensiveValue, CleanUp] {
  
if (!memo[info]) {
    memo[info] = doExpensiveOperation(info)
    memoKeyTotals[info] = 0
  } else {
    memoKeyTotals[info]++
  }

  const clean = () => {
    memoKeyTotals--
    if (!memoKeyTotals) {
      delete memoKeyTotaks[info]
      delete memo[info]
    }
  }
  return [memo[info], clean]
}

The ExpensiveComponent.vue file:

<script setup>
import { onUnmounted } from 'vue'
import { performExpensiveOperation } from './expensive'

const props = defineProps(['info'])
const [value, cleanUp] = performExpensiveOperation(props.info)
// onUnmounted doesn’t run on the server so GC is never run.
onUnmounted(cleanUp)
</script>

<template>
  {{ value }}
</template>

What does the proposed API look like?

Proposal: onDestroyed

A lifecycle hook that is universally called after server and client render. Perhaps called onDestroyed().

<script setup>
import { onDestroyed } from 'vue'
import { performExpensiveOperation } from './expensive'

const props = defineProps(['info'])
const [value, cleanUp] = performExpensiveOperation(props.info)
onDestroyed(cleanUp). // 👀 Called on the client and the server
</script>

<template>
  {{ value }}
</template>

Depending on the mechanics of the server, it may necessary to indicate the finality of an app’s lifecycle to reliably perform the onDestroyed hook:

// server.mjs
import { createSSRApp } from 'vue'
import { renderToString, destroy } from '@vue/server-renderer'

const server = http.createServer((req, res) => {
  // Creates a new app on each request for context isolation:
  const app = createSSRApp({
    template: '<ExpensiveComponent />',
  })

  renderToString(app).then((html) => {
    destroy(app)
    res.statusCode = 200
    res.setHeader('Content-Type', 'text/html')
    res.end(`<!DOCTYPE />
    <html>
      ${html}
    </html>`)
  })
})

On the client this hook would be executed after all existing lifecycle hooks.

Possible alternative?

Node 14.6 added the FinalizationRegistry API which could theoretically be used server side to detect when an object within a component’s scope is garbage collected and trigger the appropriate cleanup operation.

@justin-schroeder justin-schroeder added the ✨ feature request New feature or request label Apr 11, 2023
@Craystyle1212

This comment was marked as off-topic.

@posva
Copy link
Member

posva commented Aug 9, 2023

Related to #4516

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ feature request New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants