perf(webui): lazily load module descriptors

This commit is contained in:
2026-07-30 04:35:57 +02:00
parent 9e219bc4d3
commit 47e106684d
14 changed files with 604 additions and 84 deletions
+86 -33
View File
@@ -1,5 +1,5 @@
import { Activity, Bell, BookUser, Building2, CalendarClock, CalendarDays, ClipboardPenLine, DatabaseZap, Folder, Form, Inbox, LayoutDashboard, LayoutTemplate, Mail, Mails, RadioTower, Shield, ShieldCheck, Users, Waypoints, Workflow as WorkflowIcon, type LucideIcon } from "lucide-react";
import installedWebModules from "virtual:govoplan-installed-modules";
import installedWebModuleLoaderSource from "virtual:govoplan-installed-modules";
import type { AuthInfo, DashboardWidgetContribution, DashboardWidgetsUiCapability, EffectiveViewProjection, PlatformModuleInfo, PlatformNavItem, PlatformPublicModuleInfo, PlatformViewSurface, PlatformWebModule } from "../types";
import {
hasUiCapability as hasUiCapabilityForModules,
@@ -21,12 +21,20 @@ import {
export const fallbackDashboardNavItem: PlatformNavItem =
{ to: "/dashboard", label: "i18n:govoplan-core.dashboard.d87f47b4", iconName: "dashboard", order: 10 };
type InstalledWebModuleLoader = {
packageName: string;
load: () => Promise<{ default: PlatformWebModule }>;
};
const installedWebModuleLoaders =
installedWebModuleLoaderSource as unknown as InstalledWebModuleLoader[];
export function shellNavItemsForModules(modules: PlatformWebModule[]): PlatformNavItem[] {
return moduleInstalledForModules("dashboard", modules) ? [] : [fallbackDashboardNavItem];
}
const localModules: PlatformWebModule[] = installedWebModules;
const localModuleCache = new Map<string, Promise<PlatformWebModule | null>>();
const loadedLocalModules = new Map<string, PlatformWebModule>();
const remoteModuleCache = new Map<string, Promise<PlatformWebModule | null>>();
declare global {
@@ -214,44 +222,39 @@ function publicModuleInfo(info: PlatformPublicModuleInfo): PlatformModuleInfo {
};
}
export function resolveInstalledPublicWebModules(
export async function loadInstalledPublicWebModules(
platformModules: PlatformPublicModuleInfo[] | null | undefined
): PlatformWebModule[] {
): Promise<PlatformWebModule[]> {
if (!platformModules?.length) return [];
const localById = new Map(localModules.map((module) => [module.id, module]));
return platformModules.flatMap((info) => {
const local = localById.get(info.id);
if (!local) return [];
const resolved = applyServerMetadata(local, publicModuleInfo(info));
return resolved.publicRoutes?.length ? [resolved] : [];
});
const loaded = await loadInstalledWebModules(platformModules.map(publicModuleInfo));
return loaded.filter((module) => module.publicRoutes?.length);
}
export async function loadRemotePublicWebModules(
platformModules: PlatformPublicModuleInfo[] | null | undefined,
alreadyLoaded: PlatformWebModule[] = resolveInstalledPublicWebModules(platformModules)
alreadyLoaded: PlatformWebModule[] = []
): Promise<PlatformWebModule[]> {
if (!platformModules?.length) return [];
const loaded = await loadRemoteWebModules(platformModules.map(publicModuleInfo), alreadyLoaded);
return loaded.filter((module) => module.publicRoutes?.length);
}
export function resolveInstalledWebModules(platformModules: PlatformModuleInfo[] | null | undefined): PlatformWebModule[] {
if (!platformModules?.length) return localModules;
export async function loadInstalledWebModules(
platformModules: PlatformModuleInfo[] | null | undefined
): Promise<PlatformWebModule[]> {
if (!platformModules?.length) return [];
const localById = new Map(localModules.map((module) => [module.id, module]));
return platformModules.
filter((module) => module.enabled).
map((module) => {
const local = localById.get(module.id);
return local ? applyServerMetadata(local, module) : null;
}).
filter((module): module is PlatformWebModule => module !== null);
const enabledModules = platformModules.filter((module) => module.enabled);
const resolved = await Promise.all(enabledModules.map(async (info) => {
const local = await loadInstalledWebModule(info);
return local ? applyServerMetadata(local, info) : null;
}));
return resolved.filter((module): module is PlatformWebModule => module !== null);
}
export async function loadRemoteWebModules(
platformModules: PlatformModuleInfo[] | null | undefined,
alreadyLoaded: PlatformWebModule[] = resolveInstalledWebModules(platformModules))
alreadyLoaded: PlatformWebModule[] = [])
: Promise<PlatformWebModule[]> {
if (!platformModules?.length) return [];
const loadedIds = new Set(alreadyLoaded.map((module) => module.id));
@@ -274,6 +277,56 @@ alreadyLoaded: PlatformWebModule[] = resolveInstalledWebModules(platformModules)
return loaded.filter((module): module is PlatformWebModule => module !== null);
}
async function loadInstalledWebModule(info: PlatformModuleInfo): Promise<PlatformWebModule | null> {
if (!info.frontend) return null;
const packageName = info.frontend?.package_name;
if (packageName) {
const loader = installedWebModuleLoaders.find((candidate) => candidate.packageName === packageName);
return loader ? loadInstalledWebModulePackage(loader, info.id) : null;
}
// Legacy manifests may omit package_name. Keep compatibility without making
// every modern module eager: only this fallback imports all installed descriptors.
const candidates = await Promise.all(
installedWebModuleLoaders.map((loader) => loadInstalledWebModulePackage(loader))
);
return candidates.find((candidate) => candidate?.id === info.id) ?? null;
}
async function loadInstalledWebModulePackage(
loader: InstalledWebModuleLoader,
expectedModuleId?: string
): Promise<PlatformWebModule | null> {
let promise = localModuleCache.get(loader.packageName);
if (!promise) {
promise = loader.load().
then((imported) => {
const module = imported.default;
if (!isPlatformWebModule(module)) {
throw new Error(`${loader.packageName} does not export a PlatformWebModule`);
}
loadedLocalModules.set(loader.packageName, module);
return module;
}).
catch((error) => {
localModuleCache.delete(loader.packageName);
console.warn("GovOPlaN installed WebUI module was not loaded:", loader.packageName, error);
return null;
});
localModuleCache.set(loader.packageName, promise);
}
const module = await promise;
if (module && expectedModuleId && module.id !== expectedModuleId) {
console.warn(
"GovOPlaN installed WebUI package id mismatch:",
loader.packageName,
`expected ${expectedModuleId}, received ${module.id}`
);
return null;
}
return module;
}
async function loadRemoteWebModule(info: PlatformModuleInfo): Promise<PlatformWebModule | null> {
const frontend = info.frontend;
if (!frontend?.asset_manifest) return null;
@@ -387,26 +440,26 @@ function constantTimeEqual(left: string, right: string): boolean {
}
export function installedLocalWebModules(): PlatformWebModule[] {
return localModules;
return [...loadedLocalModules.values()];
}
export function uiCapability<T = unknown>(capabilityName: string, modules: PlatformWebModule[] = localModules): T | null {
export function uiCapability<T = unknown>(capabilityName: string, modules: PlatformWebModule[] = installedLocalWebModules()): T | null {
return uiCapabilityForModules<T>(capabilityName, modules);
}
export function uiCapabilities<T = unknown>(capabilityName: string, modules: PlatformWebModule[] = localModules): T[] {
export function uiCapabilities<T = unknown>(capabilityName: string, modules: PlatformWebModule[] = installedLocalWebModules()): T[] {
return uiCapabilitiesForModules<T>(capabilityName, modules);
}
export function hasUiCapability(capabilityName: string, modules: PlatformWebModule[] = localModules): boolean {
export function hasUiCapability(capabilityName: string, modules: PlatformWebModule[] = installedLocalWebModules()): boolean {
return hasUiCapabilityForModules(capabilityName, modules);
}
export function moduleInstalled(moduleId: string, modules: PlatformWebModule[] = localModules): boolean {
export function moduleInstalled(moduleId: string, modules: PlatformWebModule[] = installedLocalWebModules()): boolean {
return moduleInstalledForModules(moduleId, modules);
}
export function moduleIntegrationEnabled(moduleId: string, dependencyId: string, modules: PlatformWebModule[] = localModules): boolean {
export function moduleIntegrationEnabled(moduleId: string, dependencyId: string, modules: PlatformWebModule[] = installedLocalWebModules()): boolean {
return moduleIntegrationEnabledForModules(moduleId, dependencyId, modules);
}
@@ -419,7 +472,7 @@ export function publicRouteContributionsForModules(modules: PlatformWebModule[])
}
export function dashboardWidgetsForModules(
modules: PlatformWebModule[] = localModules,
modules: PlatformWebModule[] = installedLocalWebModules(),
projection?: EffectiveViewProjection | null
): DashboardWidgetContribution[] {
const catalogue = viewSurfaceCatalogueForModules(modules);
@@ -446,7 +499,7 @@ export function navItemsForModules(
sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
}
export function visibleNavItems(auth: AuthInfo | null | undefined, modules: PlatformWebModule[] = localModules, projection?: EffectiveViewProjection | null): PlatformNavItem[] {
export function visibleNavItems(auth: AuthInfo | null | undefined, modules: PlatformWebModule[] = installedLocalWebModules(), projection?: EffectiveViewProjection | null): PlatformNavItem[] {
return navItemsForModules(modules, projection).filter((item) => {
if (item.allOf?.length && !item.allOf.every((scope) => hasScope(auth, scope))) return false;
if (item.anyOf?.length && !hasAnyScope(auth, item.anyOf)) return false;
@@ -454,6 +507,6 @@ export function visibleNavItems(auth: AuthInfo | null | undefined, modules: Plat
});
}
export function firstAccessibleRoute(auth: AuthInfo, modules: PlatformWebModule[] = localModules, projection?: EffectiveViewProjection | null): string {
export function firstAccessibleRoute(auth: AuthInfo, modules: PlatformWebModule[] = installedLocalWebModules(), projection?: EffectiveViewProjection | null): string {
return visibleNavItems(auth, modules, projection)[0]?.to ?? "/dashboard";
}