Optimize memory usage for large PDFs via chunked processing #10
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "batchedConversion"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Key Changes:
Chunked Processing: Pages are now processed in batches of 50. Each batch is compressed and stored temporarily, preventing the browser from holding thousands of raw canvas elements in memory simultaneously.
Memory Management: Added explicit cleanup for canvas contexts and PDF page resources (page.cleanup()) after processing each page.
Optimized Preview: The DOM preview is now limited to the first 20 pages to keep the UI responsive, while still converting the entire document in the background.
Garbage Collection: Canvas buffers are explicitly cleared for pages not shown in the preview to assist the browser's garbage collector.
Results:
Significantly reduced RAM consumption (preventing the
~15GB spikes previously observed).
Eliminated browser crashes on large files.
Maintained high-quality output (scale 3) without sacrificing resolution for performance.
Pull request overview
This PR optimizes memory usage for large PDF conversions by implementing chunked processing to prevent browser crashes from excessive RAM consumption (previously reaching ~15GB spikes).
Key Changes:
page.cleanup())💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
@ -258,2 +258,3 @@const pdfDoc = await PDFLib.PDFDocument.create();// Initialize PDF document later or in chunksconst pdf = await pdfjsLib.getDocument({ data: pdfData }).promise;The comment says "Initialize PDF document later or in chunks" but this line of code was removed. The comment is now orphaned and should be removed or rewritten to explain why the PDF document initialization was deferred.
@ -293,0 +302,4 @@await page.render(renderContext).promise;if (renderId !== currentRenderId) return;[nitpick] The
CHUNK_SIZEmagic number (50) should be defined as a constant at the top of the file or function scope for better maintainability. Consider adding a comment explaining why 50 was chosen as the optimal chunk size.@ -351,1 +396,4 @@if (renderId !== currentRenderId) return;progressContainer.style.display = 'none';[nitpick] The preview page limit (20) is a magic number that should be defined as a named constant (e.g.,
MAX_PREVIEW_PAGES) for better maintainability. This value is used in two places (lines 334 and 358) and having it as a constant would make it easier to adjust and maintain.Missing error handling for the
canvas.toBlob()promise. If the blob conversion fails or the blob is null,blob.arrayBuffer()will throw an error. Consider adding explicit error handling:[nitpick] The delay of 10ms may not be sufficient for garbage collection to occur, and
setTimeoutdoesn't guarantee GC will run. Modern browsers typically perform GC automatically based on memory pressure. Consider removing this artificial delay or increasing it if UI responsiveness is truly impacted. The comment should also clarify that this is primarily for UI updates, not GC.[nitpick] Setting
chunks[i] = nullmay not effectively release memory. The chunk bytes are already loaded intochunkDocand the array still holds the reference until the loop completes. Consider usingchunks.length = 0orchunks = []after the loop to fully release the array, or avoid storing chunks altogether by merging them immediately after creation.