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 <jovines@qq.com>
This commit is contained in:
Jovines
2026-01-21 21:32:19 +02:00
committed by GitHub
co-authored by Jovines
parent d01b58593a
commit 06c5e821a4
@@ -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<PierreDiffViewerProps> = ({
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<PierreDiffViewerProps> = ({
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<PierreDiffViewerProps> = ({
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 (
<div className="flex flex-col items-center justify-center h-full min-h-[200px] p-6">
<div className="flex flex-col items-center gap-3 text-center">
<div className="p-3 rounded-full bg-muted/50">
<RiEyeLine className="size-6 text-muted-foreground" />
</div>
<div>
<div className="typography-ui-header font-medium text-foreground">Large Diff</div>
<div className="typography-meta text-muted-foreground mt-1">
{originalLines + modifiedLines} lines
</div>
</div>
<button
type="button"
onClick={() => setShouldLoad(true)}
className="flex items-center gap-2 px-4 py-2 mt-2 rounded-lg bg-primary/10 text-primary hover:bg-primary/20 transition-colors typography-meta font-medium"
>
<RiArrowDownSLine className="size-4" />
Load Diff
</button>
</div>
</div>
);
}
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;