Skip to content

Commit 36ac918

Browse files
authored
Chore/production build optimization (#5532)
* chore: production build optimization strategy (groundwork) Investigate current asset structure and document strategy for future performance and offline improvements. - Added Docs/PRODUCTION_BUILD_STRATEGY.md for architectural roadmap. - Added scripts/analyze-production-assets.js for data-driven asset auditing. - Recovered Docs/GLOBAL_STATE_AUDIT.md for completeness. * docs: add baseline metrics and SW analysis to production strategy
1 parent 45eb847 commit 36ac918

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed

Docs/PRODUCTION_BUILD_STRATEGY.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Production Build Optimization Strategy: Architecture & Performance
2+
3+
## Why
4+
Music Blocks currently serves mostly unbundled, raw JavaScript and asset files.
5+
While this works in development, it introduces limitations for:
6+
- Production performance (high HTTP request count)
7+
- Predictable offline caching
8+
- Service worker reliability
9+
- Long-term maintainability alongside AMD-based loading
10+
11+
This document explores a **lightweight investigation** of production build optimizations without introducing a full migration or breaking existing architecture.
12+
13+
## Current Behavior
14+
- JavaScript and assets are largely served as individual files.
15+
- No formal production bundling or minification strategy exists.
16+
- Service worker caching must account for many independent assets (hundreds of individual JS files).
17+
- RequireJS / AMD loading is the primary module system, which constrains conventional bundling approaches that expect ES modules or CommonJS.
18+
19+
## Analysis: Current State & Offline Impact
20+
21+
### Baseline Metrics (as of Feb 2026)
22+
To provide a comparison reference for future optimizations:
23+
- **Total JavaScript Request Count:** ~248 files (209 Application, 39 Libraries).
24+
- **Total JavaScript Size (Uncompressed):** ~12.94 MB.
25+
- **Application Logic (`js/`):** 209 files, 6.42 MB.
26+
- **Libraries (`lib/`):** 39 files, 6.52 MB.
27+
- **Loading Model:** Sequential AMD loading, resulting in a deep waterfall of requests.
28+
29+
### Service Worker & Offline Caching
30+
The current `sw.js` implementation follows a **stale-while-revalidate** pattern with significant constraints for offline reliability:
31+
1. **Limited Pre-caching:** Only `index.html` is explicitly pre-cached. All other assets are cached dynamically upon first request.
32+
2. **Fragmentation:** Caching 200+ individual JS files increases the risk of partial cache states (where some files are cached and others are not), leading to runtime errors in offline mode.
33+
3. **Implicit Dependencies:** If the service worker fails to intercept a single AMD dependency (e.g., due to a network blip), the entire module fails to load.
34+
4. **Cache Invalidation:** Without content hashing, ensuring users receive the latest version of a file depends on the browser's fetch behavior and the SW's revalidation logic, which can be inconsistent.
35+
36+
### Proposed Strategy (Groundwork)
37+
This strategy focuses on low-risk, future-oriented thinking:
38+
39+
### 1. Selective Minification
40+
Before full bundling, we can investigate minifying individual files during a "build" step.
41+
- Reduces payload size without changing the loading model.
42+
- Keep source maps for easier production debugging.
43+
44+
### 2. Asset Grouping (Low-Risk Experiment)
45+
Instead of bundling everything into one file (which breaks RequireJS's dynamic loading), we can group "core" modules that are always loaded together.
46+
- Example: `js/utils/utils.js`, `js/turtles.js`, and basic library wrappers.
47+
48+
### 3. Hashing/Versioning
49+
Introduce a simple hashing mechanism for production assets to ensure service workers can reliably identify when an asset has changed.
50+
51+
### Running the Asset Analysis Script
52+
53+
From the repository root:
54+
55+
```bash
56+
node scripts/analyze-production-assets.js
57+
```
58+
59+
This script recursively scans the `js/` and `lib/` directories to report:
60+
- Total JavaScript file count
61+
- Aggregate size
62+
- Estimated minification savings
63+
64+
No build step or configuration is required.
65+
66+
## Scope & Constraints
67+
- **Documentation first:** This document serves as the primary outcome of this phase.
68+
- **No replacement of RequireJS / AMD:** The current module system is deeply integrated and stable.
69+
- **No build system overhaul:** Use existing Node.js scripts or lightweight tools if any implementation is attempted.
70+
- **No runtime behavior changes:** The priority is stability.
71+
72+
## Next Steps / Roadmap
73+
- [x] Audit total request count and asset sizes.
74+
- [ ] Implement a lightweight minification pass for `js/` files in the `dist/` task.
75+
- [ ] Research RequireJS `r.js` optimizer or modern alternatives (like Vite or esbuild) that can target AMD.
76+
- [ ] Create a manifest for the Service Worker to enable reliable pre-caching of core assets.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* SPDX-License-Identifier: MIT
3+
* Copyright (c) 2026 Sugar Labs
4+
*/
5+
6+
const fs = require("fs");
7+
const path = require("path");
8+
const { execSync } = require("child_process");
9+
10+
/**
11+
* Proof of Concept: Asset Analysis & Minification Groundwork
12+
* This script investigates the current asset structure and estimates
13+
* potential gains from a formal production build process.
14+
*/
15+
16+
const JS_DIR = path.join(__dirname, "../js");
17+
const LIB_DIR = path.join(__dirname, "../lib");
18+
19+
function getFiles(dir, extension) {
20+
let results = [];
21+
const list = fs.readdirSync(dir);
22+
list.forEach(file => {
23+
file = path.join(dir, file);
24+
const stat = fs.statSync(file);
25+
if (stat && stat.isDirectory()) {
26+
results = results.concat(getFiles(file, extension));
27+
} else if (file.endsWith(extension)) {
28+
results.push(file);
29+
}
30+
});
31+
return results;
32+
}
33+
34+
function analyzeDir(name, dir) {
35+
console.log(`--- Analyzing ${name} ---`);
36+
const files = getFiles(dir, ".js");
37+
let totalSize = 0;
38+
39+
files.forEach(file => {
40+
const stats = fs.statSync(file);
41+
totalSize += stats.size;
42+
});
43+
44+
console.log(`Count: ${files.length} files`);
45+
console.log(`Total Size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
46+
47+
// Estimate minification (conservative 30% reduction)
48+
console.log(
49+
`Estimated Minified Size: ${((totalSize * 0.7) / 1024 / 1024).toFixed(2)} MB (-30%)`
50+
);
51+
}
52+
53+
console.log("Music Blocks Production Build Optimization Strategy - Investigation Phase\n");
54+
analyzeDir("Application Logic (js/)", JS_DIR);
55+
console.log("");
56+
analyzeDir("Third-party Libraries (lib/)", LIB_DIR);
57+
58+
console.log("\n--- Strategic Recommendations ---");
59+
console.log("1. Selective Bundling: Grouping the 200+ JS files into 3-5 logical chunks.");
60+
console.log("2. Content Hashing: Using hashes (e.g., style.[hash].css) for better SW caching.");
61+
console.log("3. AMD Optimization: Using r.js or a modern equivalent to trace dependencies.");
62+
console.log(
63+
"4. Pre-minification: Shipping minified versions of large files like artwork.js and activity.js."
64+
);

0 commit comments

Comments
 (0)