Skip to content

Commit 3fe8adc

Browse files
Switch key areas to lazily import to improve Vite chunk size (Stirling-Tools#6278)
# Description of Changes Vite currently warns that when it's bundling our code that the chunk size is way too high because most of the imports are static so it can't split them into smaller chunks. This PR changes a few key areas to use lazy imports to try and make the chunks as small as possible with minimal code changes. Vite's warnings kick in at minified chunks being >500kB, and we've got a little way to go still to reach that, but we can keep chipping away at this and I'd rather get the biggest wins done now. I've also included Lighthouse scores because there's been discussion about improving ours recently. It's not the aim of this PR to improve it, but it's nice that it makes it a little better. ## Current main chunks Build split into 12 chunks. Largest chunk in build is: ``` [frontend:build] dist/assets/index-B6JiWDxZ.js 5,175.51 kB │ gzip: 1,495.85 kB ``` <img width="1442" height="775" alt="image" src="https://github.com/user-attachments/assets/b0e8a3fa-4ef3-4ccd-8c1d-bfed2d99bd27" /> Lighthouse score: <img width="423" height="146" alt="before" src="https://github.com/user-attachments/assets/c62056e8-2e77-49a6-a1ae-f08ec8021fb3" /> ## This PR's chunks Build split into 176 chunks. Largest chunk in build is: ``` [frontend:build] dist/assets/index-qCgeCY4B.js 2,878.54 kB │ gzip: 861.03 kB ``` <img width="1447" height="776" alt="image" src="https://github.com/user-attachments/assets/8d0c3cf0-cc25-41c3-b114-4940d3e99349" /> Lighthouse score: <img width="402" height="145" alt="after" src="https://github.com/user-attachments/assets/99a26eb3-bd15-4b92-bf22-82b58b458f52" /> --------- Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
1 parent 51f5345 commit 3fe8adc

11 files changed

Lines changed: 620 additions & 189 deletions

File tree

frontend/package-lock.json

Lines changed: 341 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
"postcss-simple-vars": "^7.0.1",
132132
"prettier": "^3.8.1",
133133
"puppeteer": "^24.25.0",
134+
"rollup-plugin-visualizer": "^7.0.1",
134135
"tsx": "^4.21.0",
135136
"typescript": "^5.9.2",
136137
"typescript-eslint": "^8.44.1",

frontend/src/core/components/layout/Workbench.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { useCallback } from "react";
2-
import { Box } from "@mantine/core";
1+
import { Suspense, lazy, useCallback } from "react";
2+
import { Box, Loader, Center } from "@mantine/core";
33
import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider";
44
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
55
import { useFileHandler } from "@app/hooks/useFileHandler";
@@ -16,14 +16,20 @@ import { FileId } from "@app/types/file";
1616
import styles from "@app/components/layout/Workbench.module.css";
1717

1818
import TopControls from "@app/components/shared/TopControls";
19-
import FileEditor from "@app/components/fileEditor/FileEditor";
20-
import PageEditor from "@app/components/pageEditor/PageEditor";
21-
import PageEditorControls from "@app/components/pageEditor/PageEditorControls";
22-
import Viewer from "@app/components/viewer/Viewer";
2319
import LandingPage from "@app/components/shared/LandingPage";
2420
import Footer from "@app/components/shared/Footer";
2521
import DismissAllErrorsButton from "@app/components/shared/DismissAllErrorsButton";
2622

23+
// Workbench panels are loaded on demand. Viewer pulls in pdfjs-dist and the
24+
// full @embedpdf plugin set; FileEditor/PageEditor are only needed once a file
25+
// is open. Lazy-loading keeps all of that out of the initial bundle.
26+
const FileEditor = lazy(() => import("@app/components/fileEditor/FileEditor"));
27+
const PageEditor = lazy(() => import("@app/components/pageEditor/PageEditor"));
28+
const PageEditorControls = lazy(
29+
() => import("@app/components/pageEditor/PageEditorControls"),
30+
);
31+
const Viewer = lazy(() => import("@app/components/viewer/Viewer"));
32+
2733
// No props needed - component uses contexts directly
2834
export default function Workbench() {
2935
const { isRainbowMode } = useRainbowThemeContext();
@@ -236,7 +242,15 @@ export default function Workbench() {
236242
...(currentView === "pageEditor" && { height: 0 }),
237243
}}
238244
>
239-
{renderMainContent()}
245+
<Suspense
246+
fallback={
247+
<Center style={{ height: "100%" }}>
248+
<Loader />
249+
</Center>
250+
}
251+
>
252+
{renderMainContent()}
253+
</Suspense>
240254
</Box>
241255

242256
<Footer
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Suspense, lazy, useEffect, useState } from "react";
2+
3+
// AppConfigModal pulls in the entire settings UI tree (admin sections,
4+
// account, supabase auth flows, etc.). We defer loading until the user first
5+
// opens the modal, then keep it mounted so the close animation runs.
6+
const AppConfigModal = lazy(
7+
() => import("@app/components/shared/AppConfigModal"),
8+
);
9+
10+
interface AppConfigModalLazyProps {
11+
opened: boolean;
12+
onClose: () => void;
13+
}
14+
15+
export default function AppConfigModalLazy({
16+
opened,
17+
onClose,
18+
}: AppConfigModalLazyProps) {
19+
const [shouldMount, setShouldMount] = useState(false);
20+
21+
useEffect(() => {
22+
if (opened) setShouldMount(true);
23+
}, [opened]);
24+
25+
if (!shouldMount) return null;
26+
27+
return (
28+
<Suspense fallback={null}>
29+
<AppConfigModal opened={opened} onClose={onClose} />
30+
</Suspense>
31+
);
32+
}

