- Removed global renderer, camera, and scene management in favor of layer framework integration. - Implemented terrain layer registration with setup, render, and dispose methods. - Enhanced texture loading and caching mechanisms. - Updated geometry building to return Mesh objects directly. - Added performance benchmarking story for render performance validation. - Created bundle size audit story to ensure effective tree-shaking and size constraints.
11 KiB
Story 3.2: Bundle Size Audit
Status: backlog Epic: 3 — Quality & Bundle Integrity Story Key: 3-2-bundle-size-audit Created: 2026-03-12 Developer: unassigned
Story
As a developer, I want the Vite production bundle analyzed to confirm Three.js tree-shaking is effective and the total bundle size increase is within budget, So that the feature does not negatively impact page load performance.
Acceptance Criteria
AC1: Three.js named imports only (NFR-B1)
Given webgl-layer-framework.ts and draw-relief-icons.ts source is inspected
When Three.js import statements are reviewed
Then no import * as THREE from 'three' exists in any src/**/*.ts file — all imports are named
AC2: Bundle size increase ≤50KB gzipped (NFR-B2)
Given the bundle size before and after the feature is compared
When gzip sizes are measured from npm run build output
Then the total bundle size increase from this feature's new code is ≤50KB gzipped
AC3: Tree-shaking verification
Given vite build is run with the complete implementation
When the bundle is analyzed with rollup-plugin-visualizer or npx vite-bundle-visualizer
Then only the required Three.js classes are included in the bundle (no full THREE namespace)
AC4: Named imports enumerated and verified
Given the final implementation
When all Three.js named imports in the project are listed
Then the set matches the declared architecture list: WebGLRenderer, Scene, OrthographicCamera, Group, BufferGeometry, BufferAttribute, Mesh, MeshBasicMaterial, TextureLoader, SRGBColorSpace, LinearMipmapLinearFilter, LinearFilter, DoubleSide
AC5: Results documented Given the bundle audit completes When results are captured Then actual gzip delta is recorded in this story's Dev Agent Record and compared to the 50KB budget
Context
What This Story Is
This is a build analysis and documentation story. Run npm run build, inspect the output, verify tree-shaking, calculate the gzip size delta vs. the baseline (pre-feature), and document findings.
Key architectural note: Three.js is already a project dependency for the globe view (public/libs/three.min.js — pre-existing). The new WebGL relief feature adds TypeScript-side consumption of Three.js via import {...} from 'three' (Vite/Rollup tree-shaking). The budget is the delta of new classes uniquely added by this feature.
Prerequisites
- Story 3.1 debe be
done(or both can be done in parallel — they're independent) npm run buildmust produce a clean output (TypeScript errors would block this)npm run lintmust be clean
Build Command
npm run build
# = tsc && vite build
# output: dist/ (built from src/, publicDir from public/)
Bundle Analysis Tools
Two options (no new prod dependencies required):
Option A — rollup-plugin-visualizer (recommended):
npx rollup-plugin-visualizer --help # check availability
# OR temporarily add to vite.config.ts:
import {visualizer} from "rollup-plugin-visualizer";
export default {
root: "./src",
plugins: [visualizer({open: true, filename: "dist/stats.html"})]
// ... rest of config
};
Then npm run build — opens dist/stats.html in browser showing tree map.
Option B — vite-bundle-visualizer:
npx vite-bundle-visualizer
Option C — manual bundle inspection (simplest, no extra tools):
npm run build 2>&1
ls -la dist/
# Check the JS chunk sizes in dist/
du -sh dist/*.js
# For gzip sizes:
for f in dist/*.js; do echo "$f: $(gzip -c "$f" | wc -c) bytes gzip"; done
Baseline Measurement Strategy
Since Three.js was already included as a CDN/pre-bundled lib (via public/libs/three.min.js), the new feature adds TypeScript module consumption of Three.js via npm (named imports in src/). Vite will tree-shake these.
Two-point comparison for NFR-B2 delta:
-
Before delta — the git state BEFORE Epic 1 (
git stashor checkout to a clean state):git stash npm run build # Record gzip sizes git stash popIf the git stash is impractical (too much state), use the
mainbranch or initial commit as baseline. -
After delta — current state:
npm run build # Record gzip sizesDelta = (after) - (before) gzip size
-
Alternative if git stash is messy — estimate based on class sizes:
webgl-layer-framework.tssource: ~280 lines of TS ≈ ~5KB minified + gzipdraw-relief-icons.tssource: ~260 lines (substantially refactored) — net delta is small- Three.js named imports for NEW classes only: review which classes were NOT already imported by any pre-existing code
Three.js Import Audit
Classes used by webgl-layer-framework.ts:
import {Group, OrthographicCamera, Scene, WebGLRenderer} from "three";
Classes used by draw-relief-icons.ts:
import {
type Group, // ← already in webgl-layer-framework.ts (shared, no extra bundle cost)
type Texture,
BufferAttribute,
BufferGeometry,
DoubleSide,
LinearFilter,
LinearMipmapLinearFilter,
Mesh,
MeshBasicMaterial,
SRGBColorSpace,
TextureLoader
} from "three";
Check for any import * as THREE — should find ZERO:
grep -r "import \* as THREE" src/
# Expected output: (nothing)
Pre-existing Three.js Usage in Project
The project already has public/libs/three.min.js (CDN/pre-built). However, this is a different bundle path — it's a global script, not a module import. The Vite build for src/ will bundle Three.js module imports separately via npm (node_modules/three).
Check if Three.js was already imported via npm in any pre-existing src/ files:
grep -r "from 'three'\|from \"three\"" src/ --include="*.ts"
If the globe view uses the pre-built three.min.js (global THREE) rather than ESM imports, then Three.js ESM bundle cost is 100% new from this feature. If there are pre-existing ESM imports, the delta is only the newly added classes.
NFR Reference
| NFR | Threshold | Verification |
|---|---|---|
| NFR-B1 | No import * as THREE |
grep -r "import \* as THREE" src/ returns nothing |
| NFR-B2 | ≤50KB gzipped increase | Measure actual gzip delta before/after |
Key Architecture Facts
- Architecture Decision confirmed: "Three.js is already present; adds no bundle cost" — [Source:
_bmad-output/planning-artifacts/architecture.md#Decision 1] - This refers to Three.js being already a dependency; the NAMED import tree-shaking still matters
- Framework code size estimate: ~5KB minified, ~2KB gzip [Source:
architecture.md#NFR-B2] - Vite version: ^7.3.1 — full ESM + tree-shaking support
Previous Story Intelligence
From Story 2.2 (draw-relief-icons.ts refactor)
- Final named Three.js imports in
draw-relief-icons.ts:BufferAttribute, BufferGeometry, DoubleSide, Group (type), LinearFilter, LinearMipmapLinearFilter, Mesh, MeshBasicMaterial, SRGBColorSpace, Texture (type), TextureLoader - The Biome import organizer (
organizeImports: on) auto-orders imports alphabetically and movestypeimports first. Confirmed lint-clean. - No
import * as THREE from "three"remains anywhere in the project src/ tree.
From Story 3.1 (performance benchmarking)
src/renderers/draw-relief-icons.bench.tsmay have been created in Story 3.1 — if so, verify its Three.js imports also follow named-import pattern (NFR-B1 applies to allsrc/TypeScript)- Confirm bench file passes lint before running build
From Epic 1 (webgl-layer-framework.ts)
webgl-layer-framework.tsimports:Group, OrthographicCamera, Scene, WebGLRenderer— 4 named classesdraw-relief-icons.tsimports: 9 additional named classes (bufffers, mesh, material, texture, consts)- Total unique Three.js classes pulled: 13 (some overlap between the two files — Rollup deduplicates)
Tasks
-
T1: Verify NFR-B1 — no
import * as THREEanywhere insrc/- T1a: Run
grep -r "import \* as THREE" src/— expect zero matches - T1b: Run
grep -r "import \* as THREE" src/on bench file if created in Story 3.1 - T1c: Document: "NFR-B1 confirmed — no namespace imports found"
- T1a: Run
-
T2: Enumerate all Three.js named imports actually used
- T2a:
grep -r "from \"three\"" src/ --include="*.ts"— list all import statements - T2b: Verify the list matches the architecture declaration (AC4)
- T2c: Document the full import inventory
- T2a:
-
T3: Run production build
- T3a:
npm run build→ confirm exit code 0 (no TypeScript errors, no Vite errors) - T3b: List
dist/output files and sizes:ls -la dist/ - T3c: Calculate gzip sizes for all JS chunks:
for f in dist/*.js; do echo "$f: $(gzip -c "$f" | wc -c) bytes"; done
- T3a:
-
T4: Establish baseline (before-feature bundle size)
- T4a:
git stash(stash current work if clean) OR usegit show HEAD~N:dist/if build artifacts were committed - T4b: If git stash feasible:
git stash→npm run build→ record gzip sizes →git stash pop - T4c: If stash impractical: use the
mainbranch in a separate terminal, build separately, record sizes - T4d: Record baseline sizes
- T4a:
-
T5: Calculate and verify NFR-B2 delta
- T5a: Compute:
after_gzip_total - before_gzip_total - T5b: Verify delta ≤ 51,200 bytes (50KB)
- T5c: If delta > 50KB: investigate which chunk grew unexpectedly (bundle visualizer)
- T5a: Compute:
-
T6: (Optional) Run bundle visualizer for tree-shaking confirmation (AC3)
- T6a: Add
rollup-plugin-visualizertemporarily to vite.config.ts - T6b: Run
npm run build→ opendist/stats.html - T6c: Verify Three.js tree nodes show only the expected named classes
- T6d: Remove the visualizer from vite.config.ts afterward (do not commit it in production config — or move to a separate
vite.analyze.tsconfig)
- T6a: Add
-
T7:
npm run lint— zero errors (T6 vite.config.ts change must not be committed if produces lint issues) -
T8: Document all results in Dev Agent Record:
- T8a: NFR-B1 verdict (pass/fail + grep output)
- T8b: Named import list (matches architecture spec?)
- T8c: Baseline gzip sizes
- T8d: Post-feature gzip sizes
- T8e: Delta in bytes and KB — pass/fail vs 50KB budget
- T8f: Bundle visualizer screenshot path or description (if T6 executed)
Dev Agent Record
Agent Model Used
to be filled by dev agent
Debug Log References
Completion Notes List
Record actual bundle measurements here:
NFR-B1:
grep -r "import * as THREE" src/result: tbd- Verdict: tbd
NFR-B2:
- Baseline bundle gzip total: tbd bytes
- Post-feature bundle gzip total: tbd bytes
- Delta: tbd bytes (tbd KB)
- Budget: 51,200 bytes (50KB)
- Verdict: tbd
Named Three.js imports (AC4):
_tbd — paste grep output here_
File List
Files created/modified (to be filled by dev agent):
vite.config.ts— TEMPORARY: add/remove visualizer plugin for T6 (do not commit)