Skip to content

Stop loading @elastic/eui and React UI libraries in the Kibana server - #300920

Closed
sphilipse wants to merge 2 commits into
elastic:mainfrom
sphilipse:server-no-eager-eui
Closed

sphilipse wants to merge 2 commits into
elastic:mainfrom
sphilipse:server-no-eager-eui

Conversation

@sphilipse

@sphilipse sphilipse commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Summary

An idle-server require-graph analysis showed ~1,985 modules of browser-only libraries loaded at Kibana server startup: @elastic/eui (1,125 modules), refractor/prismjs (~300), react-intl, react-use, react-redux, @elastic/apm-rum. A single from '@elastic/eui' barrel import loads all 1,125 EUI modules, so every server-reachable chain is severed in this PR (the commit message lists each chain; highlights below):

  • kbn-discover-utils: an internal constants module no longer round-trips through the package barrel; 13 server/common importers (saved_search, discover, canvas, observability, logs_shared) use pure subpath modules.
  • kbn-search-connectors: root barrel no longer re-exports ./components; 22 browser files import components from the subpath.
  • kbn-coloring / kbn-palettes / chart-expressions-common / kbn-visualizations-common: server/common importers switched to pure subpaths; isColorDark inlined (EUI ships no d.ts for deep lib paths); the 18 palette files deep-import eui_palettes with an ambient declaration, and kbn-ui-shared-deps exposes that path so browser bundles keep sharing the vis-color singleton. Flagging for @elastic/kibana-visualizations: please sanity-check the deep-import + shared-deps approach.
  • kbn-cases-components / kbn-user-profile-components: server importers use pure subpaths; VISUALIZATION_COLORS rebuilt from @elastic/eui-theme-borealis colorVis (verified byte-identical), likewise in security common/model and the infra/observability_shared color palettes.
  • ml: anomaly_utils barrel no longer re-exports the severity-color hooks (15 browser consumers moved to subpaths); aiops_log_rate_analysis moves its useEuiTheme hook out of constants.ts.
  • kbn-ebt-tools server consumers (core analytics, core root, files, home) use the pure performance_metric_events module (drops react-use); kibana_utils common barrels no longer re-export the React state-container helpers (public API unchanged); kbn-streamlang replaces htmlIdGenerator with an equivalent uuid factory.

Measured impact (idle server, allocation-tracking preload, GC-forced heap snapshot + full require graph)

modules in require.cache heap snapshot self size
dev, before 31,188 692.3 MB
dev, after this PR 29,231 (−1,957) 654.5 MB (−37.8 MB)

On a production source build, this PR combined with the GenAI lazy-loading PR measures −120 MB idle heap (697.4 → 577.4 MB) and −3,893 modules; roughly half of that is attributable to this PR based on the dev split.

After this PR the server's require graph contains 3 @elastic/eui modules (the deep palette path) and zero refractor/prismjs/apm-rum. Known remaining, flagged for owners rather than changed here: fleet agent_policy_settings.tsx keeps @kbn/i18n-react (render-function descriptions), share url_service keeps react-use useMountedState, aiops keeps one @reduxjs/toolkit slice.

Testing

  • node scripts/check.js --scope=local: tsc, eslint, and 16,045 jest tests across 20 configs pass
  • Booted and verified via runtime require-graph dump (counts above)
  • A full production build (node scripts/build) succeeds with the ui-shared-deps/ambient-d.ts changes

Related: #300785, #300786 (independent; measurements above were taken on top of them).

🤖 Generated with Claude Code

Production measurements (cumulative)

Idle production source builds (node scripts/build, bin/kibana against a local ES, allocation-tracking preload, 2 min idle, GC-forced heap snapshot + full require graph; same machine and protocol for every row):

Build Modules in require.cache Heap snapshot self size Idle heap_used Δ self size
main (63948de) 31,175 698.4 MB ~629 MB —
+ #300785 (swc fs-cache) no production effect — dev-only tooling 698.4 MB — 0
+ #300786 (closed; langchain entry points folded into #300921) 30,885 697.4 MB ~632 MB −1.0 MB
+ #300920 (EUI off the server) 28,932 614.7 MB ~580 MB −82.7 MB
+ #300921 (GenAI stack lazy) 26,992 577.4 MB ~545 MB −37.3 MB
total −4,183 −121.0 MB ≈ −84 MB

