Skip to content

perf(admin-ui): split bundles with lazy routes and vendor chunks#2453

Open
dirkwa wants to merge 2 commits intoSignalK:masterfrom
dirkwa:perf-bundle-splitting
Open

perf(admin-ui): split bundles with lazy routes and vendor chunks#2453
dirkwa wants to merge 2 commits intoSignalK:masterfrom
dirkwa:perf-bundle-splitting

Conversation

@dirkwa
Copy link
Contributor

@dirkwa dirkwa commented Mar 15, 2026

Motivation

  • math.js now in the UI and ballooned the bundle to > 2MB.
  • A PI3 needes depending on load and config 3-8s to process the data when cached.
  • My Pi4 with wifi is already close to unusable slow.

Summary

The admin UI shipped everything in a single 2.1MB JavaScript bundle. This splits it using route-level code splitting and vendor chunk extraction, reducing the initial page load to ~500KB.

  • Convert 13 routes to React.lazy() with a Suspense boundary — Dashboard, Login, Register, Webapps, Embedded, and EmbeddedDocs stay eagerly loaded
  • Extract mathjs and its dependencies (decimal.js, typed-function, complex.js, fraction.js, seedrandom) into a separate vendor-mathjs chunk via manualChunks — only fetched when navigating to the Data Browser
  • Remove full lodash CJS import (138KB) from actions.ts, replacing a single isUndefined() call with === undefined

Vite's own code splitting naturally groups @rjsf/ajv into the Configuration route chunk (~418KB, loaded only on plugin config) and react-select into a shared chunk (~86KB, loaded by Data Browser/Server Log/Connections).

Note: manualChunks is limited to mathjs (pure math, no React imports) because @module-federation/vite rewrites React imports into async share-scope resolution with top-level await — forcing React-dependent packages into separate vendor chunks breaks the MF initialization order.

Bundle comparison

  Before After
Initial load (bootstrap) 2,098 KB 498 KB
/databrowser +637 KB (mathjs) +131 KB (views)
/serverConfiguration/plugins/:id +418 KB (rjsf+ajv+views)
Other routes 2–62 KB each on demand

Note

dirkwa added 2 commits March 16, 2026 08:21
Plugins built with React 16 (webpack Module Federation) crash in the
React 19 admin UI due to singleton resolution picking R19 over R16,
causing hook errors (SignalK#321) and element type mismatches (SignalK#525).

Fix by temporarily hiding R19 share scope entries during container
initialization so webpack resolves to R16, then rendering legacy
components in isolated R16 subtrees via ReactDOM 16's render().

For plugins that share react but not react-dom (bt-sensors, shelly2),
fall back to a bundled ReactDOM 16 UMD loaded with the plugin's own
R16 instance to keep hooks working.

Defer R16 rendering via setTimeout(0) to avoid interfering with R19's
commit phase on soft navigation.
The admin UI shipped a single 2.1MB bootstrap chunk containing all
view code and all npm dependencies. This adds route-level code
splitting via React.lazy() and extracts heavy vendor libraries
(mathjs, @rjsf, react-select) into separate chunks via manualChunks.

Initial load drops from 2.1MB to ~500KB. The heaviest vendor chunks
(mathjs 636KB, @rjsf 409KB) are only fetched when navigating to
DataBrowser or Plugin Configuration routes.

Also removes the full lodash CJS import (138KB) from actions.ts,
replacing the single isUndefined() call with a plain === check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant