Skip to content

Commit fba0d3d

Browse files
Improving page load performance (#27)
* Improving page load performance * Clean up pending widget callbacks and avoid blocking WASM initialization on cache writes --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent bc9aa2b commit fba0d3d

17 files changed

Lines changed: 748 additions & 172 deletions

‎AGENTS.md‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ public/ # static files copied verbatim into out/:
134134
repositories.txt # synced from the repo root (sync-repositories.mjs)
135135
assets/ # mustache template, logo
136136
templates/ # workflow-sample-*.html fragments
137-
widgets/ # module bridges: vizdom WASM, three-spritetext,
137+
widgets/ # module bridges: vizdom WASM, force-graph
138+
# (3D graph + sprite labels sharing one
139+
# three build),
138140
# sentry (runtime CDN imports cannot be bundled)
139141
templates/ # user CI samples (user-refactorfirst-*.yml) for the docs
140142
ci/process-submissions.sh # shared submission validator for GH/Gl/BB CI
@@ -256,14 +258,30 @@ coverage there when introducing new markup patterns.
256258
lib/renderer.js applies **filter → sort → paginate** per table and injects
257259
`tableUi` blocks the mustache template renders; `enhanceTables` in
258260
lib/table-enhancer.js binds the controls and reports state changes back to
259-
components/report-view.jsx, which re-renders (widgets only gate the first
260-
render; the search input's focus/caret is restored after each re-render).
261-
The expensive `enhanceReport` pipeline (Chart.js charts, WASM DOT layout)
262-
runs only when the payload changes — table-state re-renders stash the live
263-
chart canvases / graph containers before the innerHTML swap and graft them
264-
back into the fresh DOM (`statefulElementIds`/`stashStatefulDom`/
265-
`graftStatefulDom` in lib/report-view.js), re-binding only the cheap popup
266-
handlers.
261+
components/report-view.jsx, which re-renders (widgets never gate the
262+
render — charts/graphs enhance progressively via `onWidgetReady`
263+
callbacks when their widget reports ready; the search input's focus/caret
264+
is restored after each re-render). The expensive `enhanceReport` pipeline
265+
(Chart.js charts, WASM DOT layout) runs only when the payload changes —
266+
it receives `{charts, graphs}` readiness flags so still-loading widgets
267+
are skipped and picked up by the widget-readiness effect instead — and
268+
table-state re-renders stash the live chart canvases / graph containers
269+
before the innerHTML swap and graft them back into the fresh DOM
270+
(`statefulElementIds`/`stashStatefulDom`/`graftStatefulDom` in
271+
lib/report-view.js), re-binding only the cheap popup handlers.
272+
- Initial render performance (LCP/CLS): the report markup renders as soon
273+
as the parallel template + report-JSON fetches resolve, never waiting on
274+
the CDN widgets or the ~4.8MB vizdom wasm. The report page shells
275+
(`[username]/[repository]/page.jsx` + `[branch]/page.jsx`) preconnect to
276+
raw.githubusercontent.com (the report JSON host; CORS fetch) and
277+
cdnjs.cloudflare.com (classic widget scripts) and render the loading
278+
placeholder inside `#report-container` — the same id the hydrated
279+
ReportView renders — with a full-viewport `min-height: 100svh` (vh
280+
fallback) in app/globals.css, so the footer starts below the fold and
281+
the report's arrival causes no layout shift. The vizdom bridge
282+
(`public/widgets/vizdom-bridge.js`) serves the wasm from Cache Storage
283+
(versioned cache name) so repeat visits never re-download it regardless
284+
of the host's HTTP cache headers.
267285
- Search `<input>`s are injected by table-enhancer — `<input>` is FORBID in
268286
the renderer's sanitization allow-list, so it must never appear in the
269287
mustache template.

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ lib/ # Shared logic consumed by client components and
5656
public/ # Published verbatim: repositories.txt, assets/
5757
# (mustache template, logo), templates/
5858
# (workflow samples), widgets/ (WASM + ESM
59-
# bridges for vizdom, three-spritetext, Sentry)
59+
# bridges for vizdom, force-graph, Sentry)
6060
repositories.txt # Listed repositories, one "user/repo" per line
6161
ci/process-submissions.sh # Shared submission validator used by GitHub
6262
# Actions, GitLab CI and Bitbucket Pipelines
@@ -436,7 +436,7 @@ in
436436
| URL routes | `lib/routes.js` (+ `tests/unit/routes.test.js`) |
437437
| Raw fetching / branch fallback (platform-aware) | `lib/fetcher.js` |
438438
| Mustache rendering | `lib/renderer.js`, `public/assets/refactor-first-report.mustache` (port of the RefactorFirst viewer template) |
439-
| Interactive report widgets | `lib/report-view.js` + CDN widgets loaded via `next/script` in `components/report-view.jsx` (Chart.js, sigma/graphology, graphlib-dot, svg-pan-zoom, 3d-force-graph) and the ES-module bridges in `public/widgets/` (vizdom WASM, three-spritetext) |
439+
| Interactive report widgets | `lib/report-view.js` + CDN widgets loaded via `next/script` in `components/report-view.jsx` (Chart.js, sigma/graphology, graphlib-dot, svg-pan-zoom) and the ES-module bridges in `public/widgets/` (vizdom WASM; force-graph: 3D graph + sprite labels sharing one pinned Three.js build) |
440440
| Search / type-ahead | `lib/search.js` + `components/{search-combobox,hero-search,menu-search}.jsx` |
441441
| Submission flow | `lib/repo-submission.js`, `components/repo-submission-form.jsx` |
442442
| Submission validation (CI) | `ci/process-submissions.sh`, `.github/workflows/add-repository.yml`, `.gitlab-ci.yml`, `bitbucket-pipelines.yml` |

‎app/[username]/[repository]/[branch]/page.jsx‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,26 @@ export default async function BranchPage({ params }) {
2727
notFound();
2828
}
2929
return (
30-
<Suspense fallback={<p className="loading" role="status">Loading report&hellip;</p>}>
31-
<ReportView username={username} repository={repository} branch={branch} />
32-
</Suspense>
30+
<>
31+
{/* Warm the connections the client-side report fetch and the lazy CDN
32+
widgets will use: raw.githubusercontent.com serves the report JSON
33+
(a CORS fetch, so the preconnect is anonymous too) and cdnjs the
34+
2D-popup scripts (classic scripts, no CORS). Self-managed
35+
GitLab/Bitbucket deployments fetch from their own platform host
36+
instead, where these hints are simply unused. React hoists both
37+
links into <head>. */}
38+
<link rel="preconnect" href="https://raw.githubusercontent.com" crossOrigin="anonymous" />
39+
<link rel="preconnect" href="https://cdnjs.cloudflare.com" />
40+
{/* The fallback mirrors the hydrated container (same id + reserved
41+
height, app/globals.css) so the footer starts below the fold before
42+
hydration too and the report's arrival causes no layout shift. */}
43+
<Suspense fallback={(
44+
<div id="report-container">
45+
<p className="loading" role="status">Loading report&hellip;</p>
46+
</div>
47+
)}>
48+
<ReportView username={username} repository={repository} branch={branch} />
49+
</Suspense>
50+
</>
3351
);
3452
}

‎app/[username]/[repository]/page.jsx‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,26 @@ export default async function RepositoryPage({ params }) {
2525
notFound();
2626
}
2727
return (
28-
<Suspense fallback={<p className="loading" role="status">Loading report&hellip;</p>}>
29-
<ReportView username={username} repository={repository} />
30-
</Suspense>
28+
<>
29+
{/* Warm the connections the client-side report fetch and the lazy CDN
30+
widgets will use: raw.githubusercontent.com serves the report JSON
31+
(a CORS fetch, so the preconnect is anonymous too) and cdnjs the
32+
2D-popup scripts (classic scripts, no CORS). Self-managed
33+
GitLab/Bitbucket deployments fetch from their own platform host
34+
instead, where these hints are simply unused. React hoists both
35+
links into <head>. */}
36+
<link rel="preconnect" href="https://raw.githubusercontent.com" crossOrigin="anonymous" />
37+
<link rel="preconnect" href="https://cdnjs.cloudflare.com" />
38+
{/* The fallback mirrors the hydrated container (same id + reserved
39+
height, app/globals.css) so the footer starts below the fold before
40+
hydration too and the report's arrival causes no layout shift. */}
41+
<Suspense fallback={(
42+
<div id="report-container">
43+
<p className="loading" role="status">Loading report&hellip;</p>
44+
</div>
45+
)}>
46+
<ReportView username={username} repository={repository} />
47+
</Suspense>
48+
</>
3149
);
3250
}

‎app/globals.css‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,21 @@ main section {
301301
padding: 3rem 0;
302302
}
303303

304+
/* Report pages fetch and render their report client-side; both the
305+
prerendered loading fallback and the hydrated container carry
306+
#report-container, so reserving a full viewport here keeps the footer
307+
below the fold from the very first paint — whatever the header's
308+
height. The report's late arrival then grows the page instead of
309+
shoving the visible footer down the screen (layout shift / CLS).
310+
Rendered reports are always far taller than this, so the reservation
311+
adds no space once content lands. svh is the guaranteed-visible
312+
viewport height on mobile (dynamic browser chrome); vh is the
313+
fallback for engines without viewport units. */
314+
#report-container {
315+
min-height: 100vh;
316+
min-height: 100svh;
317+
}
318+
304319
.site-footer {
305320
text-align: center;
306321
padding: 1.5rem 1rem;

0 commit comments

Comments
 (0)