mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-20 06:50:01 +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,8 +1,8 @@
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { resolveMarkdown } from "lit-markdown";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import type { NewsItem } from "../../core/ApiSchemas";
|
||||
import { getNews } from "../Api";
|
||||
import { renderMarkdown } from "../Markdown";
|
||||
import { translateText } from "../Utils";
|
||||
|
||||
export type { NewsItem };
|
||||
@@ -140,7 +140,7 @@ export class NewsBox extends LitElement {
|
||||
>`}
|
||||
<span
|
||||
class="text-xs text-white/50 block [&_a]:text-blue-300 [&_a:hover]:text-blue-200"
|
||||
>${resolveMarkdown(
|
||||
>${renderMarkdown(
|
||||
item.descriptionTranslationKey
|
||||
? translateText(item.descriptionTranslationKey)
|
||||
: (item.description ?? ""),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { LitElement, html, unsafeCSS } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import tailwindStyles from "../../styles.css?inline";
|
||||
import { documentStylesSheet } from "./SharedStyles";
|
||||
|
||||
export type OModalTab = { key: string; label: string };
|
||||
|
||||
@customElement("o-modal")
|
||||
export class OModal extends LitElement {
|
||||
static styles = [unsafeCSS(tailwindStyles)];
|
||||
static styles = [documentStylesSheet()];
|
||||
|
||||
@state() public isModalOpen = false;
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Shared constructed stylesheet mirroring the document's global CSS, for
|
||||
* shadow-DOM components that want the page's Tailwind styles. Importing
|
||||
* styles.css?inline instead would ship the full Tailwind CSS a second time
|
||||
* (~160 KB) inside the JS bundle.
|
||||
*
|
||||
* In production the page stylesheet <link> is fetched (same URL the browser
|
||||
* already loaded, so it resolves from HTTP cache); in dev Vite injects
|
||||
* <style> tags whose text is read directly and re-read on HMR updates.
|
||||
*/
|
||||
|
||||
let sheet: CSSStyleSheet | null = null;
|
||||
|
||||
async function populate(target: CSSStyleSheet): Promise<void> {
|
||||
const parts: string[] = [];
|
||||
for (const style of Array.from(document.querySelectorAll("style"))) {
|
||||
parts.push(style.textContent ?? "");
|
||||
}
|
||||
const links = Array.from(
|
||||
document.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]'),
|
||||
);
|
||||
await Promise.all(
|
||||
links.map(async (link) => {
|
||||
try {
|
||||
const response = await fetch(link.href);
|
||||
if (response.ok) {
|
||||
parts.push(await response.text());
|
||||
}
|
||||
} catch {
|
||||
// Unreachable stylesheet — skip; the component renders unstyled
|
||||
// rather than breaking.
|
||||
}
|
||||
}),
|
||||
);
|
||||
await target.replace(parts.join("\n"));
|
||||
}
|
||||
|
||||
export function documentStylesSheet(): CSSStyleSheet {
|
||||
if (sheet === null) {
|
||||
sheet = new CSSStyleSheet();
|
||||
void populate(sheet);
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
|
||||
// Keep the copy in sync when Vite hot-replaces CSS in dev.
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.on("vite:afterUpdate", () => {
|
||||
if (sheet !== null) {
|
||||
void populate(sheet);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user