Skip to content

chore(quickstart): defer NFS UI to async chunks and slim legacy exports - #4853

Open
debsmita1 wants to merge 1 commit into
redhat-developer:mainfrom
debsmita1:quickstart-optimization
Open

debsmita1 wants to merge 1 commit into
redhat-developer:mainfrom
debsmita1:quickstart-optimization

Conversation

@debsmita1

@debsmita1 debsmita1 commented Sep 17, 2026

Copy link
Copy Markdown
Member

Hey, I just made a Pull Request!

Resolves:
https://redhat.atlassian.net/browse/RHIDP-15553

This PR reduces the quickstart plugin’s NFS Module Federation sync footprint by deferring UI that was previously loaded eagerly at plugin registration time. The NFS entry (./) now registers only blueprint wiring and thin React.lazy boundaries; drawer UI, snackbar init, help menu content, and MUI item icons load via async chunks when actually needed.

Problem
Chunk analysis showed the quickstart NFS entry (./) at 464.8 KB across 4 sync chunks, ~294 KB larger than the OFS legacy entry (./legacy, 170.9 KB / 3 chunks). Plugin UI was pulled into the sync graph through:

Eager blueprint mounts — AppDrawerContentBlueprint and AppRootElementBlueprint used static elements (, ), sync-loading drawer UI, Snackbar/MUI, and icons even when the drawer was closed.
Sync help menu component — GlobalHeaderMenuItemBlueprint referenced QuickstartHelpMenuItem synchronously.
Static MUI icon map — eight @mui/icons-material imports in QuickstartItemIcon landed on the drawer sync path.
OFS eager legacy overrides — legacyExports.ts re-exported concrete QuickstartDrawerProvider / QuickstartButton instead of the lazy extensions already defined in plugin.ts.

Solution description:

  • Thin NFS entry — index.tsx registers blueprint wiring only; no static imports of drawer, init, or help menu UI.
  • Lazy drawer and init — React.lazy + Suspense shells in lazyQuickstartUi.tsx keep QuickstartDrawerContent and QuickstartInit off the NFS sync graph.
  • Deferred help menu — GlobalHeaderMenuItemBlueprint uses loader: () => import('./QuickstartHelpMenuItem') instead of a static component, so the help item loads only when the dropdown opens.
  • Lightweight help item — Replaced global-header/components with a locally styled MUI MenuItem and QuickstartIcon, preserving the “Quick start” entry while dropping ~1.2 MB of vendor async (core-components / markdown) from the quickstart MF graph.
  • Icon resolution — Added QuickstartIcon (system icons via app.getSystemIcon(), with Material ligature/SVG/URL fallbacks) for help, CTAs, and expand/collapse; refactored QuickstartItemIcon to resolve config icons dynamically while keeping built-in step icons on MUI outlined components in the drawer async chunk.
    @backstage/ui CTAs — QuickstartCtaLink migrates from @backstage/core-components LinkButton to @backstage/ui Button / ButtonLink.
  • Slim legacy exports — legacyExports.ts drops eager concrete component re-exports; OFS consumers use lazy extensions from plugin.ts, with quickstartTranslations / quickstartTranslationRef exported directly.

Expose summary

Metric BEFORE AFTER (HEAD) Δ
Sync chunks 3 3 0
Sync size 149 KB 158 KB +9 KB (+6.1%)
Async chunks 5 15 +10
Async size 26 KB 1,318 KB +1,292 KB (+5,034.2%)

NFS entry chunk (__federation_expose_default_export): 14 KB → 5 KB (−8 KB, −61.3%)

Sync chunk breakdown

Chunk BEFORE AFTER
Framework lodash 69 KB + zod 67 KB backstage 86 KB + zod 67 KB
NFS entry 14 KB 5 KB

The entry chunk shrank 61%, but total sync rose slightly because the framework sync chunk shifted from lodash (69 KB) to backstage (86 KB) — driven by the global-header 3.0 and @backstage/ui dependency updates in this commit, not by quickstart UI code staying on the sync graph.

Async growth (on-demand only)

Category BEFORE AFTER
Quickstart UI async 5 chunks / 26 KB 8 chunks / ~42 KB
Vendor async 0 7 chunks / ~1,275 KB

