From 06c5e821a4c487c6a47091e857c1fb0e9b438260 Mon Sep 17 00:00:00 2001 From: Jovines <1246634075@qq.com> Date: Thu, 22 Jan 2026 03:32:19 +0800 Subject: [PATCH] feat: lazy load large diffs to prevent page freeze (#186) When diff content exceeds 1500 lines or 150KB, show a placeholder with "Load Diff" button instead of parsing immediately. This prevents the page from freezing when viewing large diffs. Co-authored-by: Jovines --- .../src/components/views/PierreDiffViewer.tsx | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/views/PierreDiffViewer.tsx b/packages/ui/src/components/views/PierreDiffViewer.tsx index d6218ec3..2dbc79f4 100644 --- a/packages/ui/src/components/views/PierreDiffViewer.tsx +++ b/packages/ui/src/components/views/PierreDiffViewer.tsx @@ -2,7 +2,7 @@ import React, { useMemo, useRef, useState, useCallback, useEffect } from 'react' import { createPortal } from 'react-dom'; import { FileDiff } from '@pierre/diffs/react'; import { parseDiffFromFile, type FileContents, type FileDiffMetadata, type SelectedLineRange } from '@pierre/diffs'; -import { RiSendPlane2Line } from '@remixicon/react'; +import { RiArrowDownSLine, RiEyeLine, RiSendPlane2Line } from '@remixicon/react'; import { useOptionalThemeSystem } from '@/contexts/useThemeSystem'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; @@ -302,8 +302,21 @@ export const PierreDiffViewer: React.FC = ({ fileDiff: FileDiffMetadata; } | null>(null); - // Pre-parse the diff with cacheKey for worker pool caching + // Threshold for lazy loading (total lines > 1500 or content size > 150KB) + const isLargeDiff = useMemo(() => { + const totalLines = (original || '').split('\n').length + (modified || '').split('\n').length; + const totalSize = (original?.length || 0) + (modified?.length || 0); + return totalLines > 1500 || totalSize > 150 * 1024; + }, [original, modified]); + + // State for large diff loading + const [shouldLoad, setShouldLoad] = useState(false); + + // Parse diff when loaded (for large diffs) or always (for small diffs) const fileDiff = useMemo(() => { + // For large diffs, only parse if manually triggered + if (isLargeDiff && !shouldLoad) return null; + const cacheKey = getCacheKey(fileName, original, modified); // Return cached diff if inputs haven't changed @@ -331,7 +344,7 @@ export const PierreDiffViewer: React.FC = ({ diffCacheRef.current = { key: cacheKey, fileDiff: diff }; return diff; - }, [fileName, original, modified, language]); + }, [fileName, original, modified, language, isLargeDiff, shouldLoad]); const options = useMemo(() => ({ theme: { @@ -350,11 +363,45 @@ export const PierreDiffViewer: React.FC = ({ onLineSelected: handleSelectionChange, unsafeCSS: WEBKIT_SCROLL_FIX_CSS, }), [isDark, renderSideBySide, wrapLines, handleSelectionChange]); - + + // Show placeholder for large diffs + if (isLargeDiff && !fileDiff) { + const originalLines = (original || '').split('\n').length; + const modifiedLines = (modified || '').split('\n').length; + return ( +
+
+
+ +
+
+
Large Diff
+
+ {originalLines + modifiedLines} lines +
+
+ +
+
+ ); + } + if (typeof window === 'undefined') { return null; } - + + // fileDiff should not be null here (handled by large diff placeholder above) + if (!fileDiff) { + return null; + } + // Extracted Comment Interface Content for reuse in Portal or In-Flow const renderCommentContent = () => { if (!selection) return null;