perf: cut cold-start download 58% and startup heap 22% via measured chunk-graph fixes (#2742)
* fix(ui): update session-switch-resync test to current handleEvent/setSessionTodos signatures
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(build): split Shiki grammars/themes, CodeMirror legacy modes, and @pierre/diffs into on-demand chunks
Merging @shikijs/langs into one manual vendor chunk made the first language
request download every grammar (7.4 MB raw / 1 MB gzip). Letting Rollup split
these packages per dynamically imported module downloads only the languages,
themes, and modes actually used — matching how the worker build already
behaves. @pierre/diffs is split the same way so its pure patch parser (used by
the eager tool renderer) no longer drags the Shiki-importing render stack into
the startup graph.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(ui): restore lazy heavy views and stop eager settings-graph loading
- MainLayout: DiffView/FilesView/GitView/PlanView return to lazyWithChunkRecovery
(they were silently made static in 2031e3b4 while their Suspense wrappers
remained), keeping the CodeMirror and @pierre/diffs stacks out of startup.
- ContextPanel: same lazy treatment for its Diff/Files/Git/Plan/Walkthrough
tabs, with null Suspense fallbacks.
- CommandPalette imported getSettingsNavIcon from SettingsView, statically
pulling the entire settings surface (SkillsPage -> CodeMirrorEditor -> vim
mode, theme registry -> @pierre/diffs) into the eager graph; the helper now
lives in lib/settings/metadata.
- The windowed SettingsWindow mounts only after its first open: rendering the
lazy component closed made React fetch the SettingsView chunk graph at
startup.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(ui): keep @pierre/diffs + Shiki out of the eager chat graph and defer diff worker warmup
- DiffWorkerProvider no longer statically imports @pierre/diffs/worker or the
theme registry, and no longer spawns 3 workers plus a main-thread shared
highlighter during mount. Pools are created on demand through a dynamic
module load, warmed via requestIdleCallback after startup settles, and
useWorkerPool notifies consumers when a pool becomes available.
- ToolPart's rich diff preview moves to lazily loaded ToolPartDiffPreview;
the plain-text patch (PlainDiffFallback) renders while the chunk loads,
mirroring the existing error fallback. Theme registration happens during
render inside the lazy module so PatchDiff never renders unregistered ids.
- ChatInput mounts its lazy ToolOutputDialog only after the first attachment
preview opens instead of fetching the dialog chunk on the draft screen.
- getMarkdownSyntaxVars moves to a pierre-free markdownSyntaxVars module so
eager code-rendering consumers stop importing the registration module.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(web): load ghostty-web and Nerd Fonts on first terminal use
- ghostty-web (638 KB raw JS + WASM VT) is dynamically imported when a
terminal actually mounts; TerminalView stays eagerly importable for the
bottom dock.
- The ~2 MB of CDN Nerd Fonts are no longer preloaded and force-loaded on
every cold start. index.html exposes an idempotent
__openchamberEnsureNerdFonts hook; TerminalViewport requests it on mount
and waits up to 2s so a cached font is in place before the glyph atlas is
built, while a cold CDN fetch never blocks the terminal. Runtimes without
the hook (VS Code, mobile) resolve immediately, matching their existing
fallback-font behavior.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
---------
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
Serhii Dziupin
parent
da3d467f82
commit
bc380e6e1b
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { FitAddon, Ghostty, Terminal as GhosttyTerminal } from 'ghostty-web';
|
||||
import type { FitAddon, Ghostty, Terminal as GhosttyTerminal } from 'ghostty-web';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { TerminalTheme } from '@/lib/terminalTheme';
|
||||
@@ -15,8 +15,33 @@ import {
|
||||
} from '@/lib/terminalTouchSelection';
|
||||
import type { TerminalChunk } from '@/stores/useTerminalStore';
|
||||
|
||||
let ghosttyPromise: Promise<Ghostty> | null = null;
|
||||
const loadGhostty = (): Promise<Ghostty> => ghosttyPromise ??= Ghostty.load();
|
||||
// ghostty-web (638 KB raw of JS + the WASM VT) loads on demand: TerminalView
|
||||
// stays eagerly importable for the bottom dock without pulling the emulator
|
||||
// into the startup graph before a terminal is actually mounted.
|
||||
type GhosttyModule = typeof import('ghostty-web');
|
||||
type GhosttyRuntime = { module: GhosttyModule; ghostty: Ghostty };
|
||||
let ghosttyRuntimePromise: Promise<GhosttyRuntime> | null = null;
|
||||
const loadGhostty = (): Promise<GhosttyRuntime> =>
|
||||
ghosttyRuntimePromise ??= import('ghostty-web').then(async (module) => ({
|
||||
module,
|
||||
ghostty: await module.Ghostty.load(),
|
||||
}));
|
||||
|
||||
// The web entry defers its ~2 MB Nerd Font download until a terminal actually
|
||||
// mounts (see the `__openchamberEnsureNerdFonts` hook in index.html). Wait for
|
||||
// it with a short bound so a cached font is in place before the glyph atlas is
|
||||
// built, while a cold CDN fetch never blocks the terminal from opening; the
|
||||
// runtimes without the hook (VS Code, mobile) resolve immediately.
|
||||
const NERD_FONT_WAIT_MS = 2000;
|
||||
const ensureNerdFonts = (): Promise<void> => {
|
||||
if (typeof window === 'undefined') return Promise.resolve();
|
||||
const loader = (window as typeof window & { __openchamberEnsureNerdFonts?: () => Promise<void> }).__openchamberEnsureNerdFonts;
|
||||
if (typeof loader !== 'function') return Promise.resolve();
|
||||
return Promise.race([
|
||||
Promise.resolve(loader()).catch(() => undefined),
|
||||
new Promise<void>((resolve) => setTimeout(resolve, NERD_FONT_WAIT_MS)),
|
||||
]).then(() => undefined);
|
||||
};
|
||||
|
||||
type TerminalSize = { cols: number; rows: number };
|
||||
|
||||
@@ -211,13 +236,13 @@ const TerminalViewport = React.forwardRef<TerminalController, Props>(({
|
||||
window.addEventListener('focus', handleWindowFocus);
|
||||
window.addEventListener('blur', handleWindowBlur);
|
||||
|
||||
loadGhostty().then((ghostty) => {
|
||||
Promise.all([loadGhostty(), ensureNerdFonts()]).then(([{ module, ghostty }]) => {
|
||||
if (disposed) return;
|
||||
terminal = new GhosttyTerminal({
|
||||
terminal = new module.Terminal({
|
||||
...getGhosttyTerminalOptions(fontFamily, fontSize, theme, ghostty, false),
|
||||
...(provisionalSizeRef.current ?? {}),
|
||||
});
|
||||
const fitAddon = new FitAddon();
|
||||
const fitAddon = new module.FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.open(container);
|
||||
terminalRef.current = terminal;
|
||||
|
||||
Reference in New Issue
Block a user