/** * Standard App Shell Vite Configuration Factory * * @description Creates a production-ready Vite config for app shells * @version Vite 8.0.0-beta.8+ * * Features: * - Application mode with optimized chunking * - Consumes microfrontends via source imports (build-time composition) * - Single React instance across all linked packages * - Hot reload with microfrontend source watching * - Production-ready build with chunk splitting * * @example * ```ts * // vite.config.ts * import { createAppShellConfig } from '@burdenoff/fe-libs/vite'; * * export default createAppShellConfig({ * name: 'workspaces', * port: 5200, * microfrontends: ['auth', 'billing', 'tags', 'files'], * }); * ``` */ import { defineConfig, type UserConfig, type Plugin } from 'vite'; import react from '@vitejs/plugin-react'; import tailwindcss from '@tailwindcss/vite'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; import { type AppShellConfig, STANDARD_WATCH_IGNORE, APP_SHELL_PORT_MAP } from './types'; import { createContextualAliasPlugin, createReactSingletonAliases } from './plugins'; /** * Creates a standardized Vite configuration for app shells. * * @param config - App shell configuration options * @param configPath - The path to the vite.config.ts file (use import.meta.url) * @returns Vite configuration object */ export function createAppShellConfig(config: AppShellConfig, configPath: string): UserConfig { // Resolve __dirname from the config file path const __dirname = typeof configPath === 'string' && configPath.startsWith('file://') ? dirname(fileURLToPath(configPath)) : configPath; const { name, port = APP_SHELL_PORT_MAP[name] || 5200, feLibsPath = '../../fe-libs/src', microfrontends = [], microfePath = '../../microfe', additionalAliases = {}, additionalPlugins = [], manualChunks = {}, optimizeDepsInclude = [], } = config; const resolvedFeLibsPath = resolve(__dirname, feLibsPath); const resolvedMicrofePath = resolve(__dirname, microfePath); const srcPath = resolve(__dirname, './src'); const nodeModulesPath = resolve(__dirname, 'node_modules'); // Build microfrontend path map for contextual alias resolution const microfePaths: Record = {}; microfrontends.forEach((mfe) => { microfePaths[`microfe-${mfe}`] = resolve(resolvedMicrofePath, `microfe-${mfe}/src`); }); // Build microfrontend aliases const microfeAliases: Record = {}; microfrontends.forEach((mfe) => { microfeAliases[`@burdenoff/microfe-${mfe}`] = resolve( resolvedMicrofePath, `microfe-${mfe}/src` ); }); const plugins: Plugin[] = [ createContextualAliasPlugin({ feLibsPath: resolvedFeLibsPath, srcPath, microfePaths, }), react(), tailwindcss(), ...additionalPlugins, ]; // Default manual chunks for optimal caching const defaultManualChunks: Record = { 'vendor-react': ['react', 'react-dom', 'react-router'], 'vendor-apollo': ['@apollo/client', 'graphql'], 'vendor-ui': ['lucide-react', 'zustand'], ...manualChunks, }; return defineConfig({ plugins, server: { port, strictPort: true, headers: { // Required for OAuth popup flows and SharedArrayBuffer 'Cross-Origin-Opener-Policy': 'same-origin-allow-popups', 'Cross-Origin-Embedder-Policy': 'unsafe-none', }, watch: { ignored: [ ...STANDARD_WATCH_IGNORE, // Ignore microfrontend dist outputs '**/microfe/*/dist/**', ], }, // Allow serving files from linked packages fs: { allow: [ resolve(__dirname, '..'), resolve(__dirname, '../..'), resolvedFeLibsPath, resolvedMicrofePath, ], }, }, build: { // Target modern browsers target: 'es2022', // Enable source maps for production debugging sourcemap: true, // Chunk size warning (500 KB) chunkSizeWarningLimit: 500, // Rolldown options (Vite 8 uses Rolldown instead of Rollup) rolldownOptions: { output: { // Manual chunk splitting for optimal caching manualChunks: defaultManualChunks, // Consistent chunk naming for better caching chunkFileNames: 'chunks/[name]-[hash].js', entryFileNames: '[name]-[hash].js', assetFileNames: 'assets/[name]-[hash].[ext]', }, }, // Minification (Vite 8 uses Oxc minifier by default) minify: true, }, resolve: { alias: { // Force single React instance to prevent "Invalid hook call" errors ...createReactSingletonAliases(nodeModulesPath), // fe-libs source '@burdenoff/fe-libs': resolvedFeLibsPath, // Microfrontend sources ...microfeAliases, // Additional aliases ...additionalAliases, }, // Prevent duplicate dependencies in bundle dedupe: [ 'react', 'react-dom', 'react-router', '@apollo/client', 'graphql', 'zustand', '@tanstack/react-query', ], // Ensure correct entry point resolution mainFields: ['module', 'main', 'browser'], conditions: ['import', 'module', 'default'], }, // Vite 8 uses Oxc for transforms instead of esbuild oxc: { jsx: { runtime: 'automatic', }, }, // Optimize dependencies for faster dev startup optimizeDeps: { include: [ 'react', 'react-dom', 'react-dom/client', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'react-router', 'react-error-boundary', '@apollo/client', '@apollo/client/react', '@apollo/client/link/context', '@apollo/client/link/error', 'graphql', 'lucide-react', 'zustand', 'sonner', 'clsx', 'tailwind-merge', ...optimizeDepsInclude, ], }, }) as UserConfig; } export default createAppShellConfig;