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:
- Runtime size manifests — per-scene raw/gzip totals and runtime-loaded chunk names.
- 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.jsonpnpm 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 bypnpm 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:
- measures every scene (
pnpm build:bundle-scenes), which fails the job when a scene exceeds itsscene-config.jsonceiling — the deterministic, never-stale gate; - 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
pnpm build:bundle-scenesBuilds 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.
pnpm build:bundle-master-infoBuilds 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
skipParitydisables 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
bytesare final emitted UTF-8 JavaScript bytes - module
bytesare 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.jsonfor current/local scene totals/bundle/master-manifest.jsonfor master scene totals/bundle/bundle-info/<scene>.jsonfor local file/module details/bundle/master-bundle-info/<scene>.jsonfor 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 Blocally 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.jsonfor local,master-manifest.jsonfor master).
CI Comment Script
scripts/report-bundle-size-deltas.ts compares:
- current:
lab/public/bundle/manifest.jsonorBUNDLE_SIZE_CURRENT_MANIFEST - master:
lab/public/bundle/master-manifest.jsonorBUNDLE_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. |