From 05dd270a9a9f5e53170592ab72f0d24a5765d774 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Jun 2026 11:26:11 +0000 Subject: [PATCH] perf(ui): lazy-load html-to-image and snapDOM Both are only needed on explicit user actions (export message as image, capture preview annotation screenshot). Defer them with dynamic import so ~140 KB leaves the eager app-shell graph. Co-authored-by: Serhii Dziupin --- packages/ui/src/components/chat/message/MessageBody.tsx | 4 +++- packages/ui/src/lib/preview/screenshot-capture.ts | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/chat/message/MessageBody.tsx b/packages/ui/src/components/chat/message/MessageBody.tsx index e2e36000..4bfcf251 100644 --- a/packages/ui/src/components/chat/message/MessageBody.tsx +++ b/packages/ui/src/components/chat/message/MessageBody.tsx @@ -33,7 +33,6 @@ import { copyTextToClipboard } from '@/lib/clipboard'; import { useChatSurfaceMode } from '@/components/chat/useChatSurfaceMode'; import { isVSCodeRuntime } from '@/lib/desktop'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; -import { toPng } from 'html-to-image'; import { toast } from '@/components/ui'; import { Icon } from "@/components/icon/Icon"; import { formatTimestampForDisplay } from './timeFormat'; @@ -1455,6 +1454,9 @@ const AssistantMessageBody = React.memo(({ wrapper.appendChild(clone); document.body.appendChild(wrapper); + // Lazy-load html-to-image: only needed when the user exports a + // message as an image, so keep it out of the eager app shell. + const { toPng } = await import('html-to-image'); const dataUrl = await toPng(wrapper, { quality: 1, pixelRatio: 2, diff --git a/packages/ui/src/lib/preview/screenshot-capture.ts b/packages/ui/src/lib/preview/screenshot-capture.ts index a587a943..28611fef 100644 --- a/packages/ui/src/lib/preview/screenshot-capture.ts +++ b/packages/ui/src/lib/preview/screenshot-capture.ts @@ -1,5 +1,3 @@ -import { snapdom } from '@zumer/snapdom'; -import { getFontEmbedCSS, toJpeg } from 'html-to-image'; import { invokeDesktop } from '@/lib/desktop'; import { runtimeFetch } from '@/lib/runtime-fetch'; @@ -825,6 +823,9 @@ async function captureIframeSnapdomScreenshot( // Defensive: undo any nested-scroll drift from asset inlining before capture. nestedScroll.reapply(); + // Lazy-load snapDOM: only needed when actually capturing a preview + // annotation screenshot, so keep it out of the eager app shell. + const { snapdom } = await import('@zumer/snapdom'); const snapdomOptions = { backgroundColor: getCaptureBackgroundColor(document), cache: 'disabled' as const, @@ -924,6 +925,8 @@ async function captureIframeDomScreenshot( await document.fonts?.ready.catch(() => undefined); restoreAssets = await inlineIframeCaptureAssets(document, viewportWidth, viewportHeight, { applyLayoutWorkarounds: true }); frameWindow.scrollTo(scrollX, scrollY); + // Lazy-load html-to-image: only the fallback DOM-capture path needs it. + const { getFontEmbedCSS, toJpeg } = await import('html-to-image'); const fontEmbedCSS = await getFontEmbedCSS(root).catch(() => ''); dataUrl = await toJpeg(root, {