Add ViteHelper fallback for missing frontend assets

This commit is contained in:
2026-03-15 07:47:59 +02:00
parent 94f4e53860
commit dff2cd752c
6 changed files with 131 additions and 3 deletions

View File

@@ -208,6 +208,20 @@ If you prefer manual control:
| `make setup-laravel` | Configure auth, API, middleware |
| `make setup-all` | Run both setup scripts |
### Frontend Assets (Vite)
Build frontend CSS/JS assets:
```bash
# Build assets for production
docker-compose run --rm node npm run build
# Or run Vite dev server (hot reload)
docker-compose --profile frontend up -d
```
> **Note:** The template includes a resilient fallback - if assets aren't built, basic styling is provided and a development warning is shown. This prevents the `ViteManifestNotFoundException` error from breaking the app.
## Laravel Setup (Auth, API, Middleware)
After installing Laravel, run the interactive setup:

View File

@@ -38,6 +38,11 @@ RUN pecl install redis && docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Install Node.js for asset building
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
# Create system user to run Composer and Artisan commands
RUN useradd -G www-data,root -u 1000 -d /home/devuser devuser
RUN mkdir -p /home/devuser/.composer && \
@@ -61,6 +66,13 @@ RUN chown -R devuser:www-data /var/www/html \
&& chmod -R 775 /var/www/html/storage 2>/dev/null || true \
&& chmod -R 775 /var/www/html/bootstrap/cache 2>/dev/null || true
# Build frontend assets (if package.json exists)
RUN if [ -f "package.json" ]; then \
npm ci --ignore-scripts && \
npm run build && \
rm -rf node_modules; \
fi
# Run post-install scripts
USER devuser
RUN composer run-script post-autoload-dump 2>/dev/null || true

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Helpers;
class ViteHelper
{
/**
* Check if the Vite manifest exists (assets have been built).
*/
public static function manifestExists(): bool
{
return file_exists(public_path('build/manifest.json'));
}
/**
* Get fallback CSS for when Vite assets are not built.
* This provides basic styling so the app remains usable.
*/
public static function fallbackStyles(): string
{
return <<<'CSS'
<style>
/* Fallback styles when Vite assets are not built */
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: ui-sans-serif, system-ui, sans-serif;
margin: 0;
background: #f3f4f6;
color: #111827;
}
.dark body { background: #111827; color: #f9fafb; }
.min-h-screen { min-height: 100vh; }
.bg-gray-100 { background: #f3f4f6; }
.bg-white { background: #fff; }
.shadow { box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.max-w-7xl { max-width: 80rem; margin: 0 auto; }
.px-4 { padding-left: 1rem; padding-right: 1rem; }
.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
.font-semibold { font-weight: 600; }
.text-xl { font-size: 1.25rem; }
.text-gray-800 { color: #1f2937; }
a { color: #3b82f6; text-decoration: none; }
a:hover { text-decoration: underline; }
.hidden { display: none; }
.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.space-x-4 > * + * { margin-left: 1rem; }
nav { background: #fff; border-bottom: 1px solid #e5e7eb; padding: 1rem; }
.container { max-width: 80rem; margin: 0 auto; padding: 0 1rem; }
button, .btn {
padding: 0.5rem 1rem;
background: #3b82f6;
color: white;
border: none;
border-radius: 0.375rem;
cursor: pointer;
}
button:hover, .btn:hover { background: #2563eb; }
input, select, textarea {
border: 1px solid #d1d5db;
border-radius: 0.375rem;
padding: 0.5rem 0.75rem;
width: 100%;
}
.alert { padding: 1rem; border-radius: 0.375rem; margin-bottom: 1rem; }
.alert-warning { background: #fef3c7; border: 1px solid #f59e0b; color: #92400e; }
</style>
CSS;
}
/**
* Get a warning banner HTML for development.
*/
public static function devWarningBanner(): string
{
if (app()->environment('production')) {
return '';
}
return <<<'HTML'
<div class="alert alert-warning" style="margin:1rem;padding:1rem;background:#fef3c7;border:1px solid #f59e0b;border-radius:0.5rem;color:#92400e;">
<strong>⚠️ Development Notice:</strong> Vite assets are not built.
Run <code style="background:#fde68a;padding:0.125rem 0.25rem;border-radius:0.25rem;">docker-compose run --rm node npm run build</code> to build assets.
</div>
HTML;
}
}

2
src/package-lock.json generated
View File

@@ -1,5 +1,5 @@
{
"name": "app",
"name": "html",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@@ -21,9 +21,16 @@
</style>
<!-- Scripts -->
@if(\App\Helpers\ViteHelper::manifestExists())
@vite(['resources/css/app.css', 'resources/js/app.js'])
@else
{!! \App\Helpers\ViteHelper::fallbackStyles() !!}
@endif
</head>
<body class="font-sans antialiased">
@if(!\App\Helpers\ViteHelper::manifestExists())
{!! \App\Helpers\ViteHelper::devWarningBanner() !!}
@endif
<div class="min-h-screen bg-gray-100 dark:bg-gray-900">
@include('layouts.navigation')

View File

@@ -21,9 +21,16 @@
</style>
<!-- Scripts -->
@if(\App\Helpers\ViteHelper::manifestExists())
@vite(['resources/css/app.css', 'resources/js/app.js'])
@else
{!! \App\Helpers\ViteHelper::fallbackStyles() !!}
@endif
</head>
<body class="font-sans text-gray-900 antialiased">
@if(!\App\Helpers\ViteHelper::manifestExists())
{!! \App\Helpers\ViteHelper::devWarningBanner() !!}
@endif
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-gray-900">
<div>
<a href="/">