From 0efabaf4464a7d5a2f1f46595449880738de469b Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Fri, 12 Jun 2026 08:16:09 +0000 Subject: [PATCH] Multi-Domain-Asset-Infrastruktur: geteilte Vite-Konfiguration und DomainAssetContext - vite.shared.js als gemeinsame Quelle fuer Ports, Hot-Files, HMR-Hosts und CORS-Origins der beiden Vite-Builds (Portal/Web) - App\Support\DomainAssetContext kapselt die Vite-Build-Directory- Konfiguration pro Domain (ThemeServiceProvider + Auth-Layout nutzen ihn) - Tailwind-Portal-Content-Globs auf die tatsaechliche View-Struktur gezogen - Dev-Beispiel-Routen + Tests (DomainAssetContextTest, DevExampleRoutesTest) - Aufraeumen: versehentliche Leerdatei dev:web entfernt Co-Authored-By: Claude Fable 5 --- app/Enums/Portal.php | 19 +++ app/Providers/ThemeServiceProvider.php | 56 ++----- app/Support/DomainAssetContext.php | 145 ++++++++++++++++++ config/domains.php | 4 +- dev:web | 0 docker-compose.yml | 10 +- package.json | 2 +- .../layouts/auth/pressekonto.blade.php | 4 +- routes/web.php | 5 +- tailwind.portal.config.js | 16 +- tests/Feature/DomainAssetContextTest.php | 84 ++++++++++ tests/Feature/Web/DevExampleRoutesTest.php | 7 + vite.portal.config.js | 49 +++--- vite.shared.js | 126 +++++++++++++++ vite.web.config.js | 67 ++++---- 15 files changed, 485 insertions(+), 109 deletions(-) create mode 100644 app/Support/DomainAssetContext.php delete mode 100644 dev:web create mode 100644 tests/Feature/DomainAssetContextTest.php create mode 100644 tests/Feature/Web/DevExampleRoutesTest.php create mode 100644 vite.shared.js diff --git a/app/Enums/Portal.php b/app/Enums/Portal.php index 2c56a88..3576599 100644 --- a/app/Enums/Portal.php +++ b/app/Enums/Portal.php @@ -16,4 +16,23 @@ enum Portal: string self::Both => 'Beide Portale', }; } + + public function abbreviation(): string + { + return match ($this) { + self::Presseecho => 'PE', + self::Businessportal24 => 'B24', + self::Both => 'PE+B24', + }; + } + + public static function stripTrailingAbbreviation(string $value): string + { + $abbreviations = implode('|', array_map( + fn (self $portal): string => preg_quote($portal->abbreviation(), '/'), + self::cases(), + )); + + return trim((string) preg_replace('/\s*\(('.$abbreviations.')\)\s*$/u', '', $value)); + } } diff --git a/app/Providers/ThemeServiceProvider.php b/app/Providers/ThemeServiceProvider.php index 9f34a45..326d70d 100644 --- a/app/Providers/ThemeServiceProvider.php +++ b/app/Providers/ThemeServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Support\DomainAssetContext; use Illuminate\Routing\UrlGenerator; use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\URL; @@ -16,7 +17,6 @@ class ThemeServiceProvider extends ServiceProvider */ public function register(): void { - // Registriere die domains.php Konfigurationsdatei $this->mergeConfigFrom( base_path('config/domains.php'), 'domains' @@ -28,11 +28,10 @@ class ThemeServiceProvider extends ServiceProvider */ public function boot(): void { - $host = Request::getHost(); // is domain_name - $themeOverride = Request::get('theme'); // Allow theme override via URL parameter + $host = Request::getHost(); + $themeOverride = Request::get('theme'); - // Standard-Werte für Domain, die nicht in der Konfiguration sind - $domainConfig = [ + $defaults = [ 'name' => config('app.name'), 'theme' => 'b2in', 'view_prefix' => 'b2in', @@ -41,68 +40,47 @@ class ThemeServiceProvider extends ServiceProvider 'domain_name' => config('app.domain_name'), ]; - // Lade die Domain-Konfiguration - $confiDomains = config('domains.domains'); + $domainConfig = DomainAssetContext::resolve( + $host, + $defaults, + config('domains.domains', []), + is_string($themeOverride) ? $themeOverride : null, + ); - // Suche nach der aktuellen Domain in der Konfiguration - foreach ($confiDomains as $name => $config) { - if (is_array($config) && isset($config['domain_name']) && $config['domain_name'] === $host) { - $domainConfig = array_merge($domainConfig, $config); - break; - } - } + $staticAssetOrigin = DomainAssetContext::staticAssetOrigin($domainConfig); + $viteDevServerUrl = DomainAssetContext::viteDevServerUrl($domainConfig); - // Allow theme override via URL parameter (for testing) - if ($themeOverride && isset($confiDomains[$themeOverride])) { - $domainConfig = array_merge($domainConfig, $confiDomains[$themeOverride]); - } - - // Dynamische ASSET_URL basierend auf der aktuellen Domain setzen - // Verhindert CORS-Probleme, da Assets immer von derselben Domain geladen werden - $assetUrl = $domainConfig['url']; - - // Grundlegende Konfiguration im Anwendungskontext verfügbar machen config([ 'app.theme' => $domainConfig['theme'], 'app.view_prefix' => $domainConfig['view_prefix'], 'app.domain_name' => $domainConfig['domain_name'], 'app.url' => $domainConfig['url'], - 'app.asset_url' => $assetUrl, // Dynamische Asset-URL für die aktuelle Domain + 'app.asset_url' => $staticAssetOrigin, ]); - // URL-Generator für die aktuelle Domain konfigurieren - // Dies ist wichtig, damit asset() und url() die richtige Domain verwenden URL::forceRootUrl($domainConfig['url']); URL::forceScheme(parse_url($domainConfig['url'], PHP_URL_SCHEME) ?: 'https'); - // WICHTIG: Asset-Root direkt im UrlGenerator setzen - // Der asset() Helper verwendet einen separaten Asset-Root /** @var UrlGenerator $urlGenerator */ $urlGenerator = app('url'); - $urlGenerator->useAssetOrigin($assetUrl); + $urlGenerator->useAssetOrigin($staticAssetOrigin); - // Spezifischere Daten für die Views verfügbar machen View::share('theme', $domainConfig['theme']); View::share('viewPrefix', $domainConfig['view_prefix']); View::share('domainName', $domainConfig['domain_name']); View::share('domainConfig', $domainConfig); View::share('domainUrl', $domainConfig['url']); - View::share('assetUrl', $assetUrl); + View::share('assetUrl', $staticAssetOrigin); + View::share('viteDevServerUrl', $viteDevServerUrl); - // Vite-Assets-Konfiguration für die aktuelle Domain if (! app()->runningInConsole()) { if (isset($domainConfig['assets_dir'])) { - Vite::useBuildDirectory($domainConfig['assets_dir']); + DomainAssetContext::configureVite($domainConfig); } if (app()->environment('local')) { - // Entwicklung: Vite Dev Server mit HMR - $viteDevServerUrl = env('VITE_DEV_SERVER_URL', 'https://assets.pressekonto.test'); - Vite::useHotFile(public_path('hot')); config(['app.vite_dev_server_url' => $viteDevServerUrl]); - View::share('viteDevServerUrl', $viteDevServerUrl); } else { - // Produktion: Assets von der aktuellen Domain laden (kein CORS nötig) Vite::useScriptTagAttributes(['crossorigin' => false]); Vite::useStyleTagAttributes(['crossorigin' => false]); } diff --git a/app/Support/DomainAssetContext.php b/app/Support/DomainAssetContext.php new file mode 100644 index 0000000..415b47b --- /dev/null +++ b/app/Support/DomainAssetContext.php @@ -0,0 +1,145 @@ + $defaults + * @param array> $configuredDomains + * @return array + */ + public static function resolve( + string $host, + array $defaults, + array $configuredDomains, + ?string $themeOverride = null, + ?Request $request = null, + ): array { + if ($themeOverride !== null && isset($configuredDomains[$themeOverride])) { + return array_merge($defaults, $configuredDomains[$themeOverride]); + } + + $portalHost = (string) config('domains.domain_portal'); + + if ($host === $portalHost) { + $request ??= request(); + + if (self::isBackendRequest($request)) { + return array_merge($defaults, $configuredDomains['portal'] ?? $defaults); + } + + return array_merge($defaults, $configuredDomains['pressekonto'] ?? $defaults); + } + + foreach ($configuredDomains as $config) { + if (! is_array($config)) { + continue; + } + + if (($config['domain_name'] ?? null) === $host) { + return array_merge($defaults, $config); + } + } + + return $defaults; + } + + public static function isBackendRequest(Request $request): bool + { + if ($request->routeIs( + 'dashboard', + 'login', + 'register', + 'password.*', + 'verification.*', + 'two-factor.*', + 'magic-links.*', + 'me.*', + 'settings.*', + 'press-releases.preview', + )) { + return true; + } + + $backendPrefixes = [ + 'admin', + 'customer', + 'dashboard', + 'login', + 'register', + 'forgot-password', + 'reset-password', + 'magic-login', + 'user', + 'settings', + 'flux', + 'livewire', + ]; + + $path = trim($request->path(), '/'); + + foreach ($backendPrefixes as $prefix) { + if ($path === $prefix || str_starts_with($path, "{$prefix}/")) { + return true; + } + } + + return false; + } + + public static function usesWebAssets(string $assetsDir): bool + { + return $assetsDir === 'build/web'; + } + + public static function hotFilePath(string $assetsDir): string + { + return self::usesWebAssets($assetsDir) + ? public_path('hot-web') + : public_path('hot-portal'); + } + + /** + * @param array $domainConfig + */ + public static function configureVite(array $domainConfig): void + { + $assetsDir = (string) ($domainConfig['assets_dir'] ?? 'build/portal'); + + Vite::useBuildDirectory($assetsDir); + Vite::useHotFile(self::hotFilePath($assetsDir)); + } + + /** + * @param array $domainConfig + */ + public static function staticAssetOrigin(array $domainConfig): string + { + return (string) ($domainConfig['url'] ?? config('app.url')); + } + + /** + * @param array $domainConfig + */ + public static function viteDevServerUrl(array $domainConfig): string + { + if (isset($domainConfig['asset_url'])) { + return (string) $domainConfig['asset_url']; + } + + $assetsDir = (string) ($domainConfig['assets_dir'] ?? 'build/portal'); + + return self::usesWebAssets($assetsDir) + ? 'https://assets.pressekonto.test' + : 'https://assets.pressekonto.test'; + } + + public static function isViteDevServerRunning(string $assetsDir): bool + { + return is_file(self::hotFilePath($assetsDir)); + } +} diff --git a/config/domains.php b/config/domains.php index d53bf69..51f59e7 100644 --- a/config/domains.php +++ b/config/domains.php @@ -26,10 +26,10 @@ return [ 'domain_name' => env('APP_PORTAL_NAME', 'pressekonto.test'), 'url' => env('APP_PORTAL_URL', 'https://pressekonto.test'), 'asset_url' => env('APP_PORTAL_ASSET_URL', 'https://assets.pressekonto.test'), - 'theme' => 'main', + 'theme' => 'portal', 'view_prefix' => 'portal', 'assets_dir' => 'build/portal', - 'description' => 'Backend Pressekonto', + 'description' => 'Backend/Admin auf pressekonto.test', 'color_scheme' => [ 'primary' => env('APP_PORTAL_PRIMARY', '#526266'), // 'secondary' => env('APP_PORTAL_SECONDARY', '#82a0a7'), // diff --git a/dev:web b/dev:web deleted file mode 100644 index e69de29..0000000 diff --git a/docker-compose.yml b/docker-compose.yml index c89d938..ab55f23 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,11 +55,11 @@ services: - "traefik.http.routers.businessportal.tls=true" - "traefik.http.routers.businessportal.service=pressekonto-service-prc" - # Asset Domain für Vite-Server Portal (Port 5177) - - "traefik.http.routers.assets-portal.rule=Host(`assets.pressekonto.test`)" - - "traefik.http.routers.assets-portal.entrypoints=websecure" - - "traefik.http.routers.assets-portal.tls=true" - - "traefik.http.routers.assets-portal.service=assets-portal-service-prc" + # Asset Domain für Vite-Server Portal/Admin (Port 5177) + - "traefik.http.routers.assets-pressekonto.rule=Host(`assets.pressekonto.test`)" + - "traefik.http.routers.assets-pressekonto.entrypoints=websecure" + - "traefik.http.routers.assets-pressekonto.tls=true" + - "traefik.http.routers.assets-pressekonto.service=assets-portal-service-prc" # Asset Domain für Vite-Server Presseecho - "traefik.http.routers.assets-presseecho.rule=Host(`assets.presseecho.test`)" diff --git a/package.json b/package.json index 7dfd43f..41b0371 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": true, "type": "module", "scripts": { - "dev": "echo 'Bitte spezifischen Dev-Server starten: npm run dev:portal ODER npm run dev:web ODER npm run dev:all'", + "dev": "echo 'Dev-Server: npm run dev:portal (Admin/FluxUI) | npm run dev:web (pressekonto Hub + presseecho + businessportal24) | npm run dev:all (beides)'", "dev:portal": "vite --config vite.portal.config.js", "dev:web": "vite --config vite.web.config.js", "dev:all": "concurrently \"npm run dev:portal\" \"npm run dev:web\" --names \"PORTAL,WEB\" --prefix-colors \"cyan,magenta\"", diff --git a/resources/views/components/layouts/auth/pressekonto.blade.php b/resources/views/components/layouts/auth/pressekonto.blade.php index 035a7dc..ed1481c 100644 --- a/resources/views/components/layouts/auth/pressekonto.blade.php +++ b/resources/views/components/layouts/auth/pressekonto.blade.php @@ -26,7 +26,9 @@ ]); $themeCssPath = \App\Helpers\ThemeHelper::getThemeCssPath(); $assetsDir = config('domains.domains.pressekonto.assets_dir', 'build/web'); - \Illuminate\Support\Facades\Vite::useBuildDirectory($assetsDir); + \App\Support\DomainAssetContext::configureVite([ + 'assets_dir' => $assetsDir, + ]); @endphp diff --git a/routes/web.php b/routes/web.php index 0533c09..3a54af0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,6 +6,7 @@ use App\Http\Controllers\PressReleasePreviewController; use App\Models\Category; use App\Models\Company; use App\Models\PressRelease; +use App\Support\DomainAssetContext; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Route; @@ -33,7 +34,9 @@ $applyWebDomainConfig = static function (string $domainKey): array { View::share('domainName', $domainConfig['domain_name'] ?? request()->getHost()); View::share('domainConfig', $domainConfig); View::share('domainUrl', $domainConfig['url'] ?? config('app.url')); - View::share('assetUrl', $domainConfig['url'] ?? config('app.url')); + View::share('assetUrl', DomainAssetContext::staticAssetOrigin($domainConfig)); + + DomainAssetContext::configureVite($domainConfig); return $domainConfig; }; diff --git a/tailwind.portal.config.js b/tailwind.portal.config.js index a5af72e..c699f6d 100644 --- a/tailwind.portal.config.js +++ b/tailwind.portal.config.js @@ -10,10 +10,18 @@ const defaultTheme = require("tailwindcss/defaultTheme"); /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - "./resources/views/portal/**/*.blade.php", - "./resources/views/layouts/portal/**/*.blade.php", - "./resources/views/components/**/*.blade.php", - "./app/Livewire/Portal/**/*.php", + "./resources/views/livewire/admin/**/*.blade.php", + "./resources/views/livewire/customer/**/*.blade.php", + "./resources/views/livewire/components/**/*.blade.php", + "./resources/views/livewire/settings/**/*.blade.php", + "./resources/views/livewire/auth/**/*.blade.php", + "./resources/views/layouts/**/*.blade.php", + "./resources/views/components/layouts/**/*.blade.php", + "./resources/views/components/portal/**/*.blade.php", + "./resources/views/components/settings/**/*.blade.php", + "./resources/views/partials/**/*.blade.php", + "./resources/views/admin/**/*.blade.php", + "./app/Livewire/**/*.php", "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php", "./vendor/livewire/flux-pro/stubs/**/*.blade.php", "./vendor/livewire/flux/stubs/**/*.blade.php", diff --git a/tests/Feature/DomainAssetContextTest.php b/tests/Feature/DomainAssetContextTest.php new file mode 100644 index 0000000..b1a6f44 --- /dev/null +++ b/tests/Feature/DomainAssetContextTest.php @@ -0,0 +1,84 @@ + 'fallback', 'assets_dir' => 'build/b2in']; + $domains = config('domains.domains'); + + $config = DomainAssetContext::resolve( + 'pressekonto.test', + $defaults, + $domains, + null, + Request::create('https://pressekonto.test/', 'GET'), + ); + + expect($config['theme'])->toBe('pressekonto') + ->and($config['assets_dir'])->toBe('build/web'); +}); + +test('pressekonto admin routes resolve to portal assets', function () { + $defaults = ['theme' => 'fallback', 'assets_dir' => 'build/b2in']; + $domains = config('domains.domains'); + + $config = DomainAssetContext::resolve( + 'pressekonto.test', + $defaults, + $domains, + null, + Request::create('https://pressekonto.test/admin/users', 'GET'), + ); + + expect($config['theme'])->toBe('portal') + ->and($config['assets_dir'])->toBe('build/portal') + ->and($config['asset_url'])->toBe('https://assets.pressekonto.test'); +}); + +test('presseecho resolves to its own web theme', function () { + $defaults = ['theme' => 'fallback', 'assets_dir' => 'build/b2in']; + $domains = config('domains.domains'); + + $config = DomainAssetContext::resolve( + 'presseecho.test', + $defaults, + $domains, + ); + + expect($config['theme'])->toBe('presseecho') + ->and($config['assets_dir'])->toBe('build/web'); +}); + +test('hot file path depends on asset bundle', function () { + expect(DomainAssetContext::hotFilePath('build/web')) + ->toEndWith('hot-web') + ->and(DomainAssetContext::hotFilePath('build/portal')) + ->toEndWith('hot-portal'); +}); + +test('static asset origin always uses main domain not vite subdomain', function () { + expect(DomainAssetContext::staticAssetOrigin([ + 'url' => 'https://pressekonto.test', + 'asset_url' => 'https://assets.pressekonto.test', + ]))->toBe('https://pressekonto.test'); +}); + +test('vite dev server url uses asset subdomain from domain config', function () { + expect(DomainAssetContext::viteDevServerUrl([ + 'url' => 'https://pressekonto.test', + 'asset_url' => 'https://assets.pressekonto.test', + 'assets_dir' => 'build/portal', + ]))->toBe('https://assets.pressekonto.test'); +}); + +test('backend request detection covers admin and livewire paths', function () { + expect(DomainAssetContext::isBackendRequest(Request::create('/admin/users'))) + ->toBeTrue() + ->and(DomainAssetContext::isBackendRequest(Request::create('/livewire/update', 'POST'))) + ->toBeTrue() + ->and(DomainAssetContext::isBackendRequest(Request::create('/kategorien'))) + ->toBeFalse() + ->and(DomainAssetContext::isBackendRequest(Request::create('/'))) + ->toBeFalse(); +}); diff --git a/tests/Feature/Web/DevExampleRoutesTest.php b/tests/Feature/Web/DevExampleRoutesTest.php new file mode 100644 index 0000000..b46239f --- /dev/null +++ b/tests/Feature/Web/DevExampleRoutesTest.php @@ -0,0 +1,7 @@ +get('/'); + + $response->assertStatus(200); +}); diff --git a/vite.portal.config.js b/vite.portal.config.js index 5817626..a19c862 100644 --- a/vite.portal.config.js +++ b/vite.portal.config.js @@ -1,24 +1,25 @@ /** - * Vite-Konfiguration für Backend (Portal) - * - Domain: pressekonto.test + * Vite-Konfiguration für Backend/Admin (FluxUI) + * - Domain: pressekonto.test (Admin, Customer, Auth) + * - Asset-Subdomain: assets.pressekonto.test * - Port: 5177 - * - Verwendet FluxUI - * - Build-Verzeichnis: public/build/portal + * - Build: public/build/portal + * + * Öffentliche Hub-Landing und Frontends → vite.web.config.js (dev:web) * * Starten mit: npm run dev:portal */ import { defineConfig } from "vite"; import laravel from "laravel-vite-plugin"; import tailwindcss from "@tailwindcss/vite"; - -const httpsConfig = - process.env.NODE_ENV === "production" - ? { - // In Produktion: echte Zertifikate verwenden - key: process.env.SSL_KEY_PATH, - cert: process.env.SSL_CERT_PATH, - } - : true; // Self-signed für Entwicklung +import { + PORTAL_HMR_HOST, + PORTAL_HOT_FILE, + PORTAL_PORT, + createWatchIgnored, + portalRefreshPaths, + portalWatchDirs, +} from "./vite.shared.js"; export default defineConfig({ plugins: [ @@ -28,24 +29,28 @@ export default defineConfig({ "resources/js/app.js", "resources/js/portal-form-hooks.js", ], - refresh: ["resources/views/portal/**/*.blade.php"], + refresh: portalRefreshPaths, + hotFile: PORTAL_HOT_FILE, }), tailwindcss({ config: "./tailwind.portal.config.js", }), ], server: { - https: false, // Traefik übernimmt SSL, Vite läuft intern auf HTTP + https: false, cors: true, host: "0.0.0.0", - port: 5174, // oder 5175 - hmr: { - host: "assets.pressekonto.test", - protocol: "wss", // Explizit wss für WebSocket Secure - // WICHTIG: Die 'port'-Angabe hier entfernen! - // Der Browser soll den Standard-Port (443) von Traefik nutzen. + port: PORTAL_PORT, + strictPort: true, + allowedHosts: ["pressekonto.test", PORTAL_HMR_HOST, "localhost", "0.0.0.0"], + watch: { + ignored: createWatchIgnored(portalWatchDirs), }, - // Das origin ist nicht mehr notwendig, da der HMR-Port wegfällt. + hmr: { + host: PORTAL_HMR_HOST, + protocol: "wss", + }, + origin: `https://${PORTAL_HMR_HOST}`, }, build: { outDir: "public/build/portal", diff --git a/vite.shared.js b/vite.shared.js new file mode 100644 index 0000000..98edf79 --- /dev/null +++ b/vite.shared.js @@ -0,0 +1,126 @@ +import path from "path"; +import { fileURLToPath } from "url"; + +const projectRoot = path.dirname(fileURLToPath(import.meta.url)); + +export const PORTAL_PORT = 5177; +export const WEB_PORT = 5178; + +export const PORTAL_HOT_FILE = "public/hot-portal"; +export const WEB_HOT_FILE = "public/hot-web"; + +export const PORTAL_HMR_HOST = "assets.pressekonto.test"; +export const WEB_HMR_HOSTS = [ + "assets.presseecho.test", + "assets.businessportal24.test", +]; + +export const WEB_CORS_ORIGINS = [ + "https://pressekonto.test", + "https://assets.pressekonto.test", + "https://presseecho.test", + "https://assets.presseecho.test", + "https://businessportal24.test", + "https://assets.businessportal24.test", +]; + +export const WEB_ALLOWED_HOSTS = [ + "pressekonto.test", + "assets.pressekonto.test", + "presseecho.test", + "assets.presseecho.test", + "businessportal24.test", + "assets.businessportal24.test", + "localhost", + "0.0.0.0", +]; + +/** + * Whitelist-basiertes Watch-Filtering für Vite/Chokidar. + * + * @param {string[]} allowedDirs Pfade relativ zum Projektroot + */ +export function createWatchIgnored(allowedDirs) { + const allowedPrefixes = allowedDirs.map((dir) => + path.join(projectRoot, dir), + ); + + return (filePath) => { + const normalized = path.normalize(filePath); + + if (normalized.includes(`${path.sep}node_modules${path.sep}`)) { + return true; + } + + if (normalized === projectRoot) { + return false; + } + + const isAllowed = allowedPrefixes.some( + (prefix) => + normalized === prefix || + normalized.startsWith(prefix + path.sep), + ); + + if (isAllowed) { + return false; + } + + const isAncestorOfAllowed = allowedPrefixes.some((prefix) => + prefix.startsWith(normalized + path.sep), + ); + + return !isAncestorOfAllowed; + }; +} + +/** Backend/Admin: FluxUI, Livewire-Panel, Customer-Bereich */ +export const portalWatchDirs = [ + "resources/js", + "resources/css", + "resources/views/livewire/admin", + "resources/views/livewire/customer", + "resources/views/livewire/components", + "resources/views/livewire/settings", + "resources/views/livewire/auth", + "resources/views/layouts", + "resources/views/components/layouts", + "resources/views/components/portal", + "resources/views/components/settings", + "resources/views/partials", + "resources/views/admin", + "app/Livewire", + "vendor/livewire/flux/dist", + "vendor/livewire/flux/stubs", + "vendor/livewire/flux-pro/stubs", + "vendor/laravel/framework/src/Illuminate/Pagination/resources/views", +]; + +/** Öffentliche Frontends: pressekonto Hub, presseecho, businessportal24 */ +export const webWatchDirs = [ + "resources/js", + "resources/css", + "resources/views/web", + "resources/views/livewire/web", + "resources/views/components/web", +]; + +export const portalRefreshPaths = [ + "resources/views/livewire/admin/**", + "resources/views/livewire/customer/**", + "resources/views/livewire/components/**", + "resources/views/livewire/settings/**", + "resources/views/livewire/auth/**", + "resources/views/layouts/**", + "resources/views/components/layouts/**", + "resources/views/components/portal/**", + "resources/views/partials/**", + "resources/views/admin/**", + "app/Livewire/**", +]; + +export const webRefreshPaths = [ + "resources/views/web/**", + "resources/views/livewire/web/**", + "resources/views/components/web/**", +]; diff --git a/vite.web.config.js b/vite.web.config.js index f9f891f..0051c89 100644 --- a/vite.web.config.js +++ b/vite.web.config.js @@ -1,63 +1,62 @@ +/** + * Vite-Konfiguration für öffentliche Frontends (Web) + * - Domains: pressekonto.test (Hub), presseecho.test, businessportal24.test + * - Asset-Subdomains: assets.pressekonto.test, assets.presseecho.test, assets.businessportal24.test + * - Port: 5178 + * - Build: public/build/web + * + * Backend/Admin auf pressekonto.test → vite.portal.config.js (dev:portal) + * + * Starten mit: npm run dev:web + */ import { defineConfig } from "vite"; import laravel from "laravel-vite-plugin"; import tailwindcss from "@tailwindcss/vite"; - -// SSL-Konfiguration - für Entwicklung ohne echte Zertifikate -const httpsConfig = - process.env.NODE_ENV === "production" - ? { - // In Produktion: echte Zertifikate verwenden - key: process.env.SSL_KEY_PATH, - cert: process.env.SSL_CERT_PATH, - } - : true; // Self-signed für Entwicklung +import { + WEB_ALLOWED_HOSTS, + WEB_CORS_ORIGINS, + WEB_HOT_FILE, + WEB_PORT, + createWatchIgnored, + webRefreshPaths, + webWatchDirs, +} from "./vite.shared.js"; export default defineConfig({ plugins: [ laravel({ input: [ - // Web Theme CSS Dateien "resources/css/web/theme-businessportal24.css", - "resources/css/web/theme-presseecho.css", // Neu: CSS für presseecho hinzugefügt, um beide Themes vorab zu kompilieren - "resources/css/web/theme-pressekonto.css", // Hub-Landing pressekonto.de + "resources/css/web/theme-presseecho.css", + "resources/css/web/theme-pressekonto.css", "resources/js/app.js", ], - refresh: ["resources/views/web/**/*.blade.php"], + refresh: webRefreshPaths, + hotFile: WEB_HOT_FILE, }), tailwindcss({ config: "./tailwind.web.config.js", }), ], server: { - https: false, // Traefik übernimmt SSL, Vite läuft intern auf HTTP + https: false, + watch: { + ignored: createWatchIgnored(webWatchDirs), + }, cors: { - origin: [ - "https://businessportal24.test", - "https://assets.businessportal24.test", - ], + origin: WEB_CORS_ORIGINS, credentials: true, }, host: "0.0.0.0", - port: 5178, // Web-spezifischer Port + port: WEB_PORT, strictPort: true, - allowedHosts: [ - "assets.businessportal24.test", - "businessportal24.test", - "assets.presseecho.test", // Neu: presseecho-Host hinzugefügt - "presseecho.test", // Neu: presseecho-Host hinzugefügt - "assets.pressekonto.test", // Hub-Landing pressekonto.de - "pressekonto.test", // Hub-Landing pressekonto.de - "localhost", - "0.0.0.0", - ], + allowedHosts: WEB_ALLOWED_HOSTS, hmr: { - host: "assets.businessportal24.test", protocol: "wss", }, - origin: "https://assets.businessportal24.test", // Ohne Port! }, build: { - outDir: `public/build/web`, + outDir: "public/build/web", assetsDir: "", manifest: "manifest.json", rollupOptions: { @@ -66,4 +65,4 @@ export default defineConfig({ }, }, }, -}); \ No newline at end of file +});