From 8e1bafbc9921290e5601ab1b8b9ba0505aeb392d Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 25 Feb 2026 10:36:16 +0100 Subject: [PATCH] Core: Avoid performance bottlenecks when infering args for recursive calls on DOM elemens --- .../modules/store/inferArgTypes.ts | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/code/core/src/preview-api/modules/store/inferArgTypes.ts b/code/core/src/preview-api/modules/store/inferArgTypes.ts index 04637f18ac59..f4e69dd6b3c8 100644 --- a/code/core/src/preview-api/modules/store/inferArgTypes.ts +++ b/code/core/src/preview-api/modules/store/inferArgTypes.ts @@ -6,7 +6,12 @@ import { dedent } from 'ts-dedent'; import { combineParameters } from './parameters'; -const inferType = (value: any, name: string, visited: Set): SBType => { +const inferType = ( + value: any, + name: string, + visited: Set, + cache: Map +): SBType => { const type = typeof value; switch (type) { case 'boolean': @@ -19,6 +24,12 @@ const inferType = (value: any, name: string, visited: Set): SBType => { break; } if (value) { + // Check cache first for previously computed results + if (cache.has(value)) { + return cache.get(value)!; + } + + // Check for cycles (currently being processed in this path) if (visited.has(value)) { logger.warn(dedent` We've detected a cycle in arg '${name}'. Args should be JSON-serializable. @@ -29,25 +40,36 @@ const inferType = (value: any, name: string, visited: Set): SBType => { `); return { name: 'other', value: 'cyclic object' }; } + visited.add(value); + + let result: SBType; + if (Array.isArray(value)) { const childType: SBType = value.length > 0 - ? inferType(value[0], name, new Set(visited)) + ? inferType(value[0], name, visited, cache) : { name: 'other', value: 'unknown' }; - return { name: 'array', value: childType }; + result = { name: 'array', value: childType }; + } else { + const fieldTypes = mapValues(value, (field) => inferType(field, name, visited, cache)); + result = { name: 'object', value: fieldTypes }; } - const fieldTypes = mapValues(value, (field) => inferType(field, name, new Set(visited))); - return { name: 'object', value: fieldTypes }; + + visited.delete(value); // Remove from current path after processing + cache.set(value, result); // Cache the result for future lookups + + return result; } return { name: 'object', value: {} }; }; export const inferArgTypes: ArgTypesEnhancer = (context) => { const { id, argTypes: userArgTypes = {}, initialArgs = {} } = context; + const cache = new Map(); const argTypes = mapValues(initialArgs, (arg, key) => ({ name: key, - type: inferType(arg, `${id}.${key}`, new Set()), + type: inferType(arg, `${id}.${key}`, new Set(), cache), })); const userArgTypesNames = mapValues(userArgTypes, (argType, key) => ({ name: key,