mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-16 06:13:09 +00:00
Reduce main bundle size by ~44% gzipped (732 KB → 412 KB) (#4229)
## Summary Cuts the main JS chunk from **2,891 KB (732 KB gzip)** to **1,679 KB (412 KB gzip)** by fixing two bundling issues and removing/replacing heavy dependencies. Measured with a per-module `renderedLength` analysis of the rolldown output (its prod sourcemaps are malformed, so sourcemap-based tools misattribute sizes). | Chunk | Before | After | |---|---|---| | `index-*.js` (min) | 2,891 KB | 1,679 KB | | `index-*.js` (gzip) | 732 KB | **412 KB** | ## Changes - **Sim worker moved out of the main bundle (~512 KB).** The `?worker&inline` payload is now reached through a dynamic `import()`, so it lands in its own lazy chunk fetched when a game starts. The worker itself still uses Vite's inline Blob mechanism (with its `data:` URL fallback) — runtime instantiation is byte-for-byte unchanged. - **Replaced `lit-markdown` with `marked` + the already-bundled DOMPurify (~380 KB).** lit-markdown transitively pulled sanitize-html, htmlparser2, postcss, and two copies of entities into the client just to render news markdown. New `src/client/Markdown.ts` matches its image-stripping default. - **Dropped `colorjs.io` (~114 KB).** It was only used for ΔE2000 distance in `ColorAllocator`; colord's lab plugin (already imported there) provides the same CIEDE2000 via `.delta()`. Only relative magnitudes are compared, so allocation behavior is unchanged. - **`msdf-atlas.json` (~319 KB) fetched at runtime** like the atlas PNG, preloaded in parallel with worker init in `ClientGameRunner` so game-load latency is unaffected. - **Tailwind CSS no longer shipped twice (~158 KB).** `o-modal` imported `styles.css?inline`, duplicating the emitted stylesheet as a JS string. It now adopts a constructed stylesheet built from the document's own CSS (HTTP-cache hit in prod, `<style>` tags + HMR re-sync in dev) via `SharedStyles.ts`. - **Debug GUI lazy-loaded.** lil-gui + `gl/debug/*` now load on first toggle (46 KB lazy chunk) instead of shipping in the main bundle. Also looked at the `import * as d3` in RadialMenu (~84 KB) but left it: rolldown tree-shakes the metapackage well and all but ~2 KB is the genuine dependency closure of the selection/transition/shape/color APIs in use. ## Test plan - [x] `tsc --noEmit` clean - [x] ESLint clean - [x] Full test suite passes (1,374 + 65 tests) - [x] `npm run build-prod` succeeds; worker/debug chunks present in `asset-manifest.json` for the R2 upload - [ ] Manual smoke test in dev: start a game (worker dev path), open a modal (shared stylesheet), open news modal (markdown rendering) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
export type { AttackRingInput } from "../types";
|
||||
export { createDebugGui } from "./debug/index";
|
||||
// createDebugGui is intentionally not re-exported here — it pulls lil-gui and
|
||||
// the debug GUI into the main bundle; dynamically import "./debug/index".
|
||||
export type {
|
||||
GameViewEventMap,
|
||||
GameViewEventType,
|
||||
@@ -11,6 +12,7 @@ export type {
|
||||
export { GameView } from "./GameView";
|
||||
export { GraphicsOverridesSchema } from "./GraphicsOverrides";
|
||||
export type { GraphicsOverrides } from "./GraphicsOverrides";
|
||||
export { preloadAtlasData } from "./passes/name-pass/AtlasData";
|
||||
export type { SpawnCenter } from "./passes/SpawnOverlayPass";
|
||||
export {
|
||||
applyDarkModeOverride,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import emojiAtlasMeta from "resources/atlases/emoji-atlas-meta.json";
|
||||
import atlasData from "resources/atlases/msdf-atlas.json";
|
||||
import { assetUrl } from "src/core/AssetUrls";
|
||||
import type { BMChar, BMKerning, ParsedAtlas } from "./Types";
|
||||
import { CHAR_RANGE } from "./Types";
|
||||
|
||||
@@ -12,15 +12,45 @@ import { CHAR_RANGE } from "./Types";
|
||||
// Atlas parsing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface RawMsdfAtlas {
|
||||
info: { size: number };
|
||||
common: { base: number; scaleW: number; scaleH: number };
|
||||
distanceField?: { distanceRange: number };
|
||||
chars: BMChar[];
|
||||
kernings?: BMKerning[];
|
||||
}
|
||||
|
||||
// Fetched at game-load time rather than statically imported — the JSON is
|
||||
// ~320 KB minified and would otherwise sit in the main bundle.
|
||||
let atlasData: RawMsdfAtlas | null = null;
|
||||
let atlasDataPromise: Promise<void> | null = null;
|
||||
|
||||
export function preloadAtlasData(): Promise<void> {
|
||||
atlasDataPromise ??= fetch(assetUrl("atlases/msdf-atlas.json"))
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch msdf-atlas.json: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((json) => {
|
||||
atlasData = json as RawMsdfAtlas;
|
||||
});
|
||||
return atlasDataPromise;
|
||||
}
|
||||
|
||||
export function parseAtlasData(): ParsedAtlas {
|
||||
if (atlasData === null) {
|
||||
throw new Error("Atlas data not loaded; await preloadAtlasData() first");
|
||||
}
|
||||
return {
|
||||
fontSize: atlasData.info.size,
|
||||
base: atlasData.common.base,
|
||||
scaleW: atlasData.common.scaleW,
|
||||
scaleH: atlasData.common.scaleH,
|
||||
distanceRange: (atlasData as any).distanceField?.distanceRange ?? 4,
|
||||
chars: atlasData.chars as BMChar[],
|
||||
kernings: (atlasData.kernings ?? []) as BMKerning[],
|
||||
distanceRange: atlasData.distanceField?.distanceRange ?? 4,
|
||||
chars: atlasData.chars,
|
||||
kernings: atlasData.kernings ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user