# 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 ```bash 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.