One solution is a counter that tracks the number of consumers of a particular style:
- When a component using this hook mounts:
- add the style to
<head>
- increment the counter
- 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
}
})
}
As a memory optimization, consider storing a hash of the styles in memory instead of the style strings themselves:
import { onBeforeMount, onUnmounted } from 'vue'
// https://stackoverflow.com/a/7616484/6277151
const hashCode = (str: string) => {
let hash = 0
if (str.length === 0) return hash
for (let i = 0; i < str.length; i++) {
let chr = str.charCodeAt(i)
hash = ((hash << 5) - hash) + chr
hash |= 0 // Convert to 32bit integer
}
return hash
}
// map of injected CSS style hashes and their number of consumers
const injectedStyles = {} as Record<string, number>
function useGlobalStyles(cssStyles: string) {
let styleEl: HTMLStyleElement
const styleHash = hashCode(cssStyles)
onBeforeMount(() => {
if (injectedStyles[styleHash] === undefined) {
injectedStyles[styleHash] = 1
} else {
injectedStyles[styleHash]++
// style already injected, no need to do anything
return
}
styleEl = document.createElement('style')
styleEl.textContent = cssStyles
document.head.appendChild(styleEl)
})
onUnmounted(() => {
injectedStyles[styleHash]--
if (injectedStyles[styleHash] <= 0) {
delete injectedStyles[styleHash]
styleEl?.remove()
styleEl = undefined
}
})
}