Home > Net >  How to avoid memory leaks when adding css to <head>
How to avoid memory leaks when adding css to <head>

Time:08-05

So I wrote this vue hook to inject global css programmatically

    const injectedStyles = new Set<string>()
    
    function useGlobalStyles(cssStyles: string) {
        let styleEl: HTMLStyleElement
    
        onBeforeMount(() => {
            if (injectedStyles.has(cssStyles)) {
                return
            } else {
                injectedStyles.add(cssStyles)
            }
    
            styleEl = document.createElement('style')
            styleEl.textContent = cssStyles
            document.head.appendChild(styleEl)
        })
    }

It works, but it will keep style tag in head even if component is unmounted. Fixing this is the difficult part for me. I think it would required keeping track of all component instances that depend on these particular cssStyles, but then wouldn't it prevent garbage collector from disposing of them?

CodePudding user response:

One solution is a counter that tracks the number of consumers of a particular style:

  1. When a component using this hook mounts:
    • add the style to <head>
    • increment the counter
  2. When that component unmounts:
    • decrement the counter
    • if the resulting count is zero, remove the style from <head>

Note this solution doesn't require tracking component instances, so there's no possibility of a memory leak here.

import { onBeforeMount, onUnmounted } from 'vue'

// map of injected CSS styles and their number of consumers
const injectedStyles = {} as Record<string, number>

function useGlobalStyles(cssStyles: string) {
    let styleEl: HTMLStyleElement

    onBeforeMount(() => {
        if (injectedStyles[cssStyles] === undefined) {
          injectedStyles[cssStyles] = 1
        } else {
          injectedStyles[cssStyles]  

          // style already injected, no need to do anything
          return
        }

        styleEl = document.createElement('style')
        styleEl.textContent = cssStyles
        document.head.appendChild(styleEl)
    })

    onUnmounted(() => {
        injectedStyles[cssStyles]--
        if (injectedStyles[cssStyles] <= 0) {
            delete injectedStyles[cssStyles]
            styleEl?.remove()
            styleEl = undefined
        }
    })
}
  • Related