Source: https://doc.babylonjs.com/lite/architecture/38-bundle-size-tooling

# Module: Bundle Size Tooling

> Package paths: `scripts/bundle-scenes-core.ts`, `scripts/build-bundle-scenes.ts`, `scripts/build-master-bundle-info.ts`, `scripts/report-bundle-size-deltas.ts`, `lab/index.html`

## Purpose

The bundle-size tooling builds production Vite bundles for each lab scene, measures the JavaScript actually fetched at runtime, and exposes local-vs-master size comparisons in the lab Bundle tab.

The tooling has two separate data layers:

1. **Runtime size manifests** — per-scene raw/gzip totals and runtime-loaded chunk names.
2. **File/module breakdowns** — per-scene Rollup chunk and minified source-module attribution used by the Bundle tab Files popup.

All generated local and master bundle data is gitignored; the master baseline is published by CI rather than tracked in the repository.

## Manifest Semantics

The per-scene bundle data lives in one JSON file per scene under
`lab/public/bundle/manifest/`, aggregated into a single `manifest.json` that
runtime consumers (lab UI, bundle-size test, report script, static lab site)
read. **None of it is tracked** — the whole `lab/public/bundle/` tree is
generated build output.

| File                                      | Generated by                                                     | Tracked? | Meaning                                                                                                                     |
| ----------------------------------------- | ---------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `lab/public/bundle/manifest/<scene>.json` | `pnpm build:bundle-scenes`                                       | No       | This build's per-scene runtime measurements.                                                                                |
| `lab/public/bundle/manifest.json`         | `pnpm build:bundle-scenes` (aggregated from the per-scene files) | No       | Aggregate of all per-scene files, consumed at runtime. This is the file master publishes as the baseline.                   |
| `lab/public/bundle/master-manifest.json`  | `pnpm build:bundle-scenes` and `pnpm build:bundle-master-info`   | No       | The master baseline, fetched from the published URL (or a git ref when one is named explicitly). Used for the delta report. |

`local-manifest.json` is not part of the architecture and must not be used.

### Where the Baseline Comes From

It is measured once per master build and published to a stable public URL:

```
https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/lite/bundle-baseline/manifest.json
```

`pnpm build:bundle-scenes` fetches it and writes `master-manifest.json`.

This replaced a baseline of ~227 JSON files tracked in git and refreshed by PR
authors. Because nearly every change to a shared module moves bytes in most of
the ~230 scenes, that arrangement broke the repo in two separate ways at once:

- **Red CI** — the moment any shared-code PR merged, every other open PR's
  committed manifest was stale and its Bundle Size job went red for reasons
  unrelated to that PR, curable only by a rebase plus a very slow full local
  rebuild, then again on the next merge.
- **Merge conflicts** — two branches that both regenerated the manifest rewrote
  the same ~200 tracked files and collided in git. This was the repo's dominant
  source of conflicts: in one representative PR, 168 of the 169 conflicting
  paths were manifest files and **zero** were source files.

Both symptoms have one cause: a generated file living in git, on branches that
have no reason to carry it. Publishing it instead of committing it removes the
cause — there is no tracked file to go stale, and nothing for two branches to
collide on.

Publishing rather than pushing to master also keeps CI out of the repository's
write path. `master` is protected (one required approval, plus a ruleset with
required status checks) and the CI identity deliberately holds no bypass, so a
design in which a bot commits the baseline to master could not work here without
weakening that protection.

This is safe because the baseline **gates nothing**:

- the size **gate** reads `scene-config.json` (`maxRawKB`), enforced byte-exactly
  by `pnpm build:bundle-scenes`;
- the baseline is consulted only for the advisory "increased vs master" delta.

A missing or unreachable baseline therefore degrades to "no delta report" and
never fails a build. `bundle-size.spec.ts` skips its advisory warning, and
`report-bundle-size-deltas.ts` sets `POST_BUNDLE_COMMENT=false` and exits 0.

