Files
govoplan-core/docs/WEBUI_BUNDLE_BUDGETS.md

2.9 KiB

WebUI Loading And Bundle Budgets

The Core WebUI host owns the loading boundary for installed module packages. Vite discovers configured packages at build time, but emits an asynchronous loader for each package's src/module.ts contribution descriptor. At runtime, Core imports only descriptors whose backend manifests are enabled and identify the matching frontend.package_name.

The direct descriptor entry is intentional. A package root may re-export pages for consumers; importing that barrel as module wiring can cause those pages to be evaluated before navigation. Route pages and substantial panels should use React.lazy, and Core wraps routes in the shared loading/error boundary.

Enforced Budgets

webui/bundle-budget.json contains the production limits:

Measurement Raw limit Gzip limit
Initial JavaScript static import closure 512 KiB 160 KiB
Largest individual asynchronous JavaScript chunk 384 KiB 110 KiB

npm run build writes a Vite manifest, measures the entry and its recursive static imports, writes dist/bundle-metrics.json, and fails when either budget is exceeded. npm run test:module-permutations applies the same gate to every permutation and records the collected results in dist/module-permutation-bundle-metrics.json. In CI, each result is also added to the step summary.

Budgets are limits, not targets. A change that approaches a limit should add a new lazy boundary or remove unnecessary entry code instead of raising the limit without measurement and review.

2026-07-30 Baseline

Measurements use the same full-product source tree and Node 22 runtime. The post-change build additionally includes the Search module in the default and full-product sets.

Initial-load measurement Before After Reduction
JavaScript assets in initial static closure 1 1 0%
Raw JavaScript 1,387,043 B 453,769 B 67.3%
Gzip level 9 364,767 B 141,725 B 61.1%
Brotli quality 11 254,797 B 106,701 B 58.1%
Parse proxy median 18.776 ms 7.750 ms 58.7%
Parse proxy p95 21.980 ms 8.739 ms 60.2%

The parse proxy constructs a fresh node:vm SourceTextModule from the entry source 30 times with a randomized source marker. It is useful for a controlled before/after comparison, but is not enforced in CI because absolute timings vary across runners. Transfer budgets use deterministic raw and gzip byte counts.

The first budgeted full-product build reported:

  • initial JavaScript: 453,769 B raw / 141,725 B gzip;
  • largest async chunk: CampaignWorkspace, 353,724 B raw / 98,142 B gzip.

Verification

cd /mnt/DATA/git/govoplan-core/webui
npm run build
npm run check:bundle-budget
npm run test:module-permutations

The build gate also catches accidental eager imports: a page pulled into the entry closure consumes the initial budget, while an oversized page or module descriptor consumes the asynchronous chunk budget.