Vendor async is dominated by highlight.js (~852 KB) and parse5 (~182 KB), reachable through the drawer/CTA dependency graph (@backstage/ui, markdown rendering). These load only when the drawer or help menu is opened, not at plugin startup.

Screenshots:

Screenshot 2026-09-17 at 7 40 20 PM

With fallback icon

Screenshot 2026-09-17 at 7 45 54 PM

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

@rhdh-gh-app

rhdh-gh-app Bot commented Sep 17, 2026

Copy link
Copy Markdown

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
app-legacy workspaces/quickstart/packages/app-legacy none v0.0.0
app workspaces/quickstart/packages/app none v0.0.0
@red-hat-developer-hub/backstage-plugin-quickstart workspaces/quickstart/plugins/quickstart minor v2.1.0

@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.66667% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.63%. Comparing base (947374a) to head (e944a1a).
⚠️ Report is 32 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4853      +/-   ##
==========================================
+ Coverage   63.62%   63.63%   +0.01%     
==========================================
  Files        2693     2697       +4     
  Lines      107215   107243      +28     
  Branches    29996    30006      +10     
==========================================
+ Hits        68215    68244      +29     
+ Misses      38494    38492       -2     
- Partials      506      507       +1     
Flag Coverage Δ *Carryforward flag
adoption-insights 84.77% <ø> (ø) Carriedforward from 947374a
ai-integrations 82.63% <ø> (ø) Carriedforward from 947374a
app-defaults 63.39% <ø> (ø) Carriedforward from 947374a
augment 46.67% <ø> (ø) Carriedforward from 947374a
boost 84.97% <ø> (ø) Carriedforward from 947374a
bulk-import 73.12% <ø> (ø) Carriedforward from 947374a
cost-management 13.53% <ø> (ø) Carriedforward from 947374a
dcm 73.47% <ø> (ø) Carriedforward from 947374a
e2e-adoption-insights 60.00% <ø> (ø) Carriedforward from 947374a
e2e-extensions 62.31% <ø> (ø) Carriedforward from 947374a
e2e-global-header 51.82% <ø> (ø) Carriedforward from 947374a
e2e-homepage 61.11% <ø> (ø) Carriedforward from 947374a
e2e-intelligent-assistant 46.01% <ø> (ø) Carriedforward from 947374a
e2e-orchestrator 49.49% <ø> (ø) Carriedforward from 947374a
e2e-orchestrator-plugin 49.48% <ø> (ø) Carriedforward from 947374a
e2e-quickstart 55.03% <ø> (-0.19%) ⬇️ Carriedforward from 947374a
e2e-scorecard 50.00% <ø> (ø) Carriedforward from 947374a
e2e-theme 16.36% <ø> (ø) Carriedforward from 947374a
extensions 58.30% <ø> (ø) Carriedforward from 947374a
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 947374a
global-header 67.76% <ø> (ø) Carriedforward from 947374a
homepage 55.05% <ø> (ø) Carriedforward from 947374a
install-dynamic-plugins 71.77% <ø> (ø) Carriedforward from 947374a
intelligent-assistant 78.04% <ø> (ø) Carriedforward from 947374a
konflux 91.98% <ø> (ø) Carriedforward from 947374a
lightspeed 69.02% <ø> (ø) Carriedforward from 947374a
mcp-integrations 84.46% <ø> (ø) Carriedforward from 947374a
orchestrator 77.69% <ø> (ø) Carriedforward from 947374a
quickstart 65.70% <94.66%> (+1.96%) ⬆️
sandbox 79.56% <ø> (ø) Carriedforward from 947374a
scorecard 88.71% <ø> (ø) Carriedforward from 947374a
theme 87.94% <ø> (ø) Carriedforward from 947374a
translations 5.12% <ø> (ø) Carriedforward from 947374a
x2a 78.44% <ø> (ø) Carriedforward from 947374a

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 947374a...e944a1a. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@debsmita1

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:15 PM UTC · Completed 2:42 PM UTC

Commit: 57fc8af · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $8.95

@debsmita1
debsmita1 force-pushed the quickstart-optimization branch 2 times, most recently from 57fc8af to 72be27e Compare September 17, 2026 14:16
@fullsend-ai-review fullsend-ai-review Bot added the risk/elevated PR risk: elevated label Sep 17, 2026
@fullsend-ai-review

