Load the composer, previews, dialogs and other sidebars on demand #382

Closed
opened 2026-09-16 16:48:19 +00:00 by jcoffey-dev · 0 comments
Owner

Summary

The main chunk carried everything the mail view might open, whether or not it ever did. A source-map breakdown of index-*.js showed these avoidable pieces (minified sizes):

  • the file preview with marked (~49 KB);
  • the composer, rich-text editor and pickers (~30 KB);
  • ContactEditor (14 KB);
  • the calendar, contacts and files sidebars (~22 KB);
  • the filter and share dialogs, plus the Sieve code they bring (~24 KB).

Each is now a React.lazy component inside a Suspense boundary with no fallback:

Component Where it loads
FilePreviewDialog MessageView, and only mounted while a preview is open. The Files view already loaded lazily, so it is unchanged.
FilterFromMessageDialog MessageView and MessageList
ContactEditor AddressMenu
ShareDialog MailboxTree
CalendarSidebar, ContactsSidebar, FilesTree AppShell, loaded with their sections, whose views were already lazy
Composer ComposerDock

The composer is also fetched ahead of time. It's requested with requestIdleCallback after startup, falling back to a 2-second timeout, so the first Compose doesn't wait on the network.

notify.ts is imported statically now. It was imported both statically and dynamically, so the dynamic imports split nothing, and Vite warned about it (INEFFECTIVE_DYNAMIC_IMPORT). It only imports basePath, so importing it statically can't create an import cycle.

main This branch
index-*.js 598 KB (183 KB compressed) 460 KB (145 KB compressed)
Everything index.html loads, gzip -9 273 KB 236 KB (−14%)

Both build warnings are gone: the ineffective dynamic import, and the chunk over 500 kB.

Related issues

None.

Translations

Adds none. npm run i18n:check reports the same counts as main.

Testing

  • npm test -w web passes (1,320 tests). composer-dock.test.tsx now waits for the lazy composer to arrive before checking the dock. tsc and vite build are clean.
  • Checked in Chrome against the production build served on port 8080 by npm run dev:mock. Resource timing confirmed that each chunk arrived only when needed:
    • The composer was fetched while idle, before any click, and opened with its full editor.
    • The file preview wasn't loaded at startup. Clicking an image attachment fetched it, and the dialog showed the image.
    • "Edit contact" from a sender's right-click menu fetched ContactEditor and opened the editor with its fields.
    • "Filter messages like this…" from a row's context menu fetched FilterFromMessage and opened the dialog.
    • The calendar, contacts and files sidebars weren't loaded at startup; each loaded with its section and rendered its content.
    • Not checked in the browser: the share dialog from the mail folder tree. Its only entry point there is "Stop sharing" on a folder that is already shared, and the mock inbox has none. It's wired the same way as the five above.

Merged 2026-09-16 as coffey-labs/ihasmail@9560ad06f4

Rebuilt from: GH Archive, git history, session transcript.

## Summary The main chunk carried everything the mail view might open, whether or not it ever did. A source-map breakdown of `index-*.js` showed these avoidable pieces (minified sizes): - the file preview with `marked` (~49 KB); - the composer, rich-text editor and pickers (~30 KB); - `ContactEditor` (14 KB); - the calendar, contacts and files sidebars (~22 KB); - the filter and share dialogs, plus the Sieve code they bring (~24 KB). Each is now a `React.lazy` component inside a `Suspense` boundary with no fallback: | Component | Where it loads | |---|---| | `FilePreviewDialog` | `MessageView`, and only mounted while a preview is open. The Files view already loaded lazily, so it is unchanged. | | `FilterFromMessageDialog` | `MessageView` and `MessageList` | | `ContactEditor` | `AddressMenu` | | `ShareDialog` | `MailboxTree` | | `CalendarSidebar`, `ContactsSidebar`, `FilesTree` | `AppShell`, loaded with their sections, whose views were already lazy | | `Composer` | `ComposerDock` | **The composer is also fetched ahead of time.** It's requested with `requestIdleCallback` after startup, falling back to a 2-second timeout, so the first Compose doesn't wait on the network. **`notify.ts` is imported statically now.** It was imported both statically and dynamically, so the dynamic imports split nothing, and Vite warned about it (`INEFFECTIVE_DYNAMIC_IMPORT`). It only imports `basePath`, so importing it statically can't create an import cycle. | | `main` | This branch | |---|---|---| | `index-*.js` | 598 KB (183 KB compressed) | **460 KB (145 KB compressed)** | | Everything `index.html` loads, gzip -9 | 273 KB | **236 KB (−14%)** | Both build warnings are gone: the ineffective dynamic import, and the chunk over 500 kB. ## Related issues None. ## Translations Adds none. `npm run i18n:check` reports the same counts as `main`. ## Testing - `npm test -w web` passes (1,320 tests). `composer-dock.test.tsx` now waits for the lazy composer to arrive before checking the dock. `tsc` and `vite build` are clean. - Checked in Chrome against the production build served on port 8080 by `npm run dev:mock`. Resource timing confirmed that each chunk arrived only when needed: - The composer was fetched while idle, before any click, and opened with its full editor. - The file preview wasn't loaded at startup. Clicking an image attachment fetched it, and the dialog showed the image. - "Edit contact" from a sender's right-click menu fetched `ContactEditor` and opened the editor with its fields. - "Filter messages like this…" from a row's context menu fetched `FilterFromMessage` and opened the dialog. - The calendar, contacts and files sidebars weren't loaded at startup; each loaded with its section and rendered its content. - **Not checked in the browser:** the share dialog from the mail folder tree. Its only entry point there is "Stop sharing" on a folder that is already shared, and the mock inbox has none. It's wired the same way as the five above. **Merged** 2026-09-16 as coffey-labs/ihasmail@9560ad06f435 <sub>Rebuilt from: GH Archive, git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.