frontend/src/core/components/shared/QuickAccessBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import "@app/components/shared/quickAccessBar/QuickAccessBar.css";
3030
import { Tooltip } from "@app/components/shared/Tooltip";
3131
import AllToolsNavButton from "@app/components/shared/AllToolsNavButton";
3232
import ActiveToolButton from "@app/components/shared/quickAccessBar/ActiveToolButton";
33-
import AppConfigModal from "@app/components/shared/AppConfigModal";
33+
import AppConfigModal from "@app/components/shared/AppConfigModalLazy";
3434
import { useAppConfig } from "@app/contexts/AppConfigContext";
3535
import { useGroupSigningEnabled } from "@app/hooks/useGroupSigningEnabled";
3636
import { useSharingEnabled } from "@app/hooks/useSharingEnabled";

frontend/src/core/components/shared/signing/SignPopout.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useCallback, useRef } from "react";
1+
import { lazy, useState, useEffect, useCallback, useRef } from "react";
22
import { createPortal } from "react-dom";
33
import { useTranslation } from "react-i18next";
44
import { Drawer } from "@mantine/core";
@@ -23,8 +23,15 @@ import {
2323
import { useFileSelection } from "@app/contexts/file/fileHooks";
2424
import { fileStorage } from "@app/services/fileStorage";
2525
import { useFileActions } from "@app/contexts/FileContext";
26-
import SignRequestWorkbenchView from "@app/components/tools/certSign/SignRequestWorkbenchView";
27-
import SessionDetailWorkbenchView from "@app/components/tools/certSign/SessionDetailWorkbenchView";
26+
// These workbench views pull in the PDF viewer / pdfium / @embedpdf chain, so
27+
// they are loaded on demand when the certSign collab feature actually opens
28+
// one of them. Workbench wraps custom views in <Suspense>.
29+
const SignRequestWorkbenchView = lazy(
30+
() => import("@app/components/tools/certSign/SignRequestWorkbenchView"),
31+
);
32+
const SessionDetailWorkbenchView = lazy(
33+
() => import("@app/components/tools/certSign/SessionDetailWorkbenchView"),
34+
);
2835
import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from "@app/styles/zIndex";
2936

3037
export const SIGN_REQUEST_WORKBENCH_TYPE =

frontend/src/core/components/tools/automate/ToolConfigurationModal.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
import { useState, useEffect } from "react";
1+
import { Suspense, useState, useEffect } from "react";
22
import { useTranslation } from "react-i18next";
3-
import { Modal, Title, Button, Group, Stack, Text, Alert } from "@mantine/core";
3+
import {
4+
Modal,
5+
Title,
6+
Button,
7+
Group,
8+
Stack,
9+
Text,
10+
Alert,
11+
Loader,
12+
} from "@mantine/core";
413
import { Z_INDEX_AUTOMATE_MODAL } from "@app/styles/zIndex";
514
import SettingsIcon from "@mui/icons-material/Settings";
615
import CheckIcon from "@mui/icons-material/Check";
@@ -124,7 +133,9 @@ export default function ToolConfigurationModal({
124133
<div
125134
style={{ maxHeight: "60vh", overflowY: "auto", overflowX: "hidden" }}
126135
>
127-
{renderToolSettings()}
136+
<Suspense fallback={<Loader size="sm" />}>
137+
{renderToolSettings()}
138+
</Suspense>
128139
</div>
129140

130141
<Group justify="flex-end" gap="sm">

0 commit comments

Comments
 (0)