client-web umbau
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
# Meldestelle Web-App
|
||||
|
||||
Eine moderne Web-Anwendung basierend auf **Compose for Web** (Compose Multiplatform) für das Meldestelle-Projekt.
|
||||
|
||||
## 📋 Überblick
|
||||
|
||||
Diese Web-Anwendung implementiert das Frontend für das Meldestelle-System unter Verwendung von Compose for Web. Sie folgt dem Architekturprinzip der maximalen Code-Wiederverwendung durch die Nutzung des `commonMain`-Source-Sets von Kotlin Multiplatform.
|
||||
|
||||
### Technologie-Stack
|
||||
|
||||
- **Frontend Framework**: Compose for Web (Compose Multiplatform 1.8.2)
|
||||
- **Programmiersprache**: Kotlin/JS
|
||||
- **Build-System**: Gradle 8.10
|
||||
- **HTTP-Client**: Ktor Client
|
||||
- **UI-Komponenten**: Compose Material 3 (aus commonMain)
|
||||
- **Bundler**: Webpack (über Kotlin/JS Plugin)
|
||||
- **Container**: Nginx (Production)
|
||||
|
||||
## 🏗️ Architektur
|
||||
|
||||
```
|
||||
client/web-app/
|
||||
├── src/
|
||||
│ └── jsMain/
|
||||
│ ├── kotlin/at/mocode/client/web/
|
||||
│ │ └── main.kt # Entry Point
|
||||
│ └── resources/
|
||||
│ └── index.html # HTML Template
|
||||
├── build.gradle.kts # Build Konfiguration
|
||||
└── build/ # Build Artefakte
|
||||
└── dist/js/productionExecutable/ # Produktionsversion
|
||||
```
|
||||
|
||||
### Design Prinzipien
|
||||
|
||||
1. **Code-Wiederverwendung**: Maximale Nutzung des `client:common-ui` Moduls
|
||||
2. **Compose for Web**: Deklarative UI mit `@Composable` Funktionen
|
||||
3. **State Management**: Zustandsverwaltung über ViewModels im `commonMain`
|
||||
4. **Plattform-Trennung**: UI-Code in `jsMain`, Logik in `commonMain`
|
||||
|
||||
## 🚀 Schnellstart
|
||||
|
||||
### Voraussetzungen
|
||||
|
||||
- Java 21+
|
||||
- Docker und Docker Compose
|
||||
- Node.js 18+ (wird automatisch im Container installiert)
|
||||
|
||||
### Entwicklung starten
|
||||
|
||||
#### Mit Docker (Empfohlen)
|
||||
|
||||
```bash
|
||||
# Web-App im Entwicklungsmodus starten
|
||||
docker compose -f docker-compose.yml -f docker-compose.clients.yml up -d web-app
|
||||
|
||||
# Anwendung ist verfügbar unter:
|
||||
# http://localhost:3000
|
||||
```
|
||||
|
||||
#### Lokale Entwicklung
|
||||
|
||||
```bash
|
||||
# Abhängigkeiten installieren und Entwicklungsserver starten
|
||||
./gradlew :client:web-app:jsBrowserDevelopmentRun
|
||||
|
||||
# Anwendung läuft auf http://localhost:8080
|
||||
```
|
||||
|
||||
### Produktionsbuild
|
||||
|
||||
```bash
|
||||
# Optimierte JavaScript-Bundles erstellen
|
||||
./gradlew :client:web-app:jsBrowserDistribution
|
||||
|
||||
# Artefakte befinden sich in:
|
||||
# client/web-app/build/dist/js/productionExecutable/
|
||||
```
|
||||
|
||||
## 🔧 Entwicklung
|
||||
|
||||
### Projekt-Struktur
|
||||
|
||||
```kotlin
|
||||
// main.kt - Entry Point
|
||||
fun main() {
|
||||
renderComposable(rootElementId = "root") {
|
||||
WebApp()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WebApp() {
|
||||
// Verwendet die gemeinsame App-Komponente aus commonMain
|
||||
App()
|
||||
}
|
||||
```
|
||||
|
||||
### Hot-Reload
|
||||
|
||||
Die Entwicklungsumgebung unterstützt Hot-Reload:
|
||||
- Änderungen an Kotlin-Code werden automatisch neu kompiliert
|
||||
- Browser wird automatisch aktualisiert
|
||||
- Schnelle Entwicklungszyklen durch Webpack Dev Server
|
||||
|
||||
### Build-Konfiguration
|
||||
|
||||
Die `build.gradle.kts` konfiguriert:
|
||||
|
||||
```kotlin
|
||||
kotlin {
|
||||
js(IR) {
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
outputFileName = "web-app.js"
|
||||
// Webpack optimization directory
|
||||
configDirectory = project.projectDir.resolve("webpack.config.d")
|
||||
}
|
||||
webpackTask {
|
||||
mainOutputFileName = "web-app.js"
|
||||
}
|
||||
}
|
||||
binaries.executable()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Webpack-Optimierungen
|
||||
|
||||
Das Projekt verwendet erweiterte Webpack-Optimierungen für bessere Performance:
|
||||
|
||||
#### Code Splitting
|
||||
- **Separate Chunks**: Bundle wird in ~60 kleinere, cacheable Dateien aufgeteilt
|
||||
- **Vendor Chunks**: Große Libraries (Kotlin stdlib, Compose runtime, Coroutines) werden separat geladen
|
||||
- **Lazy Loading**: Verbessertes Caching durch getrennte Vendor- und App-Code-Chunks
|
||||
|
||||
#### Bundle-Größenoptimierung
|
||||
- **Tree Shaking**: Entfernt ungenutzten Code
|
||||
- **Minification**: Aggressive Komprimierung im Produktionsbuild
|
||||
- **Scope Hoisting**: Optimiert JavaScript-Execution
|
||||
- **Performance Budget**: Warnt bei zu großen Bundles (500KB pro Asset, 800KB Gesamt)
|
||||
|
||||
#### Generierte Chunks (Beispiel)
|
||||
```
|
||||
web-app-runtime.js 1.67 KiB (Runtime)
|
||||
web-app.js 482 bytes (Main App)
|
||||
web-app-compose-runtime-*.js 274 KiB (Compose Framework)
|
||||
web-app-kotlin-stdlib.js 165 KiB (Kotlin Standard Library)
|
||||
web-app-coroutines.js 119 KiB (Kotlinx Coroutines)
|
||||
web-app-vendors-*.js 1.17 MiB (Weitere Dependencies)
|
||||
```
|
||||
```
|
||||
|
||||
**Abhängigkeiten:**
|
||||
- `compose.web.core` - Compose for Web Framework
|
||||
- `compose.runtime` - Compose Runtime
|
||||
- `project(":client:common-ui")` - Gemeinsame UI-Komponenten
|
||||
- `kotlinx-coroutines-core-js` - Coroutines für Web
|
||||
|
||||
## 🌐 Deployment
|
||||
|
||||
### Docker Container
|
||||
|
||||
Die Anwendung wird als Docker-Container deployed:
|
||||
|
||||
```dockerfile
|
||||
# Multi-stage Build
|
||||
FROM gradle:8.10-jdk21 AS builder
|
||||
# ... Build Phase
|
||||
|
||||
FROM nginx:1.25-alpine AS production
|
||||
# ... Production Phase
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Multi-stage Build für optimale Image-Größe
|
||||
- Nginx als Static File Server
|
||||
- Health Checks
|
||||
- Security Headers
|
||||
- Gzip Kompression
|
||||
|
||||
### Konfiguration
|
||||
|
||||
Umgebungsvariablen:
|
||||
- `NODE_ENV`: Entwicklungs-/Produktionsmodus
|
||||
- `API_BASE_URL`: Backend API URL
|
||||
- `APP_TITLE`: Anwendungstitel
|
||||
- `APP_VERSION`: Versionsnummer
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Container Health Check
|
||||
curl --fail http://localhost:3000/health
|
||||
|
||||
# Antwort: {"status":"ok","service":"web-app"}
|
||||
```
|
||||
|
||||
## 🔗 Integration
|
||||
|
||||
### Backend-Kommunikation
|
||||
|
||||
Die Web-App kommuniziert mit dem Backend über:
|
||||
- **API Gateway**: `http://api-gateway:8081`
|
||||
- **REST APIs**: Über Ktor Client
|
||||
- **WebSocket**: Für Realtime-Updates (geplant)
|
||||
|
||||
### Gemeinsame Komponenten
|
||||
|
||||
Nutzt Komponenten aus `client:common-ui`:
|
||||
- **ViewModels**: `PingViewModel` für Backend-Tests
|
||||
- **UI-Komponenten**: `App`, `PingScreen`
|
||||
- **Services**: `PingService` für HTTP-Aufrufe
|
||||
- **Models**: Datenklassen und UI-States
|
||||
|
||||
### Beispiel Integration
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun WebApp() {
|
||||
// Verwendet die gemeinsame App-Komponente
|
||||
// Diese enthält Material 3 Komponenten und ViewModels
|
||||
App(baseUrl = "http://localhost:8081")
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Build-Artefakte
|
||||
|
||||
Nach dem Build werden folgende Dateien generiert:
|
||||
|
||||
```
|
||||
build/dist/js/productionExecutable/
|
||||
├── web-app.js # Hauptanwendung (minifiziert)
|
||||
├── web-app.js.map # Source Maps
|
||||
├── 731.js # Code Splitting Chunk
|
||||
├── index.html # HTML Template
|
||||
├── skiko.wasm # Compose Runtime (WebAssembly)
|
||||
└── skiko.js # Compose JavaScript Runtime
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Entwicklungstests
|
||||
|
||||
```bash
|
||||
# Backend-Konnektivität testen
|
||||
# Öffne http://localhost:3000
|
||||
# Klicke "Ping Backend" Button
|
||||
```
|
||||
|
||||
### Build-Validierung
|
||||
|
||||
```bash
|
||||
# Build ohne Ausführung testen
|
||||
./gradlew :client:web-app:jsBrowserDevelopmentRun --dry-run
|
||||
|
||||
# Produktionsbuild testen
|
||||
./gradlew :client:web-app:jsBrowserDistribution
|
||||
```
|
||||
|
||||
## 📚 Weiterführende Dokumentation
|
||||
|
||||
- [Web-App Guideline](../../.junie/guidelines/web-app-guideline.md) - Architektur-Richtlinien
|
||||
- [Docker README](../../README-DOCKER.md) - Container-Dokumentation
|
||||
- [Compose for Web Docs](https://github.com/JetBrains/compose-multiplatform) - Offizielle Dokumentation
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
**Problem**: `Cannot connect to backend`
|
||||
```bash
|
||||
# Lösung: Backend Services starten
|
||||
docker-compose -f docker-compose.yml -f docker-compose.services.yml up -d
|
||||
```
|
||||
|
||||
**Problem**: `Module build failed`
|
||||
```bash
|
||||
# Lösung: Clean Build
|
||||
./gradlew :client:web-app:clean :client:web-app:jsBrowserDevelopmentRun
|
||||
```
|
||||
|
||||
**Problem**: `Port 3000 already in use`
|
||||
```bash
|
||||
# Lösung: Port in docker-compose.clients.yml ändern
|
||||
ports:
|
||||
- "3001:3000" # Externer Port ändern
|
||||
```
|
||||
|
||||
### Logs und Debugging
|
||||
|
||||
```bash
|
||||
# Container Logs anzeigen
|
||||
docker logs meldestelle-web-app
|
||||
|
||||
# Build Logs mit Details
|
||||
./gradlew :client:web-app:jsBrowserDevelopmentRun --info --stacktrace
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Letzte Aktualisierung**: 2025-09-10
|
||||
**Version**: 1.0.0
|
||||
@@ -0,0 +1,57 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("org.jetbrains.compose")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
}
|
||||
|
||||
group = "at.mocode.client.web"
|
||||
version = "1.0.0"
|
||||
|
||||
kotlin {
|
||||
js(IR) {
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
devServer = devServer?.copy(
|
||||
port = 8080,
|
||||
static = mutableListOf("src/jsMain/resources")
|
||||
)
|
||||
|
||||
// Webpack optimization settings
|
||||
configDirectory = project.projectDir.resolve("webpack.config.d")
|
||||
}
|
||||
webpackTask {
|
||||
args.add("--devtool=source-map")
|
||||
}
|
||||
runTask {
|
||||
args.add("--devtool=source-map")
|
||||
}
|
||||
|
||||
// Add npm dependencies for webpack plugins
|
||||
useCommonJs()
|
||||
}
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
// Compose for Web
|
||||
implementation(compose.html.core)
|
||||
implementation(compose.runtime)
|
||||
|
||||
// Common UI module (contains ViewModels and shared components)
|
||||
implementation(project(":client:common-ui"))
|
||||
|
||||
// Coroutines for web
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.7.3")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val jsTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-js"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package at.mocode.client.web
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import at.mocode.client.ui.App
|
||||
import org.jetbrains.compose.web.renderComposable
|
||||
|
||||
/**
|
||||
* Entry point for the Compose for Web application.
|
||||
* Follows the web-app guideline by using the shared App component from commonMain.
|
||||
*/
|
||||
fun main() {
|
||||
renderComposable(rootElementId = "root") {
|
||||
WebApp()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WebApp() {
|
||||
// Use the shared App component from commonMain
|
||||
// This follows the guideline principle of maximum code reuse
|
||||
App()
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Meldestelle</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
// Compression and module resolution optimizations
|
||||
|
||||
// Enhanced module resolution to reduce bundle size
|
||||
config.resolve = config.resolve || {};
|
||||
config.resolve.alias = config.resolve.alias || {};
|
||||
|
||||
// Resolve optimizations
|
||||
config.resolve.modules = ['node_modules'];
|
||||
config.resolve.extensions = ['.js', '.json', '.wasm'];
|
||||
|
||||
// Output optimizations
|
||||
config.output = config.output || {};
|
||||
config.output.pathinfo = false; // Disable path info in production for smaller bundles
|
||||
|
||||
// Module concatenation for better tree shaking
|
||||
config.optimization = config.optimization || {};
|
||||
config.optimization.concatenateModules = true;
|
||||
|
||||
// Enable scope hoisting for better performance
|
||||
config.optimization.moduleIds = 'deterministic';
|
||||
config.optimization.chunkIds = 'deterministic';
|
||||
|
||||
// Webpack production mode optimizations
|
||||
if (config.mode === 'production') {
|
||||
// Disable development features
|
||||
config.devtool = false; // Disable source maps in production for smaller size
|
||||
|
||||
// Additional optimization flags
|
||||
config.optimization.flagIncludedChunks = true;
|
||||
config.optimization.mergeDuplicateChunks = true;
|
||||
config.optimization.removeAvailableModules = true;
|
||||
config.optimization.removeEmptyChunks = true;
|
||||
|
||||
// Aggressive dead code elimination
|
||||
config.optimization.innerGraph = true;
|
||||
config.optimization.mangleExports = true;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Development server configuration
|
||||
config.devServer = config.devServer || {};
|
||||
config.devServer.historyApiFallback = true;
|
||||
@@ -0,0 +1,104 @@
|
||||
const path = require('path');
|
||||
|
||||
// Webpack optimization configuration for production builds
|
||||
config.optimization = {
|
||||
// Enable tree shaking and dead code elimination
|
||||
usedExports: true,
|
||||
sideEffects: false,
|
||||
|
||||
// Code splitting configuration optimized for Kotlin/JS
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
minSize: 30000,
|
||||
maxSize: 300000,
|
||||
maxInitialRequests: 8, // Allow more initial requests for better caching
|
||||
maxAsyncRequests: 15,
|
||||
cacheGroups: {
|
||||
// Kotlin standard library - separate chunk
|
||||
kotlinStdlib: {
|
||||
test: /kotlin-kotlin-stdlib/,
|
||||
name: 'kotlin-stdlib',
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
priority: 30,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
// Coroutines - separate chunk
|
||||
coroutines: {
|
||||
test: /kotlinx-coroutines/,
|
||||
name: 'coroutines',
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
priority: 25,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
// Compose runtime - separate chunk
|
||||
composeRuntime: {
|
||||
test: /compose.*runtime/,
|
||||
name: 'compose-runtime',
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
priority: 20,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
// Large vendor libraries
|
||||
largeVendors: {
|
||||
test: /ktor|androidx-collection|kotlinx-serialization/,
|
||||
name: 'large-vendors',
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
priority: 15,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
// Common vendors
|
||||
vendors: {
|
||||
test: /[\\/]kotlin[\\/]/,
|
||||
name: 'vendors',
|
||||
chunks: 'all',
|
||||
priority: 10,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
// Application code
|
||||
default: {
|
||||
minChunks: 2,
|
||||
priority: -10,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Minimize bundle size
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
// Use default TerserPlugin for JS minification
|
||||
'...',
|
||||
],
|
||||
|
||||
// Runtime chunk optimization
|
||||
runtimeChunk: {
|
||||
name: 'runtime',
|
||||
},
|
||||
};
|
||||
|
||||
// Performance budget adjusted for Kotlin/JS applications
|
||||
// Note: Kotlin/JS apps require all dependencies loaded initially, so larger budgets are realistic
|
||||
config.performance = {
|
||||
maxAssetSize: 400000, // 400KB per asset (realistic for Kotlin libs)
|
||||
maxEntrypointSize: 2000000, // 2MB total entry point (realistic for Kotlin/JS + Compose)
|
||||
hints: 'warning',
|
||||
assetFilter: function(assetFilename) {
|
||||
// Only check JS files for performance
|
||||
return assetFilename.endsWith('.js');
|
||||
},
|
||||
};
|
||||
|
||||
// Production-specific optimizations
|
||||
if (config.mode === 'production') {
|
||||
// Additional compression and optimizations
|
||||
config.optimization.concatenateModules = true;
|
||||
config.optimization.providedExports = true;
|
||||
|
||||
// More aggressive code splitting for production
|
||||
config.optimization.splitChunks.maxInitialRequests = 10;
|
||||
config.optimization.splitChunks.maxAsyncRequests = 10;
|
||||
}
|
||||
Reference in New Issue
Block a user