perf: subset the icon font and drop the unused second family (#513)

Supersedes #239 by @mml-studio, ported onto the current component layout with fixes from review.

Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied.

Co-authored-by: Memel <[email protected]>
This commit is contained in:
Bilawal SidhuandMemel authored and GitHub committed 2026-09-14 19:21:38 -05:00
1 parent da700be77f
commit 8702458cf6
4 files changed
+127 -3

No files matched your search

+9
View File
@@ -292,6 +292,15 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
### Changed ### Changed
- The interface asks Google Fonts for only the icon glyphs it draws, instead of
the whole variable icon font, and no longer requests a second icon family that
nothing renders. A check fails when a source names a glyph the request is
missing, because an absent glyph does not draw a placeholder — the element
renders the glyph's name as text. The check reads the panel templates as well
as the scripts, and reads glyph names written as literals, so a glyph chosen
through a variable has to be added to the request by hand. Contributed by
mml-studio (#239).
- Separate explicit browser build settings from standalone environment loading - Separate explicit browser build settings from standalone environment loading
and local provider middleware. Preserve provider behavior and root named exports. and local provider middleware. Preserve provider behavior and root named exports.
- Rename standalone browser startup to `src/standalone/` and add a Node-only - Rename standalone browser startup to `src/standalone/` and add a Node-only
+5 -2
View File
@@ -9,8 +9,11 @@
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0" rel="stylesheet" /> <!-- Icon font subset to the glyphs this app renders. Keep `icon_names` in sync
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet" /> with the sources: src/materialSymbolsSubset.test.mjs fails when a source
names a glyph this list is missing, because a missing glyph does not draw
a box — the ligature never forms and the element renders the literal word. -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,chevron_left,chevron_right,close,close_fullscreen,dark_mode,draw,east,flare,flight,layers_clear,light_mode,local_fire_department,my_location,navigation,normal,on,open_in_full,public,radar,radio,right_panel_close,right_panel_open,rocket_launch,skip_next,skip_previous,view_in_ar" rel="stylesheet" />
</head> </head>
<body> <body>
<!-- gev:template scene-chrome --> <!-- gev:template scene-chrome -->
+2 -1
View File
@@ -614,5 +614,6 @@
"src/layers/vessels/ingestion.test.mjs", "src/layers/vessels/ingestion.test.mjs",
"src/cityPresets.test.mjs", "src/cityPresets.test.mjs",
"server/providers/cctv/range.js", "server/providers/cctv/range.js",
"src/data/cctvMediaRange.test.mjs" "src/data/cctvMediaRange.test.mjs",
"src/materialSymbolsSubset.test.mjs"
] ]
+111
View File
@@ -0,0 +1,111 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const SRC_ROOT = fileURLToPath(new URL('.', import.meta.url));
const REPO_ROOT = path.resolve(SRC_ROOT, '..');
const INDEX_HTML = path.join(REPO_ROOT, 'index.html');
/** The glyph written as element text: `<span class="material-symbols-outlined">radar</span>`. */
const SPAN_TEXT =
/class="[^"]*material-symbols-outlined[^"]*"[^>]*>\s*([a-z0-9_]+)\s*</g;
/** A whole `textContent =` statement, across lines, so a multi-line ternary is read once. */
const TEXT_ASSIGNMENT = /(?:textContent|innerText)\s*=\s*([^;]{0,400})/gs;
const STRING_LITERAL = /['"`]([a-z0-9_]{2,})['"`]/g;
/**
* Files that SHIP markup. Tests assert on markup, they do not render it.
*
* The panel markup lives in `src/ui/templates/*.html`, so HTML under `src` is
* read as well: a glyph named only in a template is still a glyph the font has
* to carry.
*/
function sourceFiles(directory = SRC_ROOT) {
const files = [];
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const absolute = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...sourceFiles(absolute));
else if (
entry.isFile() &&
/\.(js|mjs|html)$/.test(entry.name) &&
!entry.name.endsWith('.test.mjs')
) {
files.push(absolute);
}
}
return [...files.sort(), INDEX_HTML];
}
/**
* Glyph names the sources ask the icon font for.
*
* Errs WIDE on purpose. A name the font does not carry costs nothing — Google
* ignores an unknown `icon_names` entry and still returns 200 — while a glyph
* the subset is missing breaks the interface SILENTLY: the ligature never
* forms, so the element renders the literal word `right_panel_open` instead of
* falling back to a visible box.
*
* A literal to the LEFT of a `?` is the condition being tested, not the text
* being shown, so it is dropped: `status === 'loading' ? …` must not enrol
* `loading`. Optional chaining is neutralised first — `payload?.newsStatus`
* is not a ternary, and splitting on its `?` would keep the condition.
* @returns {Map<string, string>} glyph -> the first file that names it.
*/
function referencedGlyphs() {
const found = new Map();
for (const file of sourceFiles()) {
const source = readFileSync(file, 'utf8');
const relative = path.relative(REPO_ROOT, file).split(path.sep).join('/');
const add = (glyph) => {
if (!found.has(glyph)) found.set(glyph, relative);
};
for (const match of source.matchAll(SPAN_TEXT)) add(match[1]);
for (const match of source.matchAll(TEXT_ASSIGNMENT)) {
const statement = match[1].replaceAll('?.', '.');
const assigned = statement.includes('?')
? statement.slice(statement.indexOf('?') + 1)
: statement;
for (const literal of assigned.matchAll(STRING_LITERAL)) add(literal[1]);
}
}
return found;
}
/** The `icon_names` list index.html asks Google for. */
function subsettedGlyphs(html = readFileSync(INDEX_HTML, 'utf8')) {
const match =
/Material\+Symbols\+Outlined[^"]*[?&]icon_names=([a-z0-9_,]+)/.exec(html);
assert.ok(
match,
'index.html must request Material Symbols with an icon_names subset',
);
return new Set(match[1].split(','));
}
test('every glyph the sources render is in the icon_names subset', () => {
const subset = subsettedGlyphs();
const missing = [...referencedGlyphs()]
.filter(([glyph]) => !subset.has(glyph))
.map(([glyph, file]) => `${glyph} (${file})`);
assert.deepEqual(
missing,
[],
'Glyphs named by the sources but absent from the index.html icon_names list. ' +
'Add them there — an unlisted glyph renders as its own name on screen: ' +
missing.join(', '),
);
});
test('the unused Material Icons Round family is not loaded', () => {
// A second icon font, 173 kB, for a family no source ever uses — and
// src/cockpitMarkup.test.mjs already asserts the markup must not use it.
const html = readFileSync(INDEX_HTML, 'utf8');
assert.doesNotMatch(
html,
/Material\+Icons\+Round/,
'index.html loads an icon font nothing renders',
);
});