| Build           | Measures | Enforces ceilings | Publishes the baseline |
| --------------- | -------- | ----------------- | ---------------------- |
| PR validation   | yes      | yes               | **no**                 |
| Master (merged) | yes      | yes               | yes                    |

The PR-validation Bundle Size job therefore:

1. measures every scene (`pnpm build:bundle-scenes`), which **fails the job** when a
   scene exceeds its `scene-config.json` ceiling — the deterministic, never-stale gate;
2. posts the size deltas against the published baseline as a PR comment
   (`report-bundle-size-deltas.ts`), which is how reviewers see the size impact.

`azure-pipelines-bundle-manifest.yml` re-measures on each push to master and
uploads `manifest.json` to the deployment server, then purges the CDN so the new
baseline is visible promptly. It refuses to publish an empty or missing manifest,
so a broken build cannot blank the baseline, and the ceiling check runs first so a
breach can never be published as the new normal.

To override the baseline source locally, set `BUNDLE_MASTER_MANIFEST_FILE` to a
downloaded manifest, or blank `BUNDLE_MASTER_MANIFEST_URL` to skip the fetch.

Scene bundles are built against the package's compiled **`build/lib`** output (the module-granular tree a real consumer of `@babylonjs/lite` resolves), not the TypeScript source — so the measured size is exactly what a downstream bundler produces. `pnpm build:bundle-scenes` runs `build:lib` first; it fails fast if `build/lib` is missing. (The lab dev app and the master-comparison bundle-info still resolve `babylon-lite` to source for a fast dev-iteration loop; their sizes may differ slightly, but these scene bundle-size tests are the authoritative guard against size drift.)

Each scene's runtime measurement subtracts its own **ignored bytes**, computed from that build's own `bundle-info`. The ignored set covers (a) local `*-nme.ts` NME data payloads and (b) bundled third-party WASM/shaping runtimes — `text-shaper` (default-layout text), `manifold-3d` (CSG2), and `@recast-navigation` (navmesh) — each loaded only by the feature that needs it, so engine-size ceilings track our own runtime code. The ceiling check always uses the current build's own accounting; `master-manifest.json` is consulted only for the advisory "increased vs master" delta, never to compute the gated `rawKB`.

## File/Module Breakdown Semantics

| File                                                | Generated by                    | Tracked? | Meaning                                                                                               |
| --------------------------------------------------- | ------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `lab/public/bundle/bundle-info/<scene>.json`        | `pnpm build:bundle-scenes`      | No       | Current/local Rollup chunk list with minified module-byte attribution and surviving exported symbols. |
| `lab/public/bundle/master-bundle-info/<scene>.json` | `pnpm build:bundle-master-info` | No       | Same breakdown generated from an archive of the selected master ref.                                  |

`build-master-bundle-info.ts` resolves the master ref from `MASTER_BUNDLE_REF`, `upstream/master`, `origin/master`, then `master`. It writes `master-manifest.json` from the same ref before building `master-bundle-info`, so master file comparisons and runtime chunk filtering use a consistent baseline.

## Build Commands

```text
pnpm build:bundle-scenes
```

Builds the selected local scenes (`BUNDLE_SCENES=scene1,scene75` for a subset), writes local scene bundles, writes `bundle-info/<scene>.json`, writes/updates the generated per-scene `manifest/<scene>.json` files and aggregate `manifest.json`, and refreshes `master-manifest.json` from master. All of these outputs are gitignored.

```text
pnpm build:bundle-master-info
```

Builds master file/module breakdowns for selected scenes (`BUNDLE_SCENES=...`) from the selected master ref (`MASTER_BUNDLE_REF=...` override), writes `master-bundle-info/<scene>.json`, and refreshes `master-manifest.json` from the same ref.

## PR Scene Impact Selection

The full master bundle build publishes `impact-manifest.json` beside the bundle-size baseline. It is generated from every Rollup chunk statically reachable from each scene and maps repository source files to the scenes that can consume them. This deliberately includes lazy chunks that a single baseline measurement did not fetch, so later interactions and nondeterministic runtime branches cannot create false-negative scene selections. Local source dependencies such as raw WGSL imports are included in the map. The file is a generated deployment artifact, not tracked source.