Copy link
Copy Markdown

Risk Assessment: elevated (3/5)

Details

A mid-size refactor (28 files, 1058 lines, large blast radius) with four dependency files changed and a borderline test ratio drives an elevated score, offset by no security exposure, no CI changes, and an experienced returning author.

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

Medium

  • [Logic error] workspaces/quickstart/plugins/quickstart/src/components/QuickstartContent/QuickstartItemIcon.tsx:70 — The sx prop is applied twice when rendering common icons: once on the wrapping Box via spread ({ display: 'flex', alignItems: 'center', ...sx }) and again on <CommonIcon sx={sx} />. Any spacing, sizing, or transform values in sx will be applied to both the container and the icon, producing incorrect layout. The Lightspeed branch above applies sx only to the Box.
    Remediation: Remove sx={sx} from <CommonIcon> on line 70.

  • [version-bump-mismatch] workspaces/quickstart/.changeset/quickstart-nfs-sync-optimization.md:2 — The changeset marks this as patch, but the PR adds two new @public exports (quickstartTranslationRef, quickstartTranslations) and changes exported type signatures for QuickstartButton and QuickstartDrawerProvider (concrete function types → React.ComponentType). Under semver, new public exports warrant at least minor.
    Remediation: Change the changeset from patch to minor.

  • [exported type signature change] workspaces/quickstart/plugins/quickstart/report-legacy.api.md:34QuickstartButton's exported type changed from (input: QuickstartButtonProps) => JSX_2.Element | null to React.ComponentType<QuickstartButtonProps> because the explicit re-export was removed from legacyExports.ts; the surviving wildcard re-export from plugin.ts uses the broader type. Consumers with explicit type annotations may see TypeScript errors.
    Remediation: Document this type-level change in the changeset.

  • [exported type signature change] workspaces/quickstart/plugins/quickstart/report-legacy.api.md:59QuickstartDrawerProvider's type similarly changed from (input: PropsWithChildren) => JSX_2.Element to React.ComponentType<PropsWithChildren>.
    Remediation: Document in the changeset.

Low

  • [Test adequacy] workspaces/quickstart/plugins/quickstart/src/components/QuickstartContent/QuickstartItemIcon.test.tsx — Tests for Import, Catalog, SelfService, and Learning common icons removed without replacement. Tests for http://, relative paths, and data:image/ URIs also removed; QuickstartIcon's tests only cover https:// and SVG.

  • [Code organization] workspaces/quickstart/plugins/quickstart/src/components/QuickstartContent/QuickstartItemIcon.tsx:18 — Blank line between react and @mui imports breaks the established contiguous-external-imports convention.

  • [API shape patterns] workspaces/quickstart/plugins/quickstart/src/QuickstartHelpMenuItem.tsx:80marginRight: '0.5rem' uses raw CSS in an sx block that otherwise uses MUI numeric spacing tokens.

  • [Inconsistent parallel mappings] workspaces/quickstart/plugins/quickstart/src/components/QuickstartContent/quickstartIconIds.ts:23quickstartLegacyIconAliases and commonIcons map the same legacy PascalCase IDs to different icons.

  • [style-divergence-risk] workspaces/quickstart/plugins/quickstart/src/QuickstartHelpMenuItem.tsx:46 — Locally styled MUI MenuItem duplicates global-header's dropdown entry structure; not guarded by a visual regression test.

  • [dependency version bump] workspaces/quickstart/plugins/quickstart/package.json:50global-header bumped from ^2.0.1 to ^3.0.0 (regular dependency, not peer).

  • [undocumented-major-dependency-upgrade] workspaces/quickstart/plugins/quickstart/package.json:50 — The global-header 3.x upgrade enables the loader blueprint API; partially documented in the changeset.

  • [scope-creep] workspaces/quickstart/plugins/quickstart/src/components/QuickstartContent/QuickstartCtaLink.tsx:30 — Replacing LinkButton with ButtonLink changes CTA DOM role from button to link; part of removing @backstage/core-components from the sync graph.

  • [stale identifier reference] workspaces/quickstart/plugins/quickstart/README.md:36 — OFS config lists QuickstartDrawerProvider, QuickstartDrawerStateExposer, QuickstartButton as importName values — removed from explicit re-exports but still accessible via export * from './plugin'.

  • [stale identifier reference] workspaces/quickstart/plugins/quickstart/README.md:59 — Static installation imports QuickstartDrawerProvider — still works via wildcard re-export.

  • [imprecise description] workspaces/quickstart/plugins/quickstart/README.md:135 — CTA schema says text: Button text but element renders as ButtonLink.

  • [version classification] workspaces/quickstart/.changeset/quickstart-nfs-sync-optimization.md:2 — See [version-bump-mismatch] above.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: The following review comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • workspaces/quickstart/plugins/quickstart/README.md (file-level): Line 36 · [low] stale identifier reference

