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 ────────────────────────────────────────────────
|
||||
|
||||
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)!;
|
||||
}
|
||||
|
||||
// Convert @/pages/Mail to ../../pages/Mail for dynamic import
|
||||
// PluginLoader.tsx is in src/components/plugins/, so ../../ resolves to src/
|
||||
// Known plugin components: statically chunked at build time (ARCH-019)
|
||||
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 LazyComp = lazy(() =>
|
||||
|
||||
Reference in New Issue
Block a user