UNPKG

nuxt-multi-cache

Version:

SSR route, component and data cache for Nuxt.js

191 lines (190 loc) 5.49 kB
import { defineComponent, useSSRContext, useSlots, getCurrentInstance, h } from "vue"; import { useNuxtApp } from "#app"; import { encodeComponentCacheItem } from "../../helpers/cacheItem.js"; import { logger } from "../../helpers/logger.js"; import { getExpiresValue, getMultiCacheContext, getCacheKeyWithPrefix } from "./../../helpers/server.js"; import { getCacheKey, getCachedComponent, renderSlot } from "./helpers/index.js"; import { useRuntimeConfig } from "#imports"; export default defineComponent({ name: "RenderCacheable", props: { /** * The tag to use for the wrapper. It's unfortunately not possible to * implement this without a wrapper. */ tag: { type: String, default: "div" }, /** * Disable caching entirely for this component. */ noCache: { type: Boolean, default: false }, /** * The key to use for the cache entry. If left empty a key is automatically * generated based on the props passed to the child. * The key is automatically prefixed by the component name. */ cacheKey: { type: String, default: "" }, /** * Cache tags that can be later used for invalidation. */ cacheTags: { type: Array, default: () => [] }, /** * Define a max age for the cached entry. */ maxAge: { type: Number, default: 0 }, /** * Provide the async data keys used by the cached component. * * If provided the payload data will be cached alongside the component. * If the component uses asyncData and the keys are not provided you will * receive a hydration mismatch error in the client. */ asyncDataKeys: { type: Array, default: () => [] } }, async setup(props) { const slots = useSlots(); if (!slots.default) { return () => ""; } const defaultSlot = slots.default(); const first = defaultSlot[0]; const isServer = import.meta.server || import.meta.env.VITEST; if (isServer && !props.noCache) { const { debug } = useRuntimeConfig().multiCache || {}; const cacheKey = getCacheKey(props, first, debug); if (!cacheKey) { return () => h(props.tag, slots.default()); } const currentInstance = getCurrentInstance(); const ssrContext = useSSRContext(); if (!ssrContext) { if (debug) { logger.warn("Failed to get SSR context.", props); } return () => h(props.tag, slots.default()); } const getOrCreateCachedComponent = async () => { if (!currentInstance?.parent) { if (debug) { logger.warn( "Failed to get parent component in Cacheable component.", props ); } return; } const nuxtApp = useNuxtApp(); const multiCache = getMultiCacheContext(ssrContext.event); if (!multiCache?.component) { return; } const fullCacheKey = getCacheKeyWithPrefix(cacheKey, ssrContext.event); const cached = await getCachedComponent( multiCache.component, fullCacheKey ); if (cached) { const { data: data2, payload, expires } = cached; if (expires) { const now = Date.now() / 1e3; if (now >= expires) { return; } } if (payload) { Object.keys(payload).forEach((key) => { nuxtApp.payload.data[key] = payload[key]; }); } if (debug) { logger.success("Returning cached component.", { fullCacheKey, payload: payload ? Object.keys(payload) : [], expires, props }); } return data2; } const data = await renderSlot(slots, currentInstance.parent); try { const cacheTags = props.cacheTags; const payload = props.asyncDataKeys.reduce((acc, key) => { acc[key] = nuxtApp.payload.data[key]; return acc; }, {}); const expires = props.maxAge ? getExpiresValue(props.maxAge) : void 0; multiCache.component.setItemRaw( fullCacheKey, encodeComponentCacheItem(data, payload, expires, cacheTags), { ttl: props.maxAge } ); if (debug) { logger.log("Stored component in cache.", { file: currentInstance.type.__file, fullCacheKey, expires, cacheTags }); } } catch (e) { if (debug) { logger.error("Failed to store component in cache.", { fullCacheKey, props }); } if (e instanceof Error) { console.error(e.message); } } return data; }; try { const cachedMarkup = await getOrCreateCachedComponent(); if (cachedMarkup) { return () => h(props.tag, { innerHTML: cachedMarkup }); } } catch (e) { if (debug) { logger.error("Failed to get component from cache.", { props }); } if (e instanceof Error) { console.error(e.message); } } } return () => h(props.tag, slots.default()); } });