Single-run samples; heap_used has ±10 MB run noise, snapshot self size is GC-forced and stabler.

Status: on hold pending elastic/eui#10247

The deep-import + ambient-d.ts workaround for the 18 kbn-palettes files exists only because the palette functions aren't individually importable from EUI today. elastic/eui#10247 moves them to @elastic/eui-theme-common; once that lands and Kibana upgrades EUI, the palette portion of this PR becomes a clean typed import (from '@elastic/eui-theme-common') and the ambient declaration + shared-deps entry for the deep path can be dropped. Holding merge until then.

An idle-server require-graph analysis showed ~1,985 modules of browser-only
libraries loaded at server startup: @elastic/eui (1,125), refractor/prismjs
(~300), react-intl, react-use, react-redux, @elastic/apm-rum. A single
`from '@elastic/eui'` barrel import loads all 1,125 EUI modules, so every
server-reachable chain had to be severed:

- kbn-discover-utils: internal constants module no longer round-trips through
  the package barrel; 13 server/common importers (saved_search, discover,
  canvas, observability, logs_shared) now use pure subpath modules.
- kbn-search-connectors: root barrel no longer re-exports ./components;
  22 browser files import components from the subpath instead.
- kbn-coloring / kbn-palettes / chart-expressions-common /
  kbn-visualizations-common: server/common importers switched to pure
  subpaths; isColorDark inlined (EUI ships no d.ts for deep lib paths); the 18
  palette files deep-import eui_palettes with an ambient declaration, and
  kbn-ui-shared-deps exposes that path so browser bundles keep sharing the
  vis-color singleton.
- kbn-cases-components / kbn-user-profile-components: server importers use
  pure subpaths; VISUALIZATION_COLORS rebuilt from @elastic/eui-theme-borealis
  colorVis (verified identical output), likewise in security common/model and
  the infra/observability_shared color palettes.
- observability common barrel no longer re-exports ElapsedTimestampTooltip.
- ml: anomaly_utils barrel no longer re-exports useSeverityColor/severity_ramp
  (15 browser consumers moved to subpaths); aiops_log_rate_analysis moves its
  useEuiTheme hook out of constants.ts.
- kbn-ebt-tools server consumers (core analytics, core root, files, home) use
  the pure performance_metric_events module (drops react-use).
- kibana_utils common barrels no longer re-export the React state-container
  helpers (public API unchanged).
- kbn-streamlang: htmlIdGenerator replaced with an equivalent uuid-based
  factory; ingest_pipelines server route imports MAX_TREE_LEVEL from the pure
  constants module.

Known remaining (flagged for owners, not changed here): fleet
agent_policy_settings.tsx keeps @kbn/i18n-react on the server (render-function
descriptions), share/common url_service keeps react-use useMountedState, aiops
log_rate_analysis keeps one @reduxjs/toolkit slice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds @kbn/eslint/no_browser_imports_in_server (typescript-eslint
no-restricted-imports under a dedicated id, mirroring
security_imports_restriction so local overrides cannot drop it) and enforces
it as an error for **/server/** files: value imports of @elastic/eui, react,
react-dom, react-intl/use/redux, @reduxjs/toolkit, redux, @emotion/*,
refractor, prismjs, @elastic/apm-rum*, and @kbn/i18n-react are banned;
type-only imports stay allowed, as do the pure @elastic/eui-theme-* packages.

A repo sweep of the 16,936 matching server files found only six existing
imports from the banned set, all type-only, so the rule lands with zero
violations. This guards the direct-import regression class only; transitive
chains through shared packages remain covered by the measurements in this PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sphilipse

Copy link
Copy Markdown
Member Author

Split into smaller, independently mergeable PRs per review feedback:

The union of the eight splits equals this PR's diff (130 files, validated together: full check.js run + runtime require-graph verification). Measurements and chain analysis remain in this PR's description; the end state is enforced by the runtime gate in #302010, which stays unmerged until every part plus the GenAI PR (#300921) and the three flagged residue follow-ups land.

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

Labels

release_note:skip Skip the PR/issue when compiling release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant