import { existsSync } from "node:fs"; import { createRequire } from "node:module"; import { dirname, join } from "node:path"; import { fileURLToPath, URL } from "node:url"; import { defineConfig, type Plugin } from "vite"; import react from "@vitejs/plugin-react"; const require = createRequire(import.meta.url); const webuiRoot = dirname(fileURLToPath(import.meta.url)); const installedModulesVirtualId = "virtual:govoplan-installed-modules"; const resolvedInstalledModulesVirtualId = `\0${installedModulesVirtualId}`; const installedModuleVirtualPrefix = "virtual:govoplan-installed-module/"; const resolvedInstalledModuleVirtualPrefix = `\0${installedModuleVirtualPrefix}`; const defaultWebModulePackages = [ "@govoplan/access-webui", "@govoplan/admin-webui", "@govoplan/addresses-webui", "@govoplan/audit-webui", "@govoplan/calendar-webui", "@govoplan/campaign-webui", "@govoplan/dataflow-webui", "@govoplan/datasources-webui", "@govoplan/dashboard-webui", "@govoplan/docs-webui", "@govoplan/files-webui", "@govoplan/idm-webui", "@govoplan/mail-webui", "@govoplan/notifications-webui", "@govoplan/organizations-webui", "@govoplan/ops-webui", "@govoplan/policy-webui", "@govoplan/postbox-webui", "@govoplan/risk-compliance-webui", "@govoplan/scheduling-webui", "@govoplan/search-webui", "@govoplan/tenancy-webui", "@govoplan/views-webui", "@govoplan/workflow-webui" ]; function configuredWebModulePackages(): string[] { const configured = process.env.GOVOPLAN_WEBUI_MODULE_PACKAGES; if (configured === undefined) return defaultWebModulePackages; return configured.split(",").map((specifier) => specifier.trim()).filter(Boolean); } function packageDirectory(specifier: string): string { const parts = specifier.startsWith("@") ? specifier.split("/") : [specifier]; return join(webuiRoot, "node_modules", ...parts); } function packageInstalled(specifier: string): boolean { if (existsSync(join(packageDirectory(specifier), "package.json"))) return true; try { require.resolve(specifier); return true; } catch { return false; } } function availableWebModuleSpecifiers(): string[] { return configuredWebModulePackages().filter(packageInstalled); } function deferredVendorChunk(id: string): string | undefined { const normalized = id.replaceAll("\\", "/"); if (!normalized.includes("/node_modules/")) return undefined; if ( normalized.includes("/@tiptap/core/") || normalized.includes("/@tiptap/react/") ) { return "rich-text-core"; } if (normalized.includes("/@tiptap/")) { return "rich-text-extensions"; } if ( normalized.includes("/prosemirror-") || normalized.includes("/linkifyjs/") || normalized.includes("/orderedmap/") || normalized.includes("/rope-sequence/") ) { return "rich-text-engine"; } if (normalized.includes("/bpmn-js-properties-panel/")) { return "bpmn-properties-provider"; } if (normalized.includes("/@bpmn-io/properties-panel/")) { return "bpmn-properties-ui"; } if (normalized.includes("/@codemirror/view/")) { return "bpmn-expression-view"; } if (normalized.includes("/@codemirror/state/")) { return "bpmn-expression-state"; } if (normalized.includes("/@codemirror/language/")) { return "bpmn-expression-language"; } if (normalized.includes("/@codemirror/")) { return "bpmn-expression-tools"; } if (normalized.includes("/@lezer/")) { return "bpmn-expression-parser"; } if ( normalized.includes("/@bpmn-io/feel-editor/") || normalized.includes("/@bpmn-io/feelers-editor/") ) { return "bpmn-expression-editors"; } if (normalized.includes("/focus-trap/")) { return "bpmn-properties-focus"; } if (normalized.includes("/bpmn-js/")) return "bpmn-modeler"; if (normalized.includes("/diagram-js/")) return "bpmn-diagram-engine"; if ( normalized.includes("/bpmn-moddle/") || normalized.includes("/moddle/") || normalized.includes("/moddle-xml/") || normalized.includes("/saxen/") ) { return "bpmn-xml"; } return undefined; } function govoplanInstalledModulesPlugin(): Plugin { const specifierByModuleVirtualId = new Map( availableWebModuleSpecifiers().map((specifier) => [ `${installedModuleVirtualPrefix}${encodeURIComponent(specifier)}`, specifier ]) ); return { name: "govoplan-installed-modules", resolveId(id: string) { if (id === installedModulesVirtualId) return resolvedInstalledModulesVirtualId; if (specifierByModuleVirtualId.has(id)) return `\0${id}`; return null; }, load(id: string) { if (id === resolvedInstalledModulesVirtualId) { const loaders = [...specifierByModuleVirtualId].map(([virtualId, specifier]) => ( `{ packageName: ${JSON.stringify(specifier)}, load: () => import(${JSON.stringify(virtualId)}) }` )); return [ `export const installedWebModuleLoaders = [${loaders.join(", ")}];`, "export default installedWebModuleLoaders;" ].join("\n"); } if (!id.startsWith(resolvedInstalledModuleVirtualPrefix)) return null; const publicVirtualId = id.slice(1); const specifier = specifierByModuleVirtualId.get(publicVirtualId); if (!specifier) return null; const moduleEntry = join(packageDirectory(specifier), "src", "module.ts"); if (!existsSync(moduleEntry)) { throw new Error(`${specifier} does not expose src/module.ts`); } // Import the contribution descriptor directly. Package root barrels may // intentionally re-export pages and would otherwise defeat route splitting. return `export { default } from ${JSON.stringify(moduleEntry)};`; } }; } export default defineConfig({ plugins: [govoplanInstalledModulesPlugin(), react()], optimizeDeps: { exclude: availableWebModuleSpecifiers(), // Dataflow is discovered through the virtual module registry, so Vite's // initial scan cannot see this CommonJS-backed transitive dependency. include: ["@xyflow/react"] }, build: { manifest: true, rollupOptions: { output: { manualChunks: deferredVendorChunk, // Keep dependencies of deferred BPMN packages in their lazy graph. The // legacy Rollup behavior merged those dependencies into manual chunks // and hoisted the properties-panel runtime into the application entry. onlyExplicitManualChunks: true } }, chunkSizeWarningLimit: 500 }, resolve: { preserveSymlinks: false, dedupe: [ "@tiptap/core", "@tiptap/pm", "@tiptap/react", "@xyflow/react", "@bpmn-io/properties-panel", "bpmn-js", "bpmn-js-properties-panel", "bpmn-moddle", "lucide-react", "react", "react-dom", "react-router", "read-excel-file" ], alias: [ { find: "@govoplan/core-webui/app", replacement: fileURLToPath(new URL("./src/app.ts", import.meta.url)) }, { find: "@govoplan/core-webui/definition-graph", replacement: fileURLToPath(new URL("./src/definitionGraph.ts", import.meta.url)) }, { find: "@govoplan/core-webui/wysiwyg", replacement: fileURLToPath(new URL("./src/wysiwyg.ts", import.meta.url)) }, { find: "@govoplan/core-webui", replacement: fileURLToPath(new URL("./src/index.ts", import.meta.url)) } ] }, server: { fs: { allow: [ fileURLToPath(new URL('.', import.meta.url)), fileURLToPath(new URL('../../govoplan-access/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-admin/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-addresses/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-audit/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-calendar/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-dataflow/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-datasources/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-dashboard/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-docs/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-files/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-idm/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-mail/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-notifications/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-organizations/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-campaign/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-ops/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-policy/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-postbox/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-risk-compliance/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-scheduling/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-search/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-tenancy/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-views/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-workflow/webui', import.meta.url)) ] }, proxy: { "/api": { target: process.env.VITE_API_PROXY_TARGET || "http://127.0.0.1:8000", changeOrigin: true }, "/health": { target: process.env.VITE_API_PROXY_TARGET || "http://127.0.0.1:8000", changeOrigin: true } } } });