Skip to content

test(ui): bit start sanity e2e, plus fixes for the qodo findings on #10628/#10629 - #10631

Merged
GiladShoham merged 2 commits into
masterfrom
test/ui-start-e2e
Aug 19, 2026
Merged

test(ui): bit start sanity e2e, plus fixes for the qodo findings on #10628/#10629#10631
GiladShoham merged 2 commits into
masterfrom
test/ui-start-e2e

Conversation

@GiladShoham

@GiladShoham GiladShoham commented Aug 18, 2026

Copy link
Copy Markdown
Member

Sanity e2e for bit start itself, covering both UI roots. Part 3 of #10596 follow-up work.

Rebased onto master now that #10628 and #10629 have merged; targets master directly.

Until now nothing exercised bit start end to end. That is how the scope SSR bundle managed to throw on every request for months (#10628), and the layout change in #10629 has a matching failure mode: name the fallback document wrong and every client-side route 404s while SSR-rendered ones keep working.

e2e/harmony/ui-start.e2e.ts starts a real server per root and asserts, over http:

  • startup writes nothing matching /error|exception|unhandled/i to stderr — a server can listen fine with an aspect that failed to load
  • the served document has a react root
  • every script and stylesheet the document references actually resolves 200 — this is the one that catches assets emitted under a path the server does not expose
  • a deep client-side route returns a document (the history-api fallback)
  • /graphql answers without errors
  • workspace only: the document loads the workspace entry and not the scope one — both roots are entries of one bundle now, so serving the wrong document would still look like a working page, just booting the other root's app
  • scope only: the markup is server-rendered and contains the exported component

12 assertions, ~1 min. All --rebuild, so they describe this repo's code rather than whichever bit release is installed.

Two supporting changes

HttpHelper can start either root. It was hardcoded to the bare scope (scopes.remotePath, and a ready-message string naming teambit.scope/scope). It now takes { extraArgs, uiRootAspectId }, derives the cwd from the root, and builds the ready message per root. Existing callers use the unchanged two-arg form. It also records stderr so tests can assert on a clean startup.

portHolders() now filters to listening sockets (lsof -ti tcp:PORT -sTCP:LISTEN). Without it lsof also reports processes holding a client socket to the port — including the mocha process itself, since node keeps connections alive after a test fetches from the server. waitForPortToBeFree read that as a foreign process squatting the port and refused to continue, failing the after hooks. Only a listener can actually hold a port. This was a latent bug in the helper; the new tests hit it because they fetch every referenced asset.


Also: Qodo review findings from #10628 / #10629

Both of those merged before their review findings were addressed, so the actionable ones land here. Each was verified against the code rather than taken on trust.

bit start 404s on an existing local UI build (from #10629) — the important one. buildIfNoBundle() treated any existing public/bit directory as a valid build, but the server now falls back to <root>.html, which a build made before #10629 does not contain. Reproduced end to end: with the pre-fix check the whole UI returns 404 on / and on deep routes; with the fix it detects the missing document, rebuilds, and serves 200. This would have hit every user upgrading past #10629 with a previously-built local UI. It now checks for the root's document rather than the directory.

Hash written for roots that were never built (from #10629). generateHash() walked a hardcoded root list and threw when one was not registered. Beyond failing in a scope-only runtime, it could record a hash for a root whose document was never emitted — which reads at startup as "a pre-bundle exists" and then 404s, the same failure as above. It now walks the same registered roots build() turns into entries, via a new UiMain.getUiRoots().

Service worker bound to a document that is not emitted (from #10629). Confirmed in the built artifact: service-worker.js contained createHandlerBoundToURL("public/index.html") while the build emits only scope.html / workspace.html. With an entry per root there is no single app shell, so navigateFallback is removed — the express history-api fallback already serves the right document. Verified the built service worker no longer contains that binding.

Entry name collisions (from #10629). Object.fromEntries would silently keep only the last of two entries sharing a sanitized name, leaving a root with no chunks and no document while still looking built. Now throws instead.

Stats filename could break (from #10628). writeBundleStats interpolated an unsanitized name into a path, so a root name containing / would fail with ENOENT into a swallowed debug log. Now sanitized.

Not changed: the "ad-hoc chalk in writeStats" rule violation. That line matches the surrounding [Rspack] log statements in the same file; the style guide it cites covers section titles and symbols in command output, not diagnostic log lines. Happy to switch it if you'd rather be strict.

The preview/bundle-stats.ts copy of the sanitization fix lands with #10632, which is where that file lives.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add bit start sanity E2E coverage for both UI roots

🧪 Tests ✨ Enhancement 🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds HTTP E2E sanity coverage for scope and workspace bit start roots.
• Verifies clean startup, root-specific assets, fallback routes, GraphQL, and scope SSR.
• Generalizes HttpHelper root selection and ignores client sockets during port cleanup.
Diagram

sequenceDiagram
  actor Test as E2E Suite
  participant Helper as HttpHelper
  participant CLI as Bit CLI
  participant Server as UI Server
  participant Root as UI Root
  participant API as GraphQL API
  Test->>Helper: start root with rebuild
  Helper->>CLI: spawn from root cwd
  CLI->>Server: start selected root
  Server-->>Helper: root-ready message
  Helper-->>Test: startup complete
  Test->>Server: fetch document and routes
  Server->>Root: serve root entry
  Root-->>Test: HTML and assets
  Test->>Server: post GraphQL query
  Server->>API: execute query
  API-->>Server: successful result
  Server-->>Test: error-free response
Loading
High-Level Assessment

The real-server HTTP approach is appropriate because the targeted regressions emerge only from the combined build output, root selection, static serving, and history fallback. Unit tests would miss those integration boundaries, while browser automation would add cost without improving these transport-level assertions.

Files changed (4) +219 / -14

Tests (2) +176 / -1
ui-ssr.e2e.tsAdopt the configurable HttpHelper options API +1/-1

Adopt the configurable HttpHelper options API

• Updates the existing SSR E2E setup to pass '--rebuild' through the new options object while preserving its prior behavior.

e2e/harmony/ui-ssr.e2e.ts

ui-start.e2e.tsAdd bit start sanity tests for both UI roots +175/-0

Add bit start sanity tests for both UI roots

• Starts real scope and workspace UI servers and validates clean startup, React documents, referenced assets, deep-route fallback, and GraphQL. It additionally verifies scope SSR output and ensures the workspace document loads only its own entry.

e2e/harmony/ui-start.e2e.ts

Documentation (1) +2 / -2
ui-server.tsDocument the root-specific E2E readiness contract +2/-2

Document the root-specific E2E readiness contract

• Clarifies that HttpHelper derives its startup readiness check from the UI server's root-specific log message.

scopes/ui-foundation/ui/ui-server.ts

Other (1) +41 / -11
http-helper.tsSupport root-specific startup and reliable port cleanup +41/-11

Support root-specific startup and reliable port cleanup

• Adds options for extra CLI arguments and UI root selection, chooses the corresponding working directory and ready message, and captures stderr for assertions. Port-holder detection now considers only listening sockets, preventing retained client connections from blocking teardown.

e2e/http-helper.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 18, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Root aliases force rebuilds 🐞 Bug ➹ Performance ⭐ New
Description
When --ui-root-name matches UIRoot.name, getUi() preserves that name as uiRootAspectId, but
the bundle emits HTML using the registered aspect ID. The new document check therefore searches for
a nonexistent name-derived file and forces a costly Rspack rebuild on every start despite a valid
local build.
Code

scopes/ui-foundation/ui/ui.main.runtime.ts[733]

+    if (fs.pathExistsSync(join(outputPath, getUiRootHtmlFilename(uiRootAspectId)))) return false;
Evidence
The start command documents and forwards --ui-root-name, while getUi() can resolve that value
through getUiByName() but returns the supplied alias as the tuple key. build() names entries
from canonical uiRootSlot IDs, whereas the added check derives its expected filename from that
alias; built-in root names are dynamic workspace/scope names, so those paths differ and the check
triggers another build.

scopes/ui-foundation/ui/start.cmd.tsx[59-63]
scopes/ui-foundation/ui/ui.main.runtime.ts[534-539]
scopes/ui-foundation/ui/ui.main.runtime.ts[254-260]
scopes/ui-foundation/ui/ui.main.runtime.ts[723-736]
scopes/workspace/workspace/workspace.ui-root.ts[24-29]
scopes/scope/scope/scope.ui-root.ts[15-20]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Selecting a UI root through its supported human-readable name retains the name as the tuple key. Bundle documents are named from registered aspect IDs, so `buildIfNoBundle()` never recognizes the emitted document and rebuilds on every invocation.

## Issue Context
`getUi()` should return the canonical slot registration ID even when lookup succeeds through `UIRoot.name`. Use that canonical ID consistently for document names, hashes, server fallback, and build selection.

## Fix Focus Areas
- scopes/ui-foundation/ui/ui.main.runtime.ts[534-539]
- scopes/ui-foundation/ui/ui.main.runtime.ts[723-736]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Startup failure hangs suite 🐞 Bug ☼ Reliability
Description
The suite disables Mocha's timeout entirely, while HttpHelper.start() has no timeout and only
settles after the exact ready message or process exit. A live process that never reaches
readiness—or a stalled fetch—therefore hangs the test job indefinitely instead of producing a
bounded failure.
Code

e2e/harmony/ui-start.e2e.ts[32]

+  this.timeout(0);
Evidence
The new suite sets this.timeout(0) at e2e/harmony/ui-start.e2e.ts:31-32 and awaits startup at
lines 39-50 and 111-120. HttpHelper.start() at e2e/http-helper.ts:49-83 creates a promise with no
timer; it settles only when the root-specific ready text is observed or the process closes, so a
still-running process that never emits that text leaves the suite pending without a deadline.

e2e/harmony/ui-start.e2e.ts[31-32]
e2e/harmony/ui-start.e2e.ts[39-50]
e2e/http-helper.ts[49-83]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `bit start` suite uses an unlimited Mocha timeout even though startup can remain pending forever if the child process stays alive without emitting the expected ready message. This can hang CI rather than fail the test.
## Issue Context
`HttpHelper.start()` resolves on a matching stdout message and rejects on process close, but does not enforce a startup deadline. The suite also performs multiple HTTP requests under the same unlimited timeout.
## Fix Focus Areas
- e2e/harmony/ui-start.e2e.ts[31-32]
- e2e/http-helper.ts[49-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 4dbb550 ⚖️ Balanced

Results up to commit c8748e6


🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)


Remediation recommended
1. Startup failure hangs suite 🐞 Bug ☼ Reliability
Description
The suite disables Mocha's timeout entirely, while HttpHelper.start() has no timeout and only
settles after the exact ready message or process exit. A live process that never reaches
readiness—or a stalled fetch—therefore hangs the test job indefinitely instead of producing a
bounded failure.
Code

e2e/harmony/ui-start.e2e.ts[32]

+  this.timeout(0);
Evidence
The new suite sets this.timeout(0) at e2e/harmony/ui-start.e2e.ts:31-32 and awaits startup at
lines 39-50 and 111-120. HttpHelper.start() at e2e/http-helper.ts:49-83 creates a promise with no
timer; it settles only when the root-specific ready text is observed or the process closes, so a
still-running process that never emits that text leaves the suite pending without a deadline.

e2e/harmony/ui-start.e2e.ts[31-32]
e2e/harmony/ui-start.e2e.ts[39-50]
e2e/http-helper.ts[49-83]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `bit start` suite uses an unlimited Mocha timeout even though startup can remain pending forever if the child process stays alive without emitting the expected ready message. This can hang CI rather than fail the test.

## Issue Context
`HttpHelper.start()` resolves on a matching stdout message and rejects on process close, but does not enforce a startup deadline. The suite also performs multiple HTTP requests under the same unlimited timeout.

## Fix Focus Areas
- e2e/harmony/ui-start.e2e.ts[31-32]
- e2e/http-helper.ts[49-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 28f6b6e


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Qodo Logo

@GiladShoham
GiladShoham force-pushed the perf/ui-bundle-single-compilation branch from 9659e1a to abfe40d Compare August 19, 2026 05:02
Base automatically changed from perf/ui-bundle-single-compilation to master August 19, 2026 05:38
* version, so the assertions would describe that release instead of the code under test.
*/
(IS_WINDOWS ? describe.skip : describe)('bit start', function () {
this.timeout(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Startup failure hangs suite 🐞 Bug ☼ Reliability

The suite disables Mocha's timeout entirely, while HttpHelper.start() has no timeout and only
settles after the exact ready message or process exit. A live process that never reaches
readiness—or a stalled fetch—therefore hangs the test job indefinitely instead of producing a
bounded failure.
Agent Prompt
## Issue description
The new `bit start` suite uses an unlimited Mocha timeout even though startup can remain pending forever if the child process stays alive without emitting the expected ready message. This can hang CI rather than fail the test.

## Issue Context
`HttpHelper.start()` resolves on a matching stdout message and rejects on process close, but does not enforce a startup deadline. The suite also performs multiple HTTP requests under the same unlimited timeout.

## Fix Focus Areas
- e2e/harmony/ui-start.e2e.ts[31-32]
- e2e/http-helper.ts[49-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c8748e6

@GiladShoham GiladShoham changed the title test(ui): add bit start sanity e2e for the scope and workspace roots test(ui): bit start sanity e2e, plus fixes for the qodo findings on #10628/#10629 Aug 19, 2026
// roots shared one compilation left an `index.html` here and no `<root>.html`, and the server
// now falls back to the latter - so "the directory exists" would skip the rebuild and every
// client-side route would 404 against an output this bit can no longer serve.
if (fs.pathExistsSync(join(outputPath, getUiRootHtmlFilename(uiRootAspectId)))) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Root aliases force rebuilds 🐞 Bug ➹ Performance

When --ui-root-name matches UIRoot.name, getUi() preserves that name as uiRootAspectId, but
the bundle emits HTML using the registered aspect ID. The new document check therefore searches for
a nonexistent name-derived file and forces a costly Rspack rebuild on every start despite a valid
local build.
Agent Prompt
## Issue description
Selecting a UI root through its supported human-readable name retains the name as the tuple key. Bundle documents are named from registered aspect IDs, so `buildIfNoBundle()` never recognizes the emitted document and rebuilds on every invocation.

## Issue Context
`getUi()` should return the canonical slot registration ID even when lookup succeeds through `UIRoot.name`. Use that canonical ID consistently for document names, hashes, server fallback, and build selection.

## Fix Focus Areas
- scopes/ui-foundation/ui/ui.main.runtime.ts[534-539]
- scopes/ui-foundation/ui/ui.main.runtime.ts[723-736]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 4dbb550

@GiladShoham
GiladShoham enabled auto-merge (squash) August 19, 2026 08:30
@GiladShoham
GiladShoham merged commit 59bd5c2 into master Aug 19, 2026
18 checks passed
@GiladShoham
GiladShoham deleted the test/ui-start-e2e branch August 19, 2026 08:48
GiladShoham added a commit that referenced this pull request Aug 19, 2026
…10628/#10629 (#10631)

Sanity e2e for `bit start` itself, covering both UI roots. Part 3 of

> Rebased onto `master` now that #10628 and #10629 have merged; targets
`master` directly.

Until now nothing exercised `bit start` end to end. That is how the
scope SSR bundle managed to throw on every request for months (#10628),
and the layout change in #10629 has a matching failure mode: name the
fallback document wrong and every *client-side* route 404s while
SSR-rendered ones keep working.

`e2e/harmony/ui-start.e2e.ts` starts a real server per root and asserts,
over http:

- startup writes nothing matching `/error|exception|unhandled/i` to
stderr — a server can listen fine with an aspect that failed to load
- the served document has a react root
- **every script and stylesheet the document references actually
resolves 200** — this is the one that catches assets emitted under a
path the server does not expose
- a deep client-side route returns a document (the history-api fallback)
- `/graphql` answers without errors
- workspace only: the document loads the *workspace* entry and not the
scope one — both roots are entries of one bundle now, so serving the
wrong document would still look like a working page, just booting the
other root's app
- scope only: the markup is server-rendered and contains the exported
component

12 assertions, ~1 min. All `--rebuild`, so they describe this repo's
code rather than whichever bit release is installed.

**`HttpHelper` can start either root.** It was hardcoded to the bare
scope (`scopes.remotePath`, and a ready-message string naming
`teambit.scope/scope`). It now takes `{ extraArgs, uiRootAspectId }`,
derives the cwd from the root, and builds the ready message per root.
Existing callers use the unchanged two-arg form. It also records stderr
so tests can assert on a clean startup.

**`portHolders()` now filters to listening sockets** (`lsof -ti tcp:PORT
-sTCP:LISTEN`). Without it lsof also reports processes holding a
*client* socket to the port — including the mocha process itself, since
node keeps connections alive after a test fetches from the server.
`waitForPortToBeFree` read that as a foreign process squatting the port
and refused to continue, failing the `after` hooks. Only a listener can
actually hold a port. This was a latent bug in the helper; the new tests
hit it because they fetch every referenced asset.

---

Both of those merged before their review findings were addressed, so the
actionable ones land here. Each was verified against the code rather
than taken on trust.

**`bit start` 404s on an existing local UI build (from #10629) — the
important one.** `buildIfNoBundle()` treated *any* existing `public/bit`
directory as a valid build, but the server now falls back to
`<root>.html`, which a build made before #10629 does not contain.
Reproduced end to end: with the pre-fix check the whole UI returns
**404** on `/` and on deep routes; with the fix it detects the missing
document, rebuilds, and serves 200. This would have hit every user
upgrading past #10629 with a previously-built local UI. It now checks
for the root's document rather than the directory.

**Hash written for roots that were never built (from #10629).**
`generateHash()` walked a hardcoded root list and threw when one was not
registered. Beyond failing in a scope-only runtime, it could record a
hash for a root whose document was never emitted — which reads at
startup as "a pre-bundle exists" and then 404s, the same failure as
above. It now walks the same registered roots `build()` turns into
entries, via a new `UiMain.getUiRoots()`.

**Service worker bound to a document that is not emitted (from
contained `createHandlerBoundToURL("public/index.html")` while the build
emits only `scope.html` / `workspace.html`. With an entry per root there
is no single app shell, so `navigateFallback` is removed — the express
history-api fallback already serves the right document. Verified the
built service worker no longer contains that binding.

**Entry name collisions (from #10629).** `Object.fromEntries` would
silently keep only the last of two entries sharing a sanitized name,
leaving a root with no chunks and no document while still looking built.
Now throws instead.

**Stats filename could break (from #10628).** `writeBundleStats`
interpolated an unsanitized name into a path, so a root name containing
`/` would fail with ENOENT into a swallowed debug log. Now sanitized.

Not changed: the "ad-hoc chalk in `writeStats`" rule violation. That
line matches the surrounding `[Rspack]` log statements in the same file;
the style guide it cites covers section titles and symbols in command
output, not diagnostic log lines. Happy to switch it if you'd rather be
strict.

The `preview/bundle-stats.ts` copy of the sanitization fix lands with

(cherry picked from commit 59bd5c2)
GiladShoham added a commit that referenced this pull request Aug 19, 2026
…SSR gap

Rebuilt the UI/preview pre-bundle from current source and refreshed
.bundle-cache/ - UI artifact 80 MB -> 16 MB, matching upstream #10629's
single-compilation dedupe now that it's reflected on this branch. Verified
end to end against a real `npm run bundle` build: 16/16 UI-bundling sanity
tests passing, including SSR. Total shipped distribution 216 MB / 2,933
files -> 160 MB / 2,839 files.

Also documents a scope-UI SSR crash found while validating (window is not
defined in useUserAgent), confirmed scoped to local --rebuild mode only -
the shipped, forPreBundle-filtered pre-bundle is unaffected. Not fixed
this session; tracked as a known gap.

See PRs #10628, #10629, #10631 for the upstream work behind the numbers.
GiladShoham added a commit that referenced this pull request Aug 19, 2026
Adds `docs/ui-bundle-size-analysis.md` — where the remaining bundle size
sits after #10628 and #10629, what I measured, and what I tried that did
not work. Written to be picked up cold in a later session.

> Rebased onto `master` now that #10628, #10629 and #10631 have merged;
targets `master` directly.

Env preview duplication is deliberately excluded — the core envs are
being removed, which takes it along.

## Also in here

- **Preview bundle stats.** `BIT_UI_BUNDLE_STATS=1` now covers the
preview pre-bundle too, so one build produces `browser`, `scope-ssr` and
`preview` stats together. It is a small copy of the UI helper rather
than an import: `@teambit/ui`'s index is imported by browser code, and
re-exporting a node-only module through it pulled `fs` polyfills into
the UI bundle (caught by the build failing on `Can't resolve
'constants'`).
- **Stats were being under-reported.** rspack's `toJson` groups assets
and modules into summary rows ("assets by status") that carry a size but
no name. That showed up as a single unattributable 3.3 MB / 17.9%
bucket, and `assets: 0`. All `groupModulesBy*` / `groupAssetsBy*` flags
are now off.
- **`analyze-bundle.mjs` crashed** on assets without a `name` (those
same grouped rows).
- **`code-view.tsx`** imported `createElement` from the
`react-syntax-highlighter` package root, which defeats its own
`prism-light` import two lines later. Changed to the deep path.
Behaviour-neutral, and worth stating plainly: **it saves nothing today**
— see below.

## Headline findings

**The two eagerly-loaded syntax highlighting registries are the biggest
single item** — `highlight.js` (1.34 MB, every language) plus
`refractor` (0.85 MB, every Prism language), in both the browser and ssr
bundles. Neither is imported directly anywhere in this repo. They come
in through `react-syntax-highlighter`'s package root, which re-exports
every build including the full-language ones.

The blocker is that the remaining root imports are in *published*
`@teambit` components in `node_modules`
(`api-reference.renderers.schema-node-member-summary`,
`documenter.ui.code-snippet`), whose source is not in this repo. I
verified this: fixing the in-repo imports changes the artifact by **0
bytes**. Filed separately as #10633.

I tried the bundler-level workaround (alias `lowlight` →
`lowlight/lib/core`, `refractor` → `refractor/core`) and **rejected
it**: bit fails the build because both are transitive and would have to
be declared dependencies of `@teambit/ui`, and it silently degrades any
consumer relying on auto-registered languages to plain text. That is a
product call, not a build one.

**`lodash` is the best effort-to-reward item.** It is CJS-only (no
`module` field), so it cannot be tree-shaken, and the repo has 280 `from
'lodash'` imports and zero cherry-picked ones. It is 0.52 MB of the 1.96
MB preview bundle — 28% — for six functions. Fixing it pays out in the
browser, ssr and preview bundles at once.

Also documented: `graphql` shipping whole into preview (28%), `sucrase`
(0.47 MB) arriving via `react-live` and never lazy-loaded, `date-fns` at
302 modules, and the fact that one 6.23 MB chunk is the entire eager
payload — which is what makes the cold-cache first paint slower than
client-only rendering (measured in #10628).

`@shikijs/langs` is 1.45 MB but already lazy-loaded per language, and is
called out as the pattern the rest of the UI should copy.



## Update after the stack merged

Rebased onto `master`. Two follow-ups folded in:

- **`preview/bundle-stats.ts` gets the filename sanitization** that
#10631 applied to its UI twin (a Qodo finding from #10628). This file
only exists on this branch, which is why it was carried over rather than
fixed there.
- **The doc records that the service worker no longer claims
navigations.** #10631 removed the `navigateFallback` that still pointed
at an `index.html` the multi-entry build stopped emitting, so the
analysis notes that re-adding an offline shell now has to answer what
that means for two roots.

Qodo reviewed this PR and found no issues.

Re-validated on the rebased branch: full compile, fresh-capsule build of
both bundles with `BIT_UI_BUNDLE_STATS=1`, and `analyze-bundle.mjs`
reproducing the figures the doc quotes — 6.23 MB eager chunk,
`@shikijs/langs` 1.45 MB / 9.5%, `highlight.js` 1.34 MB / 8.7%, and in
preview `graphql` 0.52 MB / 27.9% next to `lodash` 0.52 MB / 27.7%.
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.

2 participants