OFS config lists QuickstartDrawerProvider, QuickstartDrawerStateExposer, and QuickstartButton as importName values. These were removed from explicit re-exports in legacyExports.ts but remain accessible via export * from './plugin'.

  • workspaces/quickstart/plugins/quickstart/README.md (file-level): Line 59 · [low] stale identifier reference

Static installation example imports QuickstartDrawerProvider. Still works via wildcard re-export but the explicit export path was removed.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread workspaces/quickstart/.changeset/quickstart-nfs-sync-optimization.md Outdated
export const QuickstartButton: (
input: QuickstartButtonProps,
) => JSX_2.Element | null;
export const QuickstartButton: React.ComponentType<QuickstartButtonProps>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] exported type signature change

QuickstartButton's exported type changed from (input: QuickstartButtonProps) => JSX_2.Element | null to React.ComponentType because the explicit re-export was removed from legacyExports.ts; the surviving wildcard re-export from plugin.ts uses the broader type. Consumers with explicit type annotations may see TypeScript errors.

Suggested fix: Document this type-level change in the changeset.

export const QuickstartDrawerProvider: (
input: PropsWithChildren,
) => JSX_2.Element;
export const QuickstartDrawerProvider: React.ComponentType<PropsWithChildren>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] exported type signature change

QuickstartDrawerProvider's type similarly changed from (input: PropsWithChildren) => JSX_2.Element to React.ComponentType.

Suggested fix: Document in the changeset.

Comment thread workspaces/quickstart/plugins/quickstart/package.json
Comment thread workspaces/quickstart/plugins/quickstart/package.json
export const QuickstartCtaLink = ({ cta, onClick }: QuickstartCtaLinkProps) => {
const { t } = useTranslation();
// If no CTA is provided, show a default "Got it!" button

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] scope-creep

Replacing LinkButton with ButtonLink changes CTA DOM role from button to link (confirmed by test changes from getByRole('button') to getByRole('link')). Part of the NFS optimization goal of removing @backstage/core-components from the sync graph.

Comment thread workspaces/quickstart/plugins/quickstart/README.md Outdated
Comment thread workspaces/quickstart/.changeset/quickstart-nfs-sync-optimization.md Outdated
@debsmita1
debsmita1 force-pushed the quickstart-optimization branch from 9986da4 to e944a1a Compare September 18, 2026 11:42
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
4.3% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@ciiay

ciiay commented Sep 21, 2026

Copy link
Copy Markdown
Member

Two things are blocking this for me:

1. The bundle got bigger, not smaller.
The NFS entry chunk did shrink (14 KB → 5 KB), but total sync went from 149 KB to 158 KB. Opening the drawer now also loads about 1.3 MB of extra code, mostly highlight.js and parse5. That comes from QuickstartCtaLink importing Button and ButtonLink from the @backstage/ui barrel, which pulls in the code-highlighting stack. A CTA button should not need that. Please drop that barrel import (a MUI button plus the existing router link is enough), then re-measure. The 464.8 KB “before” number in the description also doesn’t match the 149 KB baseline in the table.

2. SonarQube quality gate failed.
New code is 4.3% duplicated (limit is 3%). ShapesOutlinedIcon is a copy of the global-header icon, and QuickstartIcon is mostly a copy of HeaderIcon. Share one implementation, or trim the copy until the gate passes.

@lokanandaprabhu

Copy link
Copy Markdown
Member

/hold

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants