mirror of
https://github.com/rommapp/romm.git
synced 2026-10-12 02:21:58 +00:00
perf(frontend): serve only the mdi icons the app uses (#5118)
* perf(frontend): serve only the mdi icons the app uses @mdi/font ships a CSS rule and four font formats for every icon. Generate a trimmed stylesheet from the icons named in the source and alias the package import to it. - scripts/build-icons.ts writes src/plugins/materialdesignicons-trimmed.css (woff2 only, LF line endings, no source map pointer). predev and prebuild run it through build:icons, and CI checks the committed file is current. - scripts/trim-mdi-fonts.ts builds the allow list from src/, Vuetify's own icon set and the icons the backend names. Only a plain .mdi-name::before rule can be dropped; anything unsure is kept. - Custom notification icons the app does not draw fall back to the level icon instead of rendering blank. - An "All icons (mdi)" story under Primitives/RIcon shows every shipped icon with search, plus the icons that are available but not shipped. - Vitest keeps ?raw CSS imports instead of emptying them. - build:tokens leaves tokens.css alone when only formatting differs. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * perf(frontend): trim the mdi icon css in a Vite plugin Replace the generated, committed icon CSS with a transform plugin that trims @mdi/font in memory, in dev, Storybook and build. A watcher reloads dev when the source names a new icon. Drops build-icons.ts, the alias, the CI staleness check and the checked-in CSS. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * PR feedback * style(frontend): sort imports in notifications test Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * perf(frontend): reduce the mdi icon trim to a build-only plugin Drop the icon browser story, the notification glyph probe, the build:tokens compare and the woff2-only font rewrite. The plugin now only removes unused icon rules in production builds, so dev serves the full set and needs no file watcher. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> Co-authored-by: Georges-Antoine Assi <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
Georges-Antoine Assi
parent
38dfb208f0
commit
4e8fec6845
@@ -0,0 +1,40 @@
|
||||
// Drops the @mdi/font icon rules nothing in src/ (or Vuetify itself) names.
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { extname, join } from "node:path";
|
||||
import type { Plugin } from "vite";
|
||||
|
||||
const ICON_NAME = /mdi-[a-z0-9-]+/g;
|
||||
const ICON_RULE = /\.(mdi-[a-z0-9-]+)::before\s*\{[^}]*\}/g;
|
||||
|
||||
export function usedIconNames(root: string): Set<string> {
|
||||
const files = readdirSync(join(root, "src"), {
|
||||
recursive: true,
|
||||
encoding: "utf8",
|
||||
})
|
||||
.filter((path) => [".vue", ".ts"].includes(extname(path)))
|
||||
.map((path) => join(root, "src", path));
|
||||
files.push(join(root, "node_modules/vuetify/lib/iconsets/mdi.js"));
|
||||
return new Set(
|
||||
files.flatMap((file) => readFileSync(file, "utf8").match(ICON_NAME) ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
export function trimIconCss(css: string, used: Set<string>): string {
|
||||
return css.replace(ICON_RULE, (rule, name) => (used.has(name) ? rule : ""));
|
||||
}
|
||||
|
||||
export function trimMdiIcons(): Plugin {
|
||||
let used = new Set<string>();
|
||||
return {
|
||||
name: "romm:trim-mdi-icons",
|
||||
apply: "build",
|
||||
enforce: "pre",
|
||||
configResolved(config) {
|
||||
used = usedIconNames(config.root);
|
||||
},
|
||||
transform(code, id) {
|
||||
if (!id.endsWith("@mdi/font/css/materialdesignicons.css")) return null;
|
||||
return { code: trimIconCss(code, used), map: null };
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { trimIconCss, usedIconNames } from "../scripts/trimMdiIcons";
|
||||
|
||||
const MDI_CSS = readFileSync(
|
||||
"node_modules/@mdi/font/css/materialdesignicons.css",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
describe("trimIconCss", () => {
|
||||
it("keeps used icon rules and the shared .mdi rules", () => {
|
||||
const css = trimIconCss(MDI_CSS, new Set(["mdi-account"]));
|
||||
|
||||
expect(css).toContain(".mdi-account::before");
|
||||
expect(css).not.toContain(".mdi-account-circle::before");
|
||||
expect(css).toContain("@font-face");
|
||||
expect(css).toContain(".mdi-spin");
|
||||
});
|
||||
});
|
||||
|
||||
describe("usedIconNames", () => {
|
||||
it("finds icons from src and the ones Vuetify draws", () => {
|
||||
const used = usedIconNames(".");
|
||||
|
||||
expect(used).toContain("mdi-magnify");
|
||||
expect(used).toContain("mdi-checkbox-marked");
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import { platformIconManifest } from "./scripts/platformIconManifest";
|
||||
import { playerIsolationHeaders } from "./scripts/playerIsolationHeaders";
|
||||
import { precompress } from "./scripts/precompress";
|
||||
import { previewAssets } from "./scripts/previewAssets";
|
||||
import { trimMdiIcons } from "./scripts/trimMdiIcons";
|
||||
|
||||
// Vuetify components to preoptimize for faster dev startup
|
||||
const VUETIFY_COMPONENTS = [
|
||||
@@ -135,6 +136,7 @@ export default defineConfig(({ mode }) => {
|
||||
playerIsolationHeaders(),
|
||||
previewAssets(),
|
||||
platformIconManifest(),
|
||||
trimMdiIcons(),
|
||||
httpsMode &&
|
||||
mkcert({
|
||||
savePath: "/app/.vite-plugin-mkcert",
|
||||
|
||||
Reference in New Issue
Block a user