Skip to content

UI pre-bundle artifact is ~90 MB: two near-identical browser builds plus a 45 MB scope SSR build #10596

Description

@GiladShoham

Summary

The pre-built UI bundle that ships inside @teambit/ui (artifacts/ui-bundle/) is ~90 MB. It is
built twice — once per UI root (workspace and scope) — and the scope copy additionally carries a
45 MB SSR build. There is a lot of room to shrink this.

This matters more than it used to: the esbuild CLI-bundling work relies on shipping this artifact so
bit start can serve a pre-built UI instead of running rspack at startup. At that point the artifact
is a large fraction of the whole distribution (~90 MB of a ~320 MB bundled bit), and the single
biggest remaining size lever.

Measurements

Measured on bit-bundle3, artifact produced by the BundleUI build task:

part size files
ui-bundle/workspace (browser) 22.6 MB 39
ui-bundle/scope (browser) 22.6 MB 54
ui-bundle/scope/public/bit/ssr 45.2 MB 45
total 90.4 MB 138

For comparison, the released 2.0.72 artifact is 58 MB (50 MB scope + 8.2 MB workspace), so this
has also grown — worth understanding separately.

Largest single file: scope/public/bit/ssr/index.js at 38 MB.

Two observations

1. workspace and scope are two full builds of the same app

Both are 22.6 MB browser bundles of the same Bit UI, differing only in the root aspect
(teambit.workspace/workspace vs teambit.scope/scope). BundleUiTask simply loops over both roots
and runs UiMain.build for each:

Object.values(UIROOT_ASPECT_IDS).map(async (uiRootAspectId) => {
  const outputPath = join(capsule.path, BundleUiTask.getArtifactDirectory(uiRootAspectId));
  await this.ui.build(uiRootAspectId, outputPath);
  await this.generateHash(outputPath);
})

Only 25 files / 1.9 MB are byte-identical between them, so naive file-level dedup buys little —
the two builds chunk and content-hash independently even where the underlying modules are the same.
The duplication is logical rather than byte-level: React, the component-compare UI, monaco, the
graph/tree components etc. are compiled into both.

Possible directions:

  • one rspack build with two entry points, sharing chunks via splitChunks, so the common vendor and
    UI chunks are emitted once;
  • or a shared runtime/vendor chunk emitted to a common dir that both roots reference.

2. The 45 MB SSR build is only ever used by the scope root

setupServerSideRendering returns immediately unless buildOptions.ssr:

private async setupServerSideRendering({ root, port, app }) {
  if (!this.buildOptions?.ssr) return;
  ...
}

ssr: true is set only on scope.ui-root.ts; workspace.ui-root.ts has ssr: false. So half the
artifact serves one root, and is never touched by bit start in a workspace — which is the common
case, and the only case a bundled CLI needs by default.

Worth asking:

  • is SSR still required for the scope UI? If yes, can it be a separate optional artifact rather than
    part of the package every install pulls down?
  • 38 MB for a single ssr/index.js looks high on its own and may be worth a metafile inspection
    independently of the packaging question.

Reproduce

bit build "teambit.ui-foundation/ui" --tasks "BundleUI" --reuse-capsules --unmodified
# then, in the capsule printed as "build output can be found in path:"
du -sh teambit.ui-foundation_ui@*/artifacts/ui-bundle/{workspace,scope}
du -sh teambit.ui-foundation_ui@*/artifacts/ui-bundle/scope/public/bit/ssr

Related

Context on why the artifact is now shipped inside the CLI bundle, and the numbers above, are recorded
in bundle-plan.md §17 on the bit-bundle3 branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions