116 lines
3.3 KiB
Vue
116 lines
3.3 KiB
Vue
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<CacheValue> = (
|
|
cache: CacheValue,
|
|
effectStyles: Record<string, boolean>,
|
|
options?: {
|
|
plain?: boolean;
|
|
},
|
|
) => [order: number, styleId: string, style: string] | null;
|
|
|
|
export default function useGlobalCache<CacheType>(
|
|
prefix: string,
|
|
keyPath: Ref<KeyType[]>,
|
|
cacheFn: () => CacheType,
|
|
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
|
|
// Add additional effect trigger by `useInsertionEffect`
|
|
onCacheEffect?: (cachedValue: CacheType) => void,
|
|
): ShallowRef<CacheType> {
|
|
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;
|
|
}
|