mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-23 15:49:42 -04:00
refine the nunjuck cache
This commit is contained in:
@@ -56,10 +56,8 @@ async function _highlightNunjucksTags(
|
||||
showVariableSourceAndValue: boolean,
|
||||
editorId: string,
|
||||
) {
|
||||
const renderCacheKey = Math.random() + '';
|
||||
|
||||
const renderString = (text: any) => render(text, renderCacheKey);
|
||||
const renderContextWithCacheKey = () => renderContext(renderCacheKey);
|
||||
const renderString = (text: any) => render(text);
|
||||
const renderContextWithCacheKey = () => renderContext();
|
||||
|
||||
const activeMarks: CodeMirror.TextMarker[] = [];
|
||||
const doc: CodeMirror.Doc = this.getDoc();
|
||||
|
||||
@@ -66,7 +66,7 @@ export const OneLineEditor = forwardRef<OneLineEditorHandle, OneLineEditorProps>
|
||||
const codeMirror = useRef<CodeMirror.EditorFromTextArea | null>(null);
|
||||
const { settings } = useRootLoaderData()!;
|
||||
const { isOwner, isEnterprisePlan } = usePlanData();
|
||||
const { handleRender, handleGetRenderContext } = useNunjucks();
|
||||
const { handleRender, handleGetRenderContext } = useNunjucks({ enableCache: true });
|
||||
|
||||
const getKeyMap = useCallback(() => {
|
||||
if (!readOnly && settings.enableKeyMapForInlineTextEditors && settings.editorKeyMap) {
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
import { getRenderContext, getRenderContextAncestors, render } from '~/common/render';
|
||||
import { useWorkspaceLoaderData } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId';
|
||||
import { useRequestLoaderData } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId';
|
||||
import { useRequestGroupLoaderData } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request-group.$requestGroupId';
|
||||
import { NUNJUCKS_TEMPLATE_GLOBAL_PROPERTY_NAME } from '~/templating';
|
||||
import type { HandleRender, RenderContextOptions } from '~/templating/types';
|
||||
import type { BaseRenderContext, HandleRender, RenderContextOptions } from '~/templating/types';
|
||||
import { getKeys } from '~/templating/utils';
|
||||
|
||||
let getRenderContextPromiseCache: any = {};
|
||||
|
||||
export interface UseNunjucksOptions {
|
||||
renderContext: Pick<Partial<RenderContextOptions>, 'purpose' | 'extraInfo'>;
|
||||
interface CacheOptions {
|
||||
//If true, will cache the render context
|
||||
enableCache?: boolean;
|
||||
//Cache duration in seconds, default is 5 seconds
|
||||
cacheDuration?: number;
|
||||
}
|
||||
export const initializeNunjucksRenderPromiseCache = () => {
|
||||
getRenderContextPromiseCache = {};
|
||||
};
|
||||
|
||||
initializeNunjucksRenderPromiseCache();
|
||||
export type UseNunjucksOptions = {
|
||||
renderContext?: Pick<Partial<RenderContextOptions>, 'purpose' | 'extraInfo'>;
|
||||
} & CacheOptions;
|
||||
|
||||
const defaultCacheDurationInSeconds = 5;
|
||||
/**
|
||||
* Access to functions useful for Nunjucks rendering
|
||||
*
|
||||
* Customized hook simplifies template rendering in React components by:
|
||||
* - Retrieving render context based on current request/folder/workspace
|
||||
* - Providing optional caching to optimize performance and avoid race conditions
|
||||
* - Managing cache lifecycle
|
||||
*
|
||||
* @param options - Configuration options for rendering and caching
|
||||
* @param options.renderContext.purpose - Purpose of the render operation (e.g., 'send', 'preview')
|
||||
* @param options.renderContext.extraInfo - Additional information to include in render context
|
||||
* @param options.enableCache - Whether to cache the render context. Mainly used for editors with many nunjucks to render
|
||||
* @param options.cacheDuration - How long to keep the cached context in seconds (default: 5)
|
||||
*
|
||||
*/
|
||||
export const useNunjucks = (options?: UseNunjucksOptions) => {
|
||||
// for all types of requests
|
||||
@@ -28,6 +39,9 @@ export const useNunjucks = (options?: UseNunjucksOptions) => {
|
||||
// for request group (folder)
|
||||
const { activeRequestGroup } = useRequestGroupLoaderData() || {};
|
||||
const workspaceData = useWorkspaceLoaderData();
|
||||
const { enableCache = false, cacheDuration = defaultCacheDurationInSeconds } = options || {};
|
||||
const renderContextPromiseCache = useRef<Promise<BaseRenderContext>>();
|
||||
const renderContextPromiseCacheTimer = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const fetchRenderContext = useCallback(async () => {
|
||||
const ancestors = await getRenderContextAncestors(
|
||||
@@ -41,49 +55,68 @@ export const useNunjucks = (options?: UseNunjucksOptions) => {
|
||||
});
|
||||
}, [
|
||||
requestData?.activeRequest,
|
||||
activeRequestGroup,
|
||||
workspaceData?.activeWorkspace,
|
||||
workspaceData?.activeEnvironment._id,
|
||||
options?.renderContext,
|
||||
activeRequestGroup,
|
||||
]);
|
||||
|
||||
const handleGetRenderContext = useCallback(
|
||||
async (contextCacheKey?: string) => {
|
||||
const context =
|
||||
contextCacheKey && getRenderContextPromiseCache[contextCacheKey]
|
||||
? await getRenderContextPromiseCache[contextCacheKey]
|
||||
: await fetchRenderContext();
|
||||
const keys = getKeys(context, NUNJUCKS_TEMPLATE_GLOBAL_PROPERTY_NAME);
|
||||
return { context, keys };
|
||||
},
|
||||
[fetchRenderContext],
|
||||
);
|
||||
const handleGetRenderContext = useCallback(async () => {
|
||||
const context =
|
||||
enableCache && renderContextPromiseCache.current
|
||||
? await renderContextPromiseCache.current
|
||||
: await fetchRenderContext();
|
||||
const keys = getKeys(context, NUNJUCKS_TEMPLATE_GLOBAL_PROPERTY_NAME);
|
||||
return { context, keys };
|
||||
}, [enableCache, fetchRenderContext]);
|
||||
/**
|
||||
* Heavily optimized render function
|
||||
*
|
||||
* @param text - template to render
|
||||
* @param contextCacheKey - if rendering multiple times in parallel, set this
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
const handleRender: HandleRender = useCallback(
|
||||
async <T>(obj: T, contextCacheKey: string | null = null) => {
|
||||
if (!contextCacheKey || !getRenderContextPromiseCache[contextCacheKey]) {
|
||||
// NOTE: We're caching promises here to avoid race conditions
|
||||
// @ts-expect-error -- TSCONVERSION contextCacheKey being null used as object index
|
||||
getRenderContextPromiseCache[contextCacheKey] = fetchRenderContext();
|
||||
async <T>(obj: T) => {
|
||||
let getOrCreateRenderContext: ReturnType<typeof fetchRenderContext>;
|
||||
if (enableCache) {
|
||||
if (!renderContextPromiseCache.current) {
|
||||
// create a new context promise to optimize performance and avoid race conditions
|
||||
console.log(`[templating] Create Nunjucks render context cache`);
|
||||
renderContextPromiseCache.current = fetchRenderContext();
|
||||
// Implement time-based cache invalidation strategy for performance optimization:
|
||||
// 1. Cache the render context to avoid expensive re-computation for multiple nunjucks
|
||||
// 2. Set timeout to clear cache after specified duration to ensure context freshness
|
||||
// 3. This pattern is mainly used for editors that render many nunjucks simultaneously
|
||||
// (e.g., code-editor, one-line-editor)
|
||||
// 4. With this approach, all templates rendered during the cache window share the same
|
||||
// context promise, avoiding redundant context creation and race conditions
|
||||
renderContextPromiseCacheTimer.current = setTimeout(() => {
|
||||
console.log(`[templating] Remove Nunjucks render context cache`);
|
||||
renderContextPromiseCache.current = undefined;
|
||||
}, cacheDuration * 1000);
|
||||
}
|
||||
getOrCreateRenderContext = renderContextPromiseCache.current;
|
||||
} else {
|
||||
getOrCreateRenderContext = fetchRenderContext();
|
||||
}
|
||||
|
||||
// Set timeout to delete the key eventually
|
||||
// @ts-expect-error -- TSCONVERSION contextCacheKey being null used as object index
|
||||
setTimeout(() => delete getRenderContextPromiseCache[contextCacheKey], 5000);
|
||||
// @ts-expect-error -- TSCONVERSION contextCacheKey being null used as object index
|
||||
const context = await getRenderContextPromiseCache[contextCacheKey];
|
||||
const context = await getOrCreateRenderContext;
|
||||
return render({ obj, context });
|
||||
},
|
||||
[fetchRenderContext],
|
||||
[enableCache, fetchRenderContext, cacheDuration],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = renderContextPromiseCacheTimer.current;
|
||||
return () => {
|
||||
console.log(`[templating] Clear Nunjucks render context cache on unmount`);
|
||||
// clear timeout when unmount
|
||||
timer && clearTimeout(timer);
|
||||
renderContextPromiseCache.current = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
handleRender,
|
||||
handleGetRenderContext,
|
||||
|
||||
Reference in New Issue
Block a user