Initial Watermaps import
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import { existsSync, readFileSync, statSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
|
||||
const webRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const distRoot = path.join(webRoot, "dist");
|
||||
const manifestPath = path.join(distRoot, ".vite", "manifest.json");
|
||||
|
||||
assert(existsSync(manifestPath), "Vite-Manifest fehlt. Zuerst den Produktions-Build ausführen.");
|
||||
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
const entry = manifest["index.html"];
|
||||
assert(entry?.isEntry, "Der Web-Einstieg fehlt im Vite-Manifest.");
|
||||
|
||||
const expectedDynamicEntries = [
|
||||
"src/components/MapView.tsx",
|
||||
"src/components/AnchorWatchPanel.tsx",
|
||||
"src/components/CourseAssistantPanel.tsx",
|
||||
"src/components/VoyageNavigationTools.tsx",
|
||||
"src/routeWeatherReport.ts"
|
||||
];
|
||||
|
||||
for (const key of expectedDynamicEntries) {
|
||||
const chunk = manifest[key];
|
||||
assert(chunk?.isDynamicEntry, `${key} ist kein dynamischer Einstieg mehr.`);
|
||||
assertFile(chunk.file);
|
||||
}
|
||||
|
||||
const mapEngineEntry = Object.entries(manifest).find(([, chunk]) => chunk.name === "map-engine");
|
||||
assert(mapEngineEntry, "Der isolierte MapLibre-Chunk fehlt.");
|
||||
const [mapEngineKey, mapEngine] = mapEngineEntry;
|
||||
assertFile(mapEngine.file);
|
||||
assert(
|
||||
!entry.imports?.includes(mapEngineKey),
|
||||
"Der MapLibre-Chunk wird wieder statisch vom App-Einstieg geladen."
|
||||
);
|
||||
assert(
|
||||
manifest["src/components/MapView.tsx"].imports?.includes(mapEngineKey),
|
||||
"MapView verweist nicht auf den isolierten MapLibre-Chunk."
|
||||
);
|
||||
const mapViewChunk = manifest["src/components/MapView.tsx"];
|
||||
assert(mapViewChunk.css?.length, "Das MapLibre-Stylesheet ist nicht mehr an MapView gekoppelt.");
|
||||
assert(
|
||||
mapViewChunk.css.every((file) => !entry.css?.includes(file)),
|
||||
"Das MapLibre-Stylesheet wird wieder vom App-Einstieg geladen."
|
||||
);
|
||||
|
||||
const initialChunkKeys = collectStaticImports("index.html");
|
||||
const initialBytes = [...initialChunkKeys].reduce(
|
||||
(total, key) => total + fileSize(manifest[key].file),
|
||||
0
|
||||
);
|
||||
assert(
|
||||
initialBytes <= 350_000,
|
||||
`Initiales JavaScript ist mit ${formatKb(initialBytes)} größer als das Budget von 350 kB.`
|
||||
);
|
||||
|
||||
const mapEngineBytes = fileSize(mapEngine.file);
|
||||
assert(
|
||||
mapEngineBytes <= 1_100_000,
|
||||
`Der MapLibre-Chunk ist mit ${formatKb(mapEngineBytes)} unerwartet gewachsen.`
|
||||
);
|
||||
for (const chunk of Object.values(manifest)) {
|
||||
if (chunk.file?.endsWith(".js") && chunk.file !== mapEngine.file) {
|
||||
const bytes = fileSize(chunk.file);
|
||||
assert(
|
||||
bytes <= 350_000,
|
||||
`${chunk.file} ist mit ${formatKb(bytes)} zu groß und sollte weiter aufgeteilt werden.`
|
||||
);
|
||||
}
|
||||
}
|
||||
const initialCssBytes = (entry.css ?? []).reduce(
|
||||
(total, file) => total + fileSize(file),
|
||||
0
|
||||
);
|
||||
assert(
|
||||
initialCssBytes <= 40_000,
|
||||
`Initiales CSS ist mit ${formatKb(initialCssBytes)} größer als das Budget von 40 kB.`
|
||||
);
|
||||
|
||||
const html = readFileSync(path.join(distRoot, "index.html"), "utf8");
|
||||
assert(
|
||||
!html.includes(path.basename(mapEngine.file)),
|
||||
"index.html lädt den dynamischen MapLibre-Chunk per modulepreload."
|
||||
);
|
||||
const serviceWorkerPath = path.join(distRoot, "sw.js");
|
||||
assert(existsSync(serviceWorkerPath), "Der PWA-Service-Worker fehlt.");
|
||||
const serviceWorker = readFileSync(serviceWorkerPath, "utf8");
|
||||
const offlineFiles = [
|
||||
mapEngine.file,
|
||||
...mapViewChunk.css,
|
||||
...expectedDynamicEntries.map((key) => manifest[key].file)
|
||||
];
|
||||
for (const file of offlineFiles) {
|
||||
assert(
|
||||
serviceWorker.includes(file),
|
||||
`${file} fehlt im PWA-Precache und wäre offline nicht zuverlässig verfügbar.`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Chunk-Prüfung erfolgreich: initial ${formatKb(initialBytes)} JS + ${formatKb(initialCssBytes)} CSS, Karte ${formatKb(mapEngineBytes)}, ${expectedDynamicEntries.length} dynamische Funktionsmodule.`
|
||||
);
|
||||
|
||||
function collectStaticImports(rootKey) {
|
||||
const collected = new Set();
|
||||
const visit = (key) => {
|
||||
if (collected.has(key)) {
|
||||
return;
|
||||
}
|
||||
const chunk = manifest[key];
|
||||
assert(chunk, `Manifest-Verweis ${key} fehlt.`);
|
||||
collected.add(key);
|
||||
for (const dependency of chunk.imports ?? []) {
|
||||
visit(dependency);
|
||||
}
|
||||
};
|
||||
visit(rootKey);
|
||||
return collected;
|
||||
}
|
||||
|
||||
function assertFile(relativePath) {
|
||||
assert(
|
||||
typeof relativePath === "string" && existsSync(path.join(distRoot, relativePath)),
|
||||
`Chunk-Datei ${relativePath ?? "(unbekannt)"} fehlt.`
|
||||
);
|
||||
}
|
||||
|
||||
function fileSize(relativePath) {
|
||||
return statSync(path.join(distRoot, relativePath)).size;
|
||||
}
|
||||
|
||||
function formatKb(bytes) {
|
||||
return `${(bytes / 1_000).toFixed(1)} kB`;
|
||||
}
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user