refactor: convert map feature to strict TypeScript

This commit is contained in:
sjg
2026-08-01 13:39:41 +02:00
parent 0338c8b8d2
commit 82121491c5
8 changed files with 3606 additions and 3451 deletions
File diff suppressed because it is too large Load Diff
@@ -10,25 +10,11 @@ const pluginGroups = {
}; };
const loaded = /* @__PURE__ */ new Set(); const loaded = /* @__PURE__ */ new Set();
const loading = /* @__PURE__ */ new Map(); const loading = /* @__PURE__ */ new Map();
const modulePlugins = /* @__PURE__ */ new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js", "/background-decode.js", "/ais.js", "/aprs.js", "/hf-aprs.js", "/sat.js", "/sat-scheduler.js", "/vchan.js", "/bookmarks.js", "/scheduler.js"]);
function loadLegacyScript(path) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = path;
script.addEventListener("load", () => {
resolve();
}, { once: true });
script.addEventListener("error", () => {
reject(new Error(`Failed to load plugin script: ${path}`));
}, { once: true });
document.body.appendChild(script);
});
}
async function loadPlugin(path) { async function loadPlugin(path) {
if (loaded.has(path)) return; if (loaded.has(path)) return;
const pending = loading.get(path); const pending = loading.get(path);
if (pending) return pending; if (pending) return pending;
const request = (modulePlugins.has(path) ? import(path).then(() => void 0) : loadLegacyScript(path)).then(() => { const request = import(path).then(() => {
loaded.add(path); loaded.add(path);
loading.delete(path); loading.delete(path);
}).catch((error) => { }).catch((error) => {
@@ -18,7 +18,6 @@ await build({
"api-client": path.join(sourceDir, "api", "client.ts"), "api-client": path.join(sourceDir, "api", "client.ts"),
app: path.join(sourceDir, "app.js"), app: path.join(sourceDir, "app.js"),
"ui-core": path.join(sourceDir, "ui-core.ts"), "ui-core": path.join(sourceDir, "ui-core.ts"),
"map-core": path.join(sourceDir, "map-core.js"),
"plugin-loader": path.join(sourceDir, "plugin-loader.ts"), "plugin-loader": path.join(sourceDir, "plugin-loader.ts"),
"plugin-runtime": path.join(sourceDir, "plugin-runtime.ts"), "plugin-runtime": path.join(sourceDir, "plugin-runtime.ts"),
screenshot: path.join(sourceDir, "screenshot.ts"), screenshot: path.join(sourceDir, "screenshot.ts"),
@@ -52,6 +51,7 @@ await build({
vchan: path.join(sourceDir, "plugins", "vchan.ts"), vchan: path.join(sourceDir, "plugins", "vchan.ts"),
bookmarks: path.join(sourceDir, "plugins", "bookmarks.ts"), bookmarks: path.join(sourceDir, "plugins", "bookmarks.ts"),
scheduler: path.join(sourceDir, "plugins", "scheduler.ts"), scheduler: path.join(sourceDir, "plugins", "scheduler.ts"),
"map-core": path.join(sourceDir, "map-core.ts"),
}, },
outdir: outputDir, outdir: outputDir,
bundle: true, bundle: true,
@@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"devDependencies": { "devDependencies": {
"@eslint/js": "9.39.2", "@eslint/js": "9.39.2",
"@types/leaflet": "1.9.22",
"esbuild": "0.25.12", "esbuild": "0.25.12",
"eslint": "9.39.2", "eslint": "9.39.2",
"globals": "16.5.0", "globals": "16.5.0",
@@ -691,6 +692,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -698,6 +706,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/leaflet": {
"version": "1.9.22",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.22.tgz",
"integrity": "sha512-h3lhECYEKDasG7LFHu+GiHqAvsgLuQvlJvVZzJDGONo3sEL+wUOqSFLnwkZlK0qVxnxbuGFW8iBlJNYs5wgndA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.51.0", "version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
@@ -16,6 +16,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "9.39.2", "@eslint/js": "9.39.2",
"@types/leaflet": "1.9.22",
"esbuild": "0.25.12", "esbuild": "0.25.12",
"eslint": "9.39.2", "eslint": "9.39.2",
"globals": "16.5.0", "globals": "16.5.0",
@@ -16,23 +16,11 @@ const pluginGroups: Readonly<Record<PluginGroup, readonly string[]>> = {
const loaded = new Set<string>(); const loaded = new Set<string>();
const loading = new Map<string, Promise<void>>(); const loading = new Map<string, Promise<void>>();
const modulePlugins = new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js", "/background-decode.js", "/ais.js", "/aprs.js", "/hf-aprs.js", "/sat.js", "/sat-scheduler.js", "/vchan.js", "/bookmarks.js", "/scheduler.js"]);
function loadLegacyScript(path: string): Promise<void> {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = path;
script.addEventListener("load", () => { resolve(); }, { once: true });
script.addEventListener("error", () => { reject(new Error(`Failed to load plugin script: ${path}`)); }, { once: true });
document.body.appendChild(script);
});
}
async function loadPlugin(path: string): Promise<void> { async function loadPlugin(path: string): Promise<void> {
if (loaded.has(path)) return; if (loaded.has(path)) return;
const pending = loading.get(path); const pending = loading.get(path);
if (pending) return pending; if (pending) return pending;
const request = (modulePlugins.has(path) ? import(path).then(() => undefined) : loadLegacyScript(path)).then(() => { const request = import(path).then(() => {
loaded.add(path); loaded.add(path);
loading.delete(path); loading.delete(path);
}).catch((error: unknown) => { }).catch((error: unknown) => {
@@ -7,6 +7,8 @@ import test from "node:test";
import { readFile } from "node:fs/promises"; import { readFile } from "node:fs/promises";
const indexPath = new URL("../../assets/web/index.html", import.meta.url); const indexPath = new URL("../../assets/web/index.html", import.meta.url);
const pluginLoaderPath = new URL("../src/plugin-loader.ts", import.meta.url);
const mapCorePath = new URL("../src/map-core.ts", import.meta.url);
test("index loads the shared UI before the application", async () => { test("index loads the shared UI before the application", async () => {
const html = await readFile(indexPath, "utf8"); const html = await readFile(indexPath, "utf8");
@@ -26,3 +28,14 @@ test("startup has no remote script or stylesheet dependencies", async () => {
false, false,
); );
}); });
test("lazy frontend features use modules and local map symbols", async () => {
const [loader, map] = await Promise.all([
readFile(pluginLoaderPath, "utf8"),
readFile(mapCorePath, "utf8"),
]);
assert.equal(loader.includes("createElement(\"script\")"), false);
assert.match(loader, /import\(path\)/);
assert.match(map, /aprs-symbol-local/);
assert.equal(map.includes("raw.githubusercontent.com"), false);
});