fix(c3,arch-019): static chunk map for plugin components - production build loads plugin pages correctly
This commit is contained in:
@@ -91,6 +91,62 @@ class PluginErrorBoundary extends Component<PluginErrorBoundaryProps, PluginErro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Static Chunk Map (ARCH-019) ─────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Vite cannot statically analyze dynamic import paths built at runtime
|
||||||
|
// (`import(/* @vite-ignore */ path)`), so production builds would fail to
|
||||||
|
// load plugin pages. All plugin-contributed component paths are known at
|
||||||
|
// build time — they are declared in the backend manifests — so we register
|
||||||
|
// them here as explicit lazy imports. Vite chunks each one correctly.
|
||||||
|
//
|
||||||
|
// Unknown paths (e.g. third-party plugins added later) fall back to the
|
||||||
|
// runtime dynamic import, which works in dev mode.
|
||||||
|
|
||||||
|
type ComponentModule = Record<string, unknown> & { default?: React.ComponentType<any> };
|
||||||
|
type LazyComponentFactory = () => Promise<{ default: React.ComponentType<any> }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a module namespace to the { default } shape React.lazy expects:
|
||||||
|
* uses the default export when present, otherwise the first named export
|
||||||
|
* (same fallback as the runtime dynamic-import path below).
|
||||||
|
*/
|
||||||
|
function normalizeModule(m: ComponentModule): { default: React.ComponentType<any> } {
|
||||||
|
const Comp = (m.default ?? m[Object.keys(m)[0]]) as React.ComponentType<any>;
|
||||||
|
return { default: Comp };
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATIC_COMPONENT_MAP: Record<string, LazyComponentFactory> = {
|
||||||
|
// Pages
|
||||||
|
'@/pages/AgentDashboard': () => import('@/pages/AgentDashboard').then(normalizeModule),
|
||||||
|
// NOTE: @/pages/AIAssistant is declared in the backend manifest but does
|
||||||
|
// not exist as a frontend page — ghost component, see PROGRESS.md.
|
||||||
|
'@/pages/AISettings': () => import('@/pages/AISettings').then(normalizeModule),
|
||||||
|
'@/pages/AutomationDashboard': () => import('@/pages/AutomationDashboard').then(normalizeModule),
|
||||||
|
'@/pages/AutomationSettings': () => import('@/pages/AutomationSettings').then(normalizeModule),
|
||||||
|
'@/pages/Calendar': () => import('@/pages/Calendar').then(normalizeModule),
|
||||||
|
'@/pages/Communication': () => import('@/pages/Communication').then(normalizeModule),
|
||||||
|
'@/pages/Dms': () => import('@/pages/Dms').then(normalizeModule),
|
||||||
|
'@/pages/DmsTrash': () => import('@/pages/DmsTrash').then(normalizeModule),
|
||||||
|
'@/pages/GlobalSearchResults': () => import('@/pages/GlobalSearchResults').then(normalizeModule),
|
||||||
|
'@/pages/ImportExport': () => import('@/pages/ImportExport').then(normalizeModule),
|
||||||
|
'@/pages/Mail': () => import('@/pages/Mail').then(normalizeModule),
|
||||||
|
'@/pages/MailSettings': () => import('@/pages/MailSettings').then(normalizeModule),
|
||||||
|
'@/pages/ProactiveAISettings': () => import('@/pages/ProactiveAISettings').then(normalizeModule),
|
||||||
|
'@/pages/Reports': () => import('@/pages/Reports').then(normalizeModule),
|
||||||
|
'@/pages/SettingsGroups': () => import('@/pages/SettingsGroups').then(normalizeModule),
|
||||||
|
'@/pages/SettingsNotifications': () => import('@/pages/SettingsNotifications').then(normalizeModule),
|
||||||
|
'@/pages/SettingsRoles': () => import('@/pages/SettingsRoles').then(normalizeModule),
|
||||||
|
'@/pages/SettingsUsers': () => import('@/pages/SettingsUsers').then(normalizeModule),
|
||||||
|
'@/pages/Tasks': () => import('@/pages/Tasks').then(normalizeModule),
|
||||||
|
'@/pages/Wiki': () => import('@/pages/Wiki').then(normalizeModule),
|
||||||
|
'@/pages/Workflows': () => import('@/pages/Workflows').then(normalizeModule),
|
||||||
|
// NOTE: The backend manifests also declare contact detail tabs
|
||||||
|
// (@/components/contact/Contact*Tab) whose components do not exist in the
|
||||||
|
// frontend yet — they are intentionally NOT registered here; they fall
|
||||||
|
// through to the runtime fallback and surface via the error boundary.
|
||||||
|
// See PROGRESS.md "Ghost components" finding.
|
||||||
|
};
|
||||||
|
|
||||||
// ── Lazy Component Cache ────────────────────────────────────────────────
|
// ── Lazy Component Cache ────────────────────────────────────────────────
|
||||||
|
|
||||||
const componentCache = new Map<string, React.LazyExoticComponent<React.ComponentType<any>>>();
|
const componentCache = new Map<string, React.LazyExoticComponent<React.ComponentType<any>>>();
|
||||||
@@ -100,8 +156,16 @@ function getLazyComponent(componentPath: string): React.LazyExoticComponent<Reac
|
|||||||
return componentCache.get(componentPath)!;
|
return componentCache.get(componentPath)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert @/pages/Mail to ../../pages/Mail for dynamic import
|
// Known plugin components: statically chunked at build time (ARCH-019)
|
||||||
// PluginLoader.tsx is in src/components/plugins/, so ../../ resolves to src/
|
const factory = STATIC_COMPONENT_MAP[componentPath];
|
||||||
|
if (factory) {
|
||||||
|
const LazyComp = lazy(factory);
|
||||||
|
componentCache.set(componentPath, LazyComp);
|
||||||
|
return LazyComp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for unknown paths (third-party plugins): runtime dynamic
|
||||||
|
// import. Works in dev; production needs the path registered above.
|
||||||
const importPath = componentPath.replace(/^@\//, '../../');
|
const importPath = componentPath.replace(/^@\//, '../../');
|
||||||
|
|
||||||
const LazyComp = lazy(() =>
|
const LazyComp = lazy(() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user