perf(clipboard): index pasted node children (#511)

- Build parent-to-child indexes once while decoding Figma clipboard data

- Reuse the index for hierarchy traversal and internal component discovery

- Avoid quadratic scans that blocked large flat pastes
This commit is contained in:
Danila Poyarkov 2026-08-14 12:37:05 +03:00 committed by GitHub
parent 5190a64017
commit 3ee3708d71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 17 additions and 13 deletions

View file

@ -11,6 +11,7 @@
### Performance
- Index Figma clipboard children once during import instead of rescanning every pasted node, keeping large flat pastes linear. (#500)
- Reduce peak memory during `.fig` export by sharing immutable binary resources with the isolated export graph.
### Fixed

View file

@ -139,25 +139,31 @@ export function figmaNodesBounds(
interface ClipboardImportMaps {
guidMap: Map<string, KiwiNodeChange>
parentMap: Map<string, string>
childMap: Map<string, string[]>
}
function buildClipboardMaps(nodeChanges: KiwiNodeChange[]): ClipboardImportMaps {
const guidMap = new Map<string, KiwiNodeChange>()
const parentMap = new Map<string, string>()
const childMap = new Map<string, string[]>()
for (const nc of nodeChanges) {
if (!nc.guid) continue
const id = `${nc.guid.sessionID}:${nc.guid.localID}`
guidMap.set(id, nc)
if (nc.parentIndex?.guid) {
parentMap.set(id, `${nc.parentIndex.guid.sessionID}:${nc.parentIndex.guid.localID}`)
const parentId = `${nc.parentIndex.guid.sessionID}:${nc.parentIndex.guid.localID}`
parentMap.set(id, parentId)
const siblings = childMap.get(parentId)
if (siblings) siblings.push(id)
else childMap.set(parentId, [id])
}
}
return { guidMap, parentMap }
return { guidMap, parentMap, childMap }
}
function findInternalNodeIds(
guidMap: Map<string, KiwiNodeChange>,
parentMap: Map<string, string>
childMap: Map<string, string[]>
): { internalCanvasIds: Set<string>; internalFigmaIds: Set<string> } {
const internalCanvasIds = new Set<string>()
for (const [id, nc] of guidMap) {
@ -169,8 +175,8 @@ function findInternalNodeIds(
const internalFigmaIds = new Set<string>()
function markInternal(id: string) {
internalFigmaIds.add(id)
for (const [childId, pid] of parentMap) {
if (pid === id && !internalFigmaIds.has(childId)) markInternal(childId)
for (const childId of childMap.get(id) ?? []) {
if (!internalFigmaIds.has(childId)) markInternal(childId)
}
}
for (const canvasId of internalCanvasIds) markInternal(canvasId)
@ -230,8 +236,8 @@ export function importClipboardNodes(
offsetY = 0,
blobs: Uint8Array[] = []
): string[] {
const { guidMap, parentMap } = buildClipboardMaps(nodeChanges)
const { internalCanvasIds, internalFigmaIds } = findInternalNodeIds(guidMap, parentMap)
const { guidMap, parentMap, childMap } = buildClipboardMaps(nodeChanges)
const { internalCanvasIds, internalFigmaIds } = findInternalNodeIds(guidMap, childMap)
const { topLevel, internalTopLevel } = classifyTopLevelNodes(
guidMap,
parentMap,
@ -262,12 +268,9 @@ export function importClipboardNodes(
created.set(figmaId, node.id)
if (ourParentId === targetParentId && !internalFigmaIds.has(figmaId)) createdIds.push(node.id)
const children: string[] = []
for (const [childId, pid] of parentMap) {
if (pid === figmaId && !NON_VISUAL_TYPES.has(guidMap.get(childId)?.type ?? '')) {
children.push(childId)
}
}
const children = (childMap.get(figmaId) ?? []).filter(
(childId) => !NON_VISUAL_TYPES.has(guidMap.get(childId)?.type ?? '')
)
sortChildren(children, nc, guidMap)
for (const childId of children) {
createNode(childId, node.id)