One attachment entry: splitting preview paths by format
Focus on dispatch: classify capability by extension first, lazy-load a parser per format, and set explicit limits for sheets, PDF pages and download fallback instead of one modal that owns every file type.
- Attachment preview
- PDF.js
- SheetJS
- docx-preview
- Code splitting
Related case: Smart ledger and order management. This note follows a local attachment-preview module’s dispatch and render boundaries. Examples keep the type decision and limits, and omit business URLs and host-bridge details. Compatibility is covered separately in previews that fail in an older WebView.
One “Preview” button is several capability paths
In a ledger or order view, “Preview” feels like one action. Implementation may be text, a spreadsheet, Word or PDF. If every parser lives in one component:
- opening the list can pull PDF/Excel dependencies and slow the first paint;
- unsupported types show a silent blank, so users do not know whether to download or retry;
- large sheets or long PDFs can freeze the main thread and look like a crash.
A clearer boundary: the entry only decides “what kind of file is this, and can we preview it”; each format owns “how to render, and how far.”
Classify first, then choose a renderer
Locally, preview type is derived from the URL/extension, then dispatched:
type PreviewType = 'image' | 'pdf' | 'text' | 'excel' | 'docx' | 'download'
function attachmentPreviewType(url: string): PreviewType {
if (/\.(?:jpe?g|png|gif|webp|svg)(?:$|[?#])/i.test(url)) return 'image'
const ext = extensionOf(url)
if (ext === '.pdf') return 'pdf'
if (ext === '.txt') return 'text'
if (ext === '.xlsx' || ext === '.xls') return 'excel'
if (ext === '.docx') return 'docx'
return 'download'
}
Three points matter:
- unknown types degrade to download instead of guessing a MIME and forcing a renderer;
- classification is separate from rendering, so list items can show a type chip without parsing the file;
- network errors and parse errors are different messages—HTTP failure is not “workbook has no sheets”.
Lazy-load parsers and publish display limits
Import libraries only when needed: SheetJS for Excel, docx-preview for Word, PDF.js for PDF. Attachments that are never opened never pay for those bundles.
Limits belong in the interaction, not only in comments:
- Excel: cap rows/columns (for example first 1000 rows, 100 columns) and say “showing a subset”;
- PDF: cap pages (for example first 30) so long documents do not flood canvases;
- text: empty files get an explicit “0 bytes” note instead of a blank box.
Open attachment
├─ type = image/pdf/text/excel/docx
│ └─ lazy-load renderer → bounded preview + download
└─ type = download
└─ download/host save path, no parser
Download is a first-class path, not a failure
Preview capability will always be smaller than “whatever users upload”. Treating “not previewable, please download” as a success branch is more honest than pretending every file can render.
Inside a desktop host, download may also need a native save API that keeps the original filename. Keep browser a[download] and host-bridge save under one download entry, instead of making the preview component guess the runtime.
Verify with minimal samples per format
Cover at least: a small PDF, a multi-page PDF, a multi-sheet xlsx, an empty txt, an unsupported extension, and a 4xx on download. Check that only used dependencies load, limit notices appear, failures are actionable, and host downloads keep filenames.
Split preview into classify → lazy render → explicit limits → download fallback, and the entry stays stable while formats keep changing.