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?
One solution is a counter that tracks the number of consumers of a particular style:
<head>
<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
}
})
}