PR jobs run `pnpm select:affected-scenes -- --azure` against the exact merge-base commit's immutable impact manifest. The selector combines that map with directly named scene files and changed `scene-config.json` entries, then sets separate Azure variables for bundle-size, parity, and performance scenes. New files inherit the scenes of changed mapped modules that import them.

Selection fails closed:

- an unavailable or mismatched impact manifest makes runtime changes run all scenes;
- an unmapped or unclassified runtime file runs all scenes;
- documentation, thumbnails, most unit tests, lite-gl, compat, and playground-only changes skip WebGPU scene jobs;
- bundle-content unit tests explicitly select the scene fixtures required by their bundle-backed assertions;
- cloud scene tests are selected by the presence of a Playwright scene spec, independently of whether `skipParity` disables Babylon/golden comparison for that scene;
- scenes opting out of bundle-size or performance checks are removed only from that specific job.

Every master build still measures all scenes and republishes both the size and impact manifests. This refreshes the dependency map after each merge and preserves repository-wide coverage without making generated metadata a merge-conflict source.

## Minified Attribution

`bundle-scenes-core.ts` builds with hidden sourcemaps and runs the final minified output through module attribution:

- chunk `bytes` are final emitted UTF-8 JavaScript bytes
- module `bytes` are minified source-map-attributed bytes
- modules with zero attributed minified bytes are omitted
- the lab Files popup adds a `(chunk overhead)` row for bytes that cannot be attributed to a source module

Small per-module deltas can be source-map attribution drift even when standalone source or emitted module code is unchanged. The chunk total remains the authoritative emitted size.

## Lab Bundle Tab

The Bundle tab loads:

- `/bundle/manifest.json` for current/local scene totals
- `/bundle/master-manifest.json` for master scene totals
- `/bundle/bundle-info/<scene>.json` for local file/module details
- `/bundle/master-bundle-info/<scene>.json` for master file/module details

The scene cards and summary compare local `manifest.json` values against `master-manifest.json`.

The Files popup compares local and master chunk/module bytes when both breakdowns are available:

- Rollup hash suffixes are ignored when matching chunk names.
- Local-only chunks/modules show positive deltas.
- Master-only chunks/modules are shown as `0 B` locally with negative deltas.
- Zero deltas are hidden.
- Sorting preserves comparison groups (common, local-only, master-only) while applying Size/Path and Asc/Desc within each group.
- Runtime chunk filtering uses each source's own manifest (`manifest.json` for local, `master-manifest.json` for master).

## CI Comment Script

`scripts/report-bundle-size-deltas.ts` compares:

- current: `lab/public/bundle/manifest.json` or `BUNDLE_SIZE_CURRENT_MANIFEST`
- master: `lab/public/bundle/master-manifest.json` or `BUNDLE_SIZE_MASTER_MANIFEST`

It emits a rounded KB Markdown summary and Azure variables for conditional PR comments.

## File Manifest

| File                                   | Purpose                                                                                                       |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `scripts/bundle-scenes-core.ts`        | Shared bundle build, runtime measurement, manifest generation, minified attribution, and bundle-info writing. |
| `scripts/build-bundle-scenes.ts`       | Local/current bundle entry point.                                                                             |
| `scripts/build-master-bundle-info.ts`  | Master-ref archive extraction and master file/module breakdown generation.                                    |
| `scripts/report-bundle-size-deltas.ts` | PR-comment delta report from current and master manifests.                                                    |
| `azure-pipelines-bundle-manifest.yml`  | Master-triggered pipeline that re-measures every scene and publishes the baseline manifest.                   |
| `lab/index.html`                       | Bundle tab summary, scene cards, and Files popup comparison UI.                                               |
| `lab/vite.config.ts`                   | Lab dev-server reload signature for current/master bundle manifests.                                          |
