import { useStyleInject } from '../StyleContext'; import type { KeyType } from '../Cache'; import useCompatibleInsertionEffect from './useCompatibleInsertionEffect'; import useHMR from './useHMR'; import type { ShallowRef, Ref } from 'vue'; import { onBeforeUnmount, watch, computed } from 'vue'; export type ExtractStyle = ( cache: CacheValue, effectStyles: Record, options?: { plain?: boolean; }, ) => [order: number, styleId: string, style: string] | null; export default function useGlobalCache( prefix: string, keyPath: Ref, cacheFn: () => CacheType, onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void, // Add additional effect trigger by `useInsertionEffect` onCacheEffect?: (cachedValue: CacheType) => void, ): ShallowRef { const styleContext = useStyleInject(); const globalCache = computed(() => styleContext.value?.cache); const deps = computed(() => [prefix, ...keyPath.value].join('%')); const HMRUpdate = useHMR(); type UpdaterArgs = [times: number, cache: CacheType]; const buildCache = (updater?: (data: UpdaterArgs) => UpdaterArgs) => { globalCache.value.update(deps.value, prevCache => { const [times = 0, cache] = prevCache || [undefined, undefined]; // HMR should always ignore cache since developer may change it let tmpCache = cache; if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) { onCacheRemove?.(tmpCache, HMRUpdate); tmpCache = null; } const mergedCache = tmpCache || cacheFn(); const data: UpdaterArgs = [times, mergedCache]; // Call updater if need additional logic return updater ? updater(data) : data; }); }; watch( deps, () => { buildCache(); }, { immediate: true }, ); let cacheEntity = globalCache.value.get(deps.value); // HMR clean the cache but not trigger `useMemo` again // Let's fallback of this // ref https://github.com/ant-design/cssinjs/issues/127 if (process.env.NODE_ENV !== 'production' && !cacheEntity) { buildCache(); cacheEntity = globalCache.value.get(deps.value); } const cacheContent = computed( () => (globalCache.value.get(deps.value) && globalCache.value.get(deps.value)![1]) || cacheEntity![1], ); // Remove if no need anymore useCompatibleInsertionEffect( () => { onCacheEffect?.(cacheContent.value); }, polyfill => { // It's bad to call build again in effect. // But we have to do this since StrictMode will call effect twice // which will clear cache on the first time. buildCache(([times, cache]) => { if (polyfill && times === 0) { onCacheEffect?.(cacheContent.value); } return [times + 1, cache]; }); return () => { globalCache.value.update(deps.value, prevCache => { const [times = 0, cache] = prevCache || []; const nextCount = times - 1; if (nextCount <= 0) { if (polyfill || !globalCache.value.get(deps.value)) { onCacheRemove?.(cache, false); } return null; } return [times - 1, cache]; }); }; }, deps, ); onBeforeUnmount(() => { buildCache(); }); return cacheContent; }