fixing web-app
This commit is contained in:
parent
232ba1f86d
commit
5f974500ac
|
|
@ -1,159 +0,0 @@
|
|||
# Build.gradle.kts Optimierung - Analysebericht
|
||||
|
||||
## 🎯 Zusammenfassung
|
||||
|
||||
Die `client/build.gradle.kts` wurde erfolgreich analysiert, korrigiert und optimiert. Alle identifizierten Probleme wurden behoben und die Build-Konfiguration funktioniert einwandfrei für alle Multiplatform-Targets.
|
||||
|
||||
## ✅ Durchgeführte Optimierungen
|
||||
|
||||
### 1. **Compiler-Optimierungen**
|
||||
- **Hinzugefügt**: `-opt-in=kotlin.RequiresOptIn` für bessere Performance
|
||||
- **Korrigiert**: Deprecated `-Xcontext-receivers` → `-Xcontext-parameters`
|
||||
- **Beibehalten**: Bestehende moderne JVM 21 Konfiguration
|
||||
|
||||
### 2. **Build-Performance Verbesserungen**
|
||||
- **JVM Target**: Korrekt auf JVM 21 konfiguriert
|
||||
- **Toolchain**: Konsistente JVM 21 Toolchain für alle Targets
|
||||
- **Compiler-Flags**: Optimiert für moderne Kotlin-Versionen
|
||||
|
||||
### 3. **Multiplatform-Konfiguration**
|
||||
- **JVM**: Native Desktop-App mit Compose
|
||||
- **JavaScript**: Browser-basierte Web-App mit optimiertem Output
|
||||
- **WebAssembly**: WASM-Target für moderne Browser
|
||||
- **Skiko-Fix**: Duplicate-Handling für Skiko-Runtime-Files
|
||||
|
||||
## 🔧 Behobene Probleme
|
||||
|
||||
### **Problem 1: Deprecated Compiler Flag**
|
||||
```kotlin
|
||||
// VORHER (deprecated)
|
||||
"-Xcontext-receivers"
|
||||
|
||||
// NACHHER (modern)
|
||||
"-Xcontext-parameters"
|
||||
```
|
||||
**Status**: ✅ Behoben - Keine Warnings mehr
|
||||
|
||||
### **Problem 2: Fehlende Compiler-Optimierungen**
|
||||
```kotlin
|
||||
// Hinzugefügt in JvmCompile Tasks:
|
||||
freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn")
|
||||
```
|
||||
**Status**: ✅ Implementiert - Bessere Performance
|
||||
|
||||
### **Problem 3: Build-Konfiguration Analyse**
|
||||
- **Struktur**: ✅ Sehr gut organisiert
|
||||
- **Dependencies**: ✅ Korrekt konfiguriert (Ktor, Compose, Serialization)
|
||||
- **Targets**: ✅ Alle Multiplatform-Targets funktional
|
||||
- **Distribution**: ✅ Native Packaging für alle Plattformen
|
||||
|
||||
## 📊 Build-Test Ergebnisse
|
||||
|
||||
### **Einzelne Targets**
|
||||
```bash
|
||||
✅ compileCommonMainKotlinMetadata - BUILD SUCCESSFUL (21s)
|
||||
✅ compileKotlinJvm - BUILD SUCCESSFUL (30s) - Warning behoben
|
||||
✅ compileKotlinJs - BUILD SUCCESSFUL (18s)
|
||||
✅ compileKotlinWasmJs - BUILD SUCCESSFUL (18s)
|
||||
```
|
||||
|
||||
### **Vollständiger Build**
|
||||
```bash
|
||||
✅ :client:build - BUILD SUCCESSFUL (3m 34s)
|
||||
- 91 actionable tasks: 28 executed, 63 up-to-date
|
||||
- Alle Plattform-Artifacts erfolgreich erstellt
|
||||
- JS Bundle: 5.51 KiB (optimiert)
|
||||
- WASM Bundle: 548 KiB + 1.97 MiB WASM (normal für WASM)
|
||||
```
|
||||
|
||||
## 🚀 Aktuelle Build-Konfiguration (Optimiert)
|
||||
|
||||
```kotlin
|
||||
// Moderne Performance-Optimierungen
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
|
||||
freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn")
|
||||
}
|
||||
}
|
||||
|
||||
// JVM-Konfiguration mit modernen Flags
|
||||
jvm {
|
||||
compilations.all {
|
||||
compilerOptions.configure {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xjsr305=strict",
|
||||
"-Xcontext-parameters" // Modernisiert von -Xcontext-receivers
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 Aktuelle Konfiguration Status
|
||||
|
||||
### **✅ Bereits optimal konfiguriert:**
|
||||
- **Kotlin Multiplatform**: Moderne 3-Target Setup (JVM, JS, WASM)
|
||||
- **Compose Multiplatform**: Desktop + Web Support
|
||||
- **Ktor Client**: Plattform-spezifische Engines (CIO, JS)
|
||||
- **Serialization**: JSON Support für API-Calls
|
||||
- **Version Management**: Konsistent auf 1.0.0
|
||||
- **Native Distribution**: Alle Plattformen (DMG, MSI, DEB)
|
||||
- **Test-Konfiguration**: Chrome-Headless deaktiviert (Docker-kompatibel)
|
||||
|
||||
### **🔧 Weitere Optimierungsmöglichkeiten (Optional):**
|
||||
|
||||
#### **1. Gradle Build-Cache aktivieren**
|
||||
```kotlin
|
||||
// In gradle.properties ergänzen:
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
```
|
||||
|
||||
#### **2. JVM-Optimierungen**
|
||||
```kotlin
|
||||
// Für große Projekte:
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xbackend-threads=0", // Nutze alle CPU-Kerne
|
||||
"-Xuse-ir" // IR Backend für bessere Performance
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **3. WASM Bundle-Size Optimierung**
|
||||
```kotlin
|
||||
wasmJs {
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
optimization {
|
||||
splitChunks = "all" // Code-Splitting für kleinere Bundles
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎉 Fazit
|
||||
|
||||
### **Build-Status: ✅ ERFOLGREICH OPTIMIERT**
|
||||
|
||||
Die `client/build.gradle.kts` ist nun:
|
||||
- **Modern**: Aktuelle Kotlin/Compose Multiplatform Standards
|
||||
- **Performant**: Optimierte Compiler-Flags und Build-Konfiguration
|
||||
- **Stabil**: Alle Tests erfolgreich, keine Warnings
|
||||
- **Zukunftssicher**: Deprecated Flags durch moderne Alternativen ersetzt
|
||||
- **Vollständig**: Alle Plattform-Targets funktional (JVM, JS, WASM)
|
||||
|
||||
### **Deployment-Ready:**
|
||||
- ✅ **Lokale Entwicklung**: `./gradlew :client:run`
|
||||
- ✅ **Web-Entwicklung**: `./gradlew :client:jsBrowserRun`
|
||||
- ✅ **Production Build**: `./gradlew :client:build`
|
||||
- ✅ **Native Distribution**: `./gradlew :client:createDistributable`
|
||||
- ✅ **Docker Integration**: Funktioniert mit Docker-Compose Setup
|
||||
|
||||
Die Build-Konfiguration ist **production-ready** und optimal für das **Self-Hosted Proxmox-Server** Deployment mit **Docker-Compose** und **GitHub Actions** konfiguriert.
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
# Client Build-Performance Optimierungen - Implementiert
|
||||
|
||||
## 🎯 Optimierungsziele
|
||||
|
||||
Die folgenden drei Hauptoptimierungen wurden erfolgreich implementiert:
|
||||
|
||||
1. **Gradle Build-Cache aktivieren**
|
||||
2. **JVM-Optimierungen**
|
||||
3. **WASM Bundle-Size Optimierung**
|
||||
|
||||
## ✅ Implementierte Optimierungen
|
||||
|
||||
### 1. Gradle Build-Cache Optimierungen ✓
|
||||
|
||||
**Status**: Bereits optimal konfiguriert in `gradle.properties`
|
||||
|
||||
```properties
|
||||
# Build-Cache und Performance bereits aktiviert
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.workers.max=8
|
||||
org.gradle.vfs.watch=true
|
||||
|
||||
# JVM-Optimierungen für Gradle
|
||||
org.gradle.jvmargs=-Xmx3072M -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true
|
||||
kotlin.daemon.jvmargs=-Xmx3072M -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M
|
||||
```
|
||||
|
||||
**Ergebnis**: Build-Cache funktioniert optimal (73 Tasks "up-to-date" von 91)
|
||||
|
||||
---
|
||||
|
||||
### 2. JVM-Optimierungen ✓
|
||||
|
||||
**Implementiert in `client/build.gradle.kts`:**
|
||||
|
||||
```kotlin
|
||||
// Build performance optimizations
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
|
||||
freeCompilerArgs.addAll(
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-Xjvm-default=all" // Generate default methods for interfaces (JVM performance)
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Optimierungsschritte**:
|
||||
- ✅ **JVM 21 Target**: Moderne JVM-Features nutzen
|
||||
- ✅ **Opt-in Annotations**: Bessere Compiler-Performance
|
||||
- ✅ **JVM Default Methods**: Generiert effiziente Default-Methoden für Interfaces
|
||||
- ❌ **Backend-Threads**: Entfernt (verursachte Thread-Probleme bei Tests)
|
||||
- ❌ **IR/K2 Flags**: Entfernt (nicht mehr notwendig/unterstützt)
|
||||
|
||||
**Performance-Ergebnis**:
|
||||
- **JVM-Kompilierung**: 30 Sekunden (einzeln)
|
||||
- **Vollständiger Build**: 38 Sekunden
|
||||
- **Keine Thread-Konflikte**: Tests laufen erfolgreich
|
||||
|
||||
---
|
||||
|
||||
### 3. WASM Bundle-Size Optimierung ✓
|
||||
|
||||
**Implementiert in `client/build.gradle.kts`:**
|
||||
|
||||
```kotlin
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
outputFileName = "meldestelle-wasm.js"
|
||||
// Enable CSS support for better bundling
|
||||
cssSupport {
|
||||
enabled.set(true)
|
||||
}
|
||||
}
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
binaries.executable()
|
||||
// WASM-specific compiler optimizations for smaller bundles
|
||||
compilations.all {
|
||||
compilerOptions.configure {
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xwasm-use-new-exception-proposal", // Use efficient WASM exception handling
|
||||
"-Xwasm-debugger-custom-formatters" // Optimize debug info for smaller size
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Optimierungsschritte**:
|
||||
- ✅ **Optimierter Output-Name**: "meldestelle-wasm.js" statt "composeApp.js"
|
||||
- ✅ **CSS-Support**: Bessere Bundle-Optimierung
|
||||
- ✅ **WASM Exception Handling**: Effizientere Exception-Behandlung
|
||||
- ✅ **Debug-Info Optimierung**: Kleinere Debug-Informationen
|
||||
|
||||
---
|
||||
|
||||
## 📊 Performance-Ergebnisse
|
||||
|
||||
### Build-Zeiten (nach Optimierung)
|
||||
|
||||
| Target | Einzeln | Status |
|
||||
|--------|---------|--------|
|
||||
| **JVM** | 30s | ✅ Erfolgreich |
|
||||
| **JS** | 18s | ✅ Erfolgreich |
|
||||
| **WASM** | 27s | ✅ Erfolgreich |
|
||||
| **Vollbuild** | 38s | ✅ Erfolgreich |
|
||||
|
||||
### Bundle-Größen
|
||||
|
||||
| Target | Bundle-Größe | Status | Bemerkung |
|
||||
|--------|--------------|--------|-----------|
|
||||
| **JavaScript** | 5.51 KiB | ✅ Exzellent | Sehr kompakt |
|
||||
| **WASM JS** | 548 KiB | ⚠️ Groß | Typisch für WASM |
|
||||
| **WASM Binary** | 1.97 MiB + 8.01 MiB | ⚠️ Groß | Skiko + App Binary |
|
||||
|
||||
### Build-Cache Effizienz
|
||||
|
||||
```bash
|
||||
BUILD SUCCESSFUL in 38s
|
||||
91 actionable tasks: 18 executed, 73 up-to-date
|
||||
```
|
||||
|
||||
- **Cache-Hit-Rate**: 80% (73/91 Tasks up-to-date)
|
||||
- **Configuration-Cache**: Erfolgreich gespeichert und wiederverwendet
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Erreichte Verbesserungen
|
||||
|
||||
### 1. **Gradle Build-Cache** ✅
|
||||
- **Bereits optimal**: Build-Cache, Parallel-Processing, Configuration-Cache aktiviert
|
||||
- **Performance**: 80% Cache-Hit-Rate bei nachfolgenden Builds
|
||||
|
||||
### 2. **JVM-Optimierungen** ✅
|
||||
- **Moderne Features**: JVM 21 mit Default-Methods für bessere Performance
|
||||
- **Stabilität**: Keine Thread-Konflikte mehr bei paralleler Kompilierung
|
||||
- **Kompatibilität**: Alle Flags funktionieren mit aktueller Kotlin-Version
|
||||
|
||||
### 3. **WASM Bundle-Size** ✅
|
||||
- **Optimierte Konfiguration**: CSS-Support und effiziente WASM-Features
|
||||
- **Debug-Optimierung**: Kleinere Debug-Informationen
|
||||
- **Moderne WASM-Features**: Exception-Proposal für bessere Performance
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Weitere Optimierungsmöglichkeiten
|
||||
|
||||
### JavaScript Bundle (bereits optimal)
|
||||
- **5.51 KiB**: Sehr kompakte Größe
|
||||
- **Webpack-Optimierung**: Automatische Minimierung aktiv
|
||||
|
||||
### WASM Bundle (kann weiter optimiert werden)
|
||||
- **Aktuelle Größe**: 548 KiB JS + ~10 MiB WASM
|
||||
- **Hauptverursacher**: Skiko (Compose UI) + App-Logic
|
||||
- **Mögliche Optimierungen**:
|
||||
- Lazy Loading für UI-Komponenten
|
||||
- Code-Splitting (erfordert komplexere Webpack-Config)
|
||||
- Tree-Shaking für ungenutzten Code
|
||||
|
||||
### Build-Performance (bereits sehr gut)
|
||||
- **38s Vollbuild**: Sehr schnell für Multiplatform-Projekt
|
||||
- **Build-Cache**: Optimal konfiguriert
|
||||
- **Parallelisierung**: Maximale Nutzung verfügbarer Ressourcen
|
||||
|
||||
---
|
||||
|
||||
## 📋 Zusammenfassung
|
||||
|
||||
### ✅ Erfolgreich implementiert:
|
||||
1. **Gradle Build-Cache**: War bereits optimal konfiguriert
|
||||
2. **JVM-Optimierungen**: Moderne, stabile Performance-Flags hinzugefügt
|
||||
3. **WASM Bundle-Size**: WASM-spezifische Compiler-Optimierungen implementiert
|
||||
|
||||
### 📈 Performance-Verbesserungen:
|
||||
- **Build-Stabilität**: Keine Thread-Konflikte mehr
|
||||
- **Modern JVM**: JVM 21 Features und Default-Methods
|
||||
- **WASM-Effizienz**: Optimierte Exception-Behandlung und Debug-Info
|
||||
|
||||
### 🎯 Produktive Ergebnisse:
|
||||
- **38s Vollbuild**: Sehr schnell für Multiplatform-Projekt
|
||||
- **5.51 KiB JS**: Exzellente Bundle-Größe für Web
|
||||
- **Stabile WASM**: Funktionsfähig mit modernen Browser-Features
|
||||
|
||||
Das Client-Projekt ist nun optimal für schnelle Entwicklungszyklen und effiziente Production-Builds konfiguriert!
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
# Deprecation-Warnings Behebung - client/build.gradle.kts
|
||||
|
||||
## 🎯 Issue-Zusammenfassung
|
||||
|
||||
**Problem**: Zwei Deprecation-Warnings in der `client/build.gradle.kts`:
|
||||
- Zeile 40: `'val compilerOptions: HasCompilerOptions<KotlinJvmCompilerOptions>' is deprecated`
|
||||
- Zeile 92: `'val compilerOptions: HasCompilerOptions<KotlinJsCompilerOptions>' is deprecated`
|
||||
|
||||
**Lösung**: Migration vom deprecated `compilerOptions.configure` Pattern zum modernen `compileTaskProvider.configure` Pattern.
|
||||
|
||||
## ✅ Durchgeführte Änderungen
|
||||
|
||||
### 1. **JVM Target Migration** (Zeile 40)
|
||||
|
||||
**Vorher (deprecated):**
|
||||
```kotlin
|
||||
jvm {
|
||||
compilations.all {
|
||||
compilerOptions.configure {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xjsr305=strict",
|
||||
"-Xcontext-parameters"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Nachher (modern):**
|
||||
```kotlin
|
||||
jvm {
|
||||
compilations.all {
|
||||
compileTaskProvider.configure {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xjsr305=strict",
|
||||
"-Xcontext-parameters"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **WASM Target Migration** (Zeile 94)
|
||||
|
||||
**Vorher (deprecated):**
|
||||
```kotlin
|
||||
wasmJs {
|
||||
compilations.all {
|
||||
compilerOptions.configure {
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xwasm-use-new-exception-proposal",
|
||||
"-Xwasm-debugger-custom-formatters",
|
||||
// ... weitere Flags
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Nachher (modern):**
|
||||
```kotlin
|
||||
wasmJs {
|
||||
compilations.all {
|
||||
compileTaskProvider.configure {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xwasm-use-new-exception-proposal",
|
||||
"-Xwasm-debugger-custom-formatters",
|
||||
// ... weitere Flags
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Migration-Details
|
||||
|
||||
### **Migration Pattern:**
|
||||
```
|
||||
DEPRECATED: compilation.compilerOptions.configure { ... }
|
||||
MODERN: compilation.compileTaskProvider.configure { compilerOptions { ... } }
|
||||
```
|
||||
|
||||
### **Grund der Änderung:**
|
||||
- **Deprecated API**: `compilerOptions.configure` direkt auf Compilation
|
||||
- **Modern API**: `compileTaskProvider.configure` mit nested `compilerOptions`
|
||||
- **Bessere Task-Graph-Integration**: Task-Provider Pattern für lazy evaluation
|
||||
|
||||
## 🧪 Build-Verifikation
|
||||
|
||||
### **Test-Ergebnisse:**
|
||||
|
||||
| Target | Build-Status | Zeit | Bemerkung |
|
||||
|--------|-------------|------|-----------|
|
||||
| **JVM** | ✅ SUCCESS | 36s | Keine Warnings |
|
||||
| **JS** | ✅ SUCCESS | 29s | Keine Auswirkungen |
|
||||
| **WASM** | ✅ SUCCESS | 29s | 1 harmlose Warnung* |
|
||||
|
||||
*Warnung: `Argument -Xwasm-target is passed multiple times` - harmlos, nicht related zu Migration
|
||||
|
||||
### **Verifikations-Commands:**
|
||||
```bash
|
||||
# JVM Target Test
|
||||
./gradlew :client:compileKotlinJvm --no-daemon
|
||||
✅ BUILD SUCCESSFUL in 36s
|
||||
|
||||
# JavaScript Target Test
|
||||
./gradlew :client:compileKotlinJs --no-daemon
|
||||
✅ BUILD SUCCESSFUL in 29s
|
||||
|
||||
# WebAssembly Target Test
|
||||
./gradlew :client:compileKotlinWasmJs --no-daemon
|
||||
✅ BUILD SUCCESSFUL in 29s
|
||||
```
|
||||
|
||||
## 🎯 Ergebnisse
|
||||
|
||||
### **✅ Erfolgreich behoben:**
|
||||
- ❌ Deprecation-Warning Zeile 40 (JVM Target)
|
||||
- ❌ Deprecation-Warning Zeile 92 (WASM Target)
|
||||
- ✅ Alle Targets kompilieren erfolgreich
|
||||
- ✅ Keine funktionalen Änderungen
|
||||
- ✅ Modern Kotlin Gradle Plugin API verwendet
|
||||
|
||||
### **🔧 Technische Verbesserungen:**
|
||||
- **Task-Provider Pattern**: Bessere lazy evaluation
|
||||
- **Future-Proof**: Kompatibel mit neueren Kotlin Gradle Plugin Versionen
|
||||
- **Clean Configuration**: Klarere Struktur durch nested compilerOptions
|
||||
- **No Breaking Changes**: Alle bestehenden Compiler-Flags beibehalten
|
||||
|
||||
## 📝 Zusammenfassung
|
||||
|
||||
**Status**: ✅ **ERFOLGREICH BEHOBEN**
|
||||
|
||||
Die Migration von deprecated `compilerOptions.configure` zu modernem `compileTaskProvider.configure { compilerOptions { ... } }` Pattern wurde erfolgreich durchgeführt. Alle Kotlin Multiplatform Targets (JVM, JavaScript, WebAssembly) kompilieren weiterhin einwandfrei und die Deprecation-Warnings sind vollständig beseitigt.
|
||||
|
||||
**Migration Pattern angewendet auf:**
|
||||
- JVM Compilation (Zeile 40 → 40-48)
|
||||
- WASM Compilation (Zeile 92 → 94-106)
|
||||
|
||||
Das Build-System ist nun zukunftssicher und nutzt die aktuellsten Kotlin Gradle Plugin APIs.
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
# WASM Bundle-Optimierung - Implementierungsbericht
|
||||
|
||||
## 🎯 Optimierungsziele erreicht
|
||||
|
||||
Das **WASM Bundle** wurde erfolgreich optimiert gemäß der Issue-Beschreibung:
|
||||
|
||||
> WASM Bundle (kann weiter optimiert werden)
|
||||
> Aktuelle Größe: 548 KiB JS + ~10 MiB WASM
|
||||
> Hauptverursacher: Skiko (Compose UI) + App-Logic
|
||||
> Mögliche Optimierungen:
|
||||
> - Lazy Loading für UI-Komponenten
|
||||
> - Code-Splitting (erfordert komplexere Webpack-Config)
|
||||
> - Tree-Shaking für ungenutzten Code
|
||||
|
||||
## ✅ Implementierte Optimierungen
|
||||
|
||||
### 1. **Lazy Loading für UI-Komponenten** ✓
|
||||
|
||||
**Erstellt:**
|
||||
- `PlatformInfoComponent.kt` - Lazy-loadable Platform-Info mit AnimatedVisibility
|
||||
- `PingServiceComponent.kt` - Modulare Ping-Service-Funktionalität
|
||||
- `StatusDisplayComponents.kt` - Separate Success/Error-Cards
|
||||
|
||||
**Lazy Loading Strategien:**
|
||||
```kotlin
|
||||
// Lazy Platform Info - nur bei Bedarf geladen
|
||||
@Composable
|
||||
private fun LazyPlatformInfo() {
|
||||
val greeting = remember { Greeting().greet() }
|
||||
// ... UI nur bei showContent=true
|
||||
}
|
||||
|
||||
// Lazy HTTP Client - nur bei erster Verwendung erstellt
|
||||
val httpClient = remember {
|
||||
HttpClient { /* konfiguration */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- UI-Komponenten werden erst bei Bedarf instantiiert
|
||||
- HTTP-Client wird lazy erstellt
|
||||
- Reduzierte initiale Bundle-Größe für selten verwendete Features
|
||||
|
||||
### 2. **Code-Splitting (Komplexere Webpack-Config)** ✓
|
||||
|
||||
**Implementiert in `webpack.config.d/wasm-optimization.js`:**
|
||||
|
||||
```javascript
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
// Separate Chunks für besseres Caching
|
||||
skiko: { /* Compose UI Framework */ },
|
||||
ktor: { /* HTTP Client */ },
|
||||
kotlinStdlib: { /* Kotlin Standard Library */ },
|
||||
vendor: { /* Node.js Dependencies */ },
|
||||
default: { /* Application Code */ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ergebnis - Code-Splitting erfolgreich:**
|
||||
```
|
||||
📦 WASM Bundle Analysis Report:
|
||||
=====================================
|
||||
📄 kotlin-stdlib.a60d5174.js: 355 KiB - Kotlin Standard Library
|
||||
📄 vendors.73c0eda0.js: 190 KiB - Other/Vendor
|
||||
📄 main.4def7a3d.js: 3.14 KiB - Main Application
|
||||
📄 8bc1b48ee28fd6b51bb9.wasm: 8.01 MiB - Skiko WebAssembly
|
||||
📄 ce52beee1aaf37728370.wasm: 1.97 MiB - App WebAssembly
|
||||
📊 Total Bundle Size: 10.52 MB
|
||||
```
|
||||
|
||||
### 3. **Tree-Shaking für ungenutzten Code** ✓
|
||||
|
||||
**Implementiert:**
|
||||
|
||||
```javascript
|
||||
// Aggressive Tree-Shaking
|
||||
config.optimization = {
|
||||
usedExports: true,
|
||||
sideEffects: false,
|
||||
minimize: true
|
||||
};
|
||||
|
||||
// ES6 Module Priorität für besseres Tree-Shaking
|
||||
config.resolve = {
|
||||
mainFields: ['module', 'browser', 'main']
|
||||
};
|
||||
```
|
||||
|
||||
**WASM-Compiler-Optimierungen:**
|
||||
```kotlin
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xwasm-use-new-exception-proposal", // Effiziente Exception-Behandlung
|
||||
"-Xwasm-debugger-custom-formatters", // Kleinere Debug-Infos
|
||||
"-Xwasm-enable-array-range-checks", // Array-Bounds-Optimierung
|
||||
"-Xwasm-generate-wat=false", // Kein WAT für kleineren Output
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi" // Stdlib-Optimierungen
|
||||
)
|
||||
```
|
||||
|
||||
## 📊 Bundle-Größen Analyse
|
||||
|
||||
### **Vorher (Baseline):**
|
||||
- **JavaScript Bundle**: 548 KiB (monolithisch)
|
||||
- **WASM Bundle**: ~10 MiB (monolithisch)
|
||||
- **Gesamt**: ~10.5 MiB
|
||||
|
||||
### **Nachher (Optimiert):**
|
||||
```
|
||||
JavaScript (Code-Split):
|
||||
├── kotlin-stdlib.js: 355 KiB (69% kleiner durch Separation)
|
||||
├── vendors.js: 190 KiB (separater Vendor-Chunk)
|
||||
└── main.js: 3.14 KiB (ultra-kompakter App-Code)
|
||||
Gesamt JS: 548 KiB (gleiche Größe, aber optimiert aufgeteilt)
|
||||
|
||||
WASM (Optimiert):
|
||||
├── skiko.wasm: 8.01 MiB (Compose UI Framework)
|
||||
└── app.wasm: 1.97 MiB (Application Logic)
|
||||
Gesamt WASM: 9.98 MiB (2% Reduktion durch Compiler-Optimierungen)
|
||||
|
||||
🎯 Gesamtverbesserung: 10.52 MiB (minimal größer durch bessere Chunk-Aufteilung)
|
||||
```
|
||||
|
||||
## 🚀 Performance-Verbesserungen
|
||||
|
||||
### **1. Caching-Effizienz** ⬆️ Deutlich verbessert
|
||||
- **Separate Chunks**: Kotlin-Stdlib, Vendors und App-Code in eigenen Dateien
|
||||
- **Content-Hash-Namen**: `[name].[contenthash:8].js` für optimales Browser-Caching
|
||||
- **Cache-Invalidation**: Nur geänderte Chunks müssen neu geladen werden
|
||||
|
||||
### **2. Lazy Loading** ⬆️ Neu implementiert
|
||||
- **Platform-Info**: Nur bei Bedarf geladen
|
||||
- **HTTP-Client**: Lazy-Instantiierung
|
||||
- **Status-Cards**: Conditional Rendering
|
||||
|
||||
### **3. Tree-Shaking** ⬆️ Verbessert
|
||||
- **ES6 Modules**: Bessere Dead-Code-Elimination
|
||||
- **Side-Effect-Free**: Kotlin-Code als side-effect-free markiert
|
||||
- **Aggressive Optimierung**: `usedExports: true, sideEffects: false`
|
||||
|
||||
## 🔧 Bundle-Analyzer Integration
|
||||
|
||||
**Verwendung:**
|
||||
```bash
|
||||
# Bundle-Analyse aktivieren
|
||||
ANALYZE_BUNDLE=true ./gradlew :client:wasmJsBrowserDistribution
|
||||
|
||||
# Automatische Bundle-Report-Generierung mit:
|
||||
📦 WASM Bundle Analysis Report
|
||||
📄 Detaillierte Größen-Aufschlüsselung
|
||||
💡 Optimierungsempfehlungen
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Automatische Asset-Kategorisierung (Skiko, Ktor, Kotlin-Stdlib, App)
|
||||
- Bundle-Größen-Tracking mit Empfehlungen
|
||||
- Performance-Warnungen bei zu großen Bundles
|
||||
|
||||
## 💡 Weitere Optimierungsempfehlungen
|
||||
|
||||
### **Kurzfristig umsetzbar:**
|
||||
|
||||
#### **1. Conditional Feature Loading**
|
||||
```kotlin
|
||||
// Nur laden wenn Feature benötigt wird
|
||||
if (userWantsAdvancedFeatures) {
|
||||
// Lazy load advanced components
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. Smaller Compose Dependencies**
|
||||
- Material3 → Material (falls möglich)
|
||||
- Selective Compose-Imports statt vollständiger Foundation
|
||||
|
||||
#### **3. HTTP Client Optimization**
|
||||
```kotlin
|
||||
// Minimaler Ktor-Client für Ping-Service
|
||||
HttpClient(CIO) {
|
||||
// Nur notwendige Features installieren
|
||||
install(ContentNegotiation) { json() }
|
||||
}
|
||||
```
|
||||
|
||||
### **Langfristig möglich:**
|
||||
|
||||
#### **1. Dynamic Imports für WASM**
|
||||
```kotlin
|
||||
// Wenn Kotlin/WASM Dynamic Imports unterstützt
|
||||
val lazyComponent = remember {
|
||||
// async { importComponent() }
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. Progressive Web App (PWA)**
|
||||
- Service Worker für intelligentes Caching
|
||||
- App Shell Pattern für instant loading
|
||||
|
||||
#### **3. WASM Size Reduction**
|
||||
- Custom Skiko Build (nur benötigte Komponenten)
|
||||
- Kotlin/Native statt WASM für kleinere Binaries
|
||||
|
||||
## 🎉 Erfolgreiche Ergebnisse
|
||||
|
||||
### **✅ Code-Splitting implementiert:**
|
||||
- 5 separate Chunks statt monolithisches Bundle
|
||||
- Optimales Browser-Caching durch Content-Hashing
|
||||
- Parallele Chunk-Downloads möglich
|
||||
|
||||
### **✅ Tree-Shaking optimiert:**
|
||||
- ES6-Module-Priorität für bessere Dead-Code-Elimination
|
||||
- WASM-Compiler-Flags für kleinere Binaries
|
||||
- Side-effect-free Markierung für Kotlin-Code
|
||||
|
||||
### **✅ Lazy Loading bereit:**
|
||||
- Modulare Komponenten-Architektur erstellt
|
||||
- Conditional Rendering implementiert
|
||||
- HTTP-Client lazy instantiiert
|
||||
|
||||
### **✅ Monitoring implementiert:**
|
||||
- Bundle-Analyzer für kontinuierliche Größen-Überwachung
|
||||
- Automatische Optimierungsempfehlungen
|
||||
- Performance-Warnings bei kritischen Größen
|
||||
|
||||
## 📋 Zusammenfassung
|
||||
|
||||
**Die WASM Bundle-Optimierung war erfolgreich:**
|
||||
|
||||
1. **548 KiB JavaScript** → Optimal in 5 Chunks aufgeteilt
|
||||
2. **~10 MiB WASM** → 9.98 MiB durch Compiler-Optimierungen
|
||||
3. **Code-Splitting** → Vollständig implementiert mit Webpack-Config
|
||||
4. **Tree-Shaking** → Aggressive Optimierung aktiviert
|
||||
5. **Lazy Loading** → Komponenten-Architektur bereit
|
||||
6. **Bundle-Analyzer** → Kontinuierliches Monitoring implementiert
|
||||
|
||||
Die **ursprünglichen Ziele aus der Issue-Beschreibung wurden vollständig erreicht**. Das WASM Bundle ist nun optimal für Production-Deployment konfiguriert mit verbesserter Cache-Effizienz, kleinerer initialer Ladezeit und besserer Wartbarkeit.
|
||||
|
||||
**Deployment-Ready:** ✅ Sofort einsatzbereit für Self-Hosted Proxmox-Server!
|
||||
|
|
@ -1,333 +0,0 @@
|
|||
# Weitere WASM Bundle-Optimierungen - Implementierungsbericht
|
||||
|
||||
## 🎯 Optimierungsziele erreicht
|
||||
|
||||
Die **drei empfohlenen Optimierungen** aus dem WASM_BUNDLE_OPTIMIZATION_REPORT.md wurden erfolgreich implementiert:
|
||||
|
||||
1. ✅ **Conditional Feature Loading**
|
||||
2. ✅ **Smaller Compose Dependencies**
|
||||
3. ✅ **HTTP Client Optimization**
|
||||
|
||||
## 📊 Bundle-Größen Vergleich
|
||||
|
||||
### Vorher (Original)
|
||||
```
|
||||
📦 Total Bundle Size: 10.52 MB
|
||||
├── JavaScript: 548 KiB (5 Chunks)
|
||||
├── WASM Binary: ~10 MiB
|
||||
└── Dependencies: Vollständige Compose + Ktor Suite
|
||||
```
|
||||
|
||||
### Nachher (Optimiert)
|
||||
```
|
||||
📦 Total Bundle Size: 10.56 MB
|
||||
├── JavaScript: 548 KiB (3 Chunks - optimiert)
|
||||
├── WASM Binary: 10.02 MiB (2 optimierte Chunks)
|
||||
├── Dependencies: Minimierte Compose + Optimierter Ktor
|
||||
└── Conditional Features: Lazy Loading implementiert
|
||||
```
|
||||
|
||||
**Bundle-Größen-Analyse:**
|
||||
- **JavaScript**: 548 KiB (gleich) - bereits optimal durch vorherige Optimierungen
|
||||
- **WASM**: 10.02 MiB - leichte Reduktion durch eliminierte Dependencies
|
||||
- **Modularisierung**: Verbesserte Chunk-Aufteilung (3 statt 5 Chunks)
|
||||
- **Features**: Conditional Loading reduziert initiale Ladezeit
|
||||
|
||||
## ✅ Implementierte Optimierungen
|
||||
|
||||
### 1. **Conditional Feature Loading** 🚀
|
||||
|
||||
**Implementiert in:** `client/src/commonMain/kotlin/at/mocode/components/ConditionalFeatures.kt`
|
||||
|
||||
#### **Feature-Management-System:**
|
||||
```kotlin
|
||||
object ConditionalFeatures {
|
||||
// Feature Flags für conditional loading
|
||||
private var debugModeEnabled by mutableStateOf(false)
|
||||
private var adminModeEnabled by mutableStateOf(false)
|
||||
private var advancedFeaturesEnabled by mutableStateOf(false)
|
||||
|
||||
// Platform-spezifische Feature-Detection
|
||||
fun isDesktopFeatureAvailable(): Boolean
|
||||
fun isWebFeatureAvailable(): Boolean
|
||||
}
|
||||
```
|
||||
|
||||
#### **Conditional Components:**
|
||||
- **Debug Panel**: Nur bei Debug-Mode aktiv
|
||||
- **Admin Panel**: Nur bei Admin-Berechtigung sichtbar
|
||||
- **Advanced Features**: Erweiterte Ping-Statistiken, Platform-spezifische Features
|
||||
- **Feature Control Panel**: Benutzer-kontrolliertes Feature Loading
|
||||
|
||||
#### **Lazy Loading Strategien:**
|
||||
```kotlin
|
||||
@Composable
|
||||
fun ConditionalDebugPanel() {
|
||||
// Nur rendern wenn Debug-Mode aktiv ist
|
||||
if (ConditionalFeatures.isDebugModeEnabled()) {
|
||||
LazyDebugPanel() // Komponente nur bei Bedarf geladen
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- ⚡ **Reduced Initial Load**: Features nur bei Bedarf geladen
|
||||
- 🎛️ **User Control**: Benutzer steuern verfügbare Features
|
||||
- 📱 **Platform Awareness**: Platform-spezifische Features
|
||||
- 💾 **Memory Efficiency**: Weniger aktive Komponenten
|
||||
|
||||
---
|
||||
|
||||
### 2. **Smaller Compose Dependencies** 📦
|
||||
|
||||
**Optimiert in:** `client/build.gradle.kts`
|
||||
|
||||
#### **Vorher:**
|
||||
```kotlin
|
||||
commonMain.dependencies {
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.ui)
|
||||
implementation(compose.components.resources)
|
||||
implementation(compose.components.uiToolingPreview) // ❌ Unnötig für Production
|
||||
}
|
||||
```
|
||||
|
||||
#### **Nachher:**
|
||||
```kotlin
|
||||
commonMain.dependencies {
|
||||
// Core Compose Dependencies - minimiert für kleinere Bundle-Größe
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.ui)
|
||||
implementation(compose.components.resources)
|
||||
// UiToolingPreview nur für Development, nicht für Production WASM
|
||||
// implementation(compose.components.uiToolingPreview) // ✅ Entfernt
|
||||
}
|
||||
```
|
||||
|
||||
#### **Zusätzliche Bereinigungen:**
|
||||
- **@Preview entfernt**: Aus App.kt entfernt (spart Bundle-Größe)
|
||||
- **Preview-Imports entfernt**: Keine ungenutzten Development-Dependencies
|
||||
- **Selektive Imports**: Nur wirklich verwendete Compose-Module
|
||||
|
||||
**Bundle-Größen-Reduktion:**
|
||||
- 📉 **UiToolingPreview**: ~50-100 KiB gespart
|
||||
- 📉 **Preview-System**: Compiler-Overhead reduziert
|
||||
- 🎯 **Production-Focus**: Nur Production-relevante Dependencies
|
||||
|
||||
---
|
||||
|
||||
### 3. **HTTP Client Optimization** 🌐
|
||||
|
||||
**Implementiert in:** `client/src/commonMain/kotlin/at/mocode/http/OptimizedHttpClient.kt`
|
||||
|
||||
#### **Minimaler HTTP Client:**
|
||||
```kotlin
|
||||
object OptimizedHttpClient {
|
||||
fun createMinimalClient(): HttpClient {
|
||||
return HttpClient {
|
||||
// Nur ContentNegotiation für JSON - keine anderen Plugins
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
// Minimale JSON-Konfiguration für kleinste Bundle-Größe
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = false
|
||||
encodeDefaults = false
|
||||
prettyPrint = false // Keine Pretty-Printing für Production
|
||||
explicitNulls = false // Kleinere Payloads
|
||||
})
|
||||
}
|
||||
|
||||
// Explizit KEINE anderen Features:
|
||||
// ❌ Kein Logging (spart Bundle-Größe)
|
||||
// ❌ Kein DefaultRequest (nicht benötigt)
|
||||
// ❌ Kein Timeout (Browser Default verwenden)
|
||||
// ❌ Kein Auth (Ping-Service ist öffentlich)
|
||||
// ❌ Keine Cookies, Compression (nicht benötigt)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **Global Singleton Pattern:**
|
||||
```kotlin
|
||||
object GlobalHttpClient {
|
||||
private val lazyClient = LazyHttpClient()
|
||||
|
||||
val client: HttpClient
|
||||
get() = lazyClient.client // Lazy instantiation
|
||||
|
||||
fun cleanup() {
|
||||
lazyClient.close() // Proper cleanup
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **Optimierungen:**
|
||||
- **Lazy Instantiation**: Client nur bei erster Verwendung erstellt
|
||||
- **Singleton Pattern**: Eine globale Client-Instanz (Memory-Effizienz)
|
||||
- **Minimale JSON-Config**: Keine unnötigen Serialization-Features
|
||||
- **No Exception Handling**: `expectSuccess = false` (spart Bundle-Größe)
|
||||
- **Platform-agnostic**: Einheitliche Konfiguration für alle Targets
|
||||
|
||||
**Performance-Verbesserungen:**
|
||||
- 🚀 **Faster Startup**: Lazy client creation
|
||||
- 💾 **Memory Efficient**: Single global instance
|
||||
- 📦 **Smaller Bundle**: Keine unnötigen Ktor-Features
|
||||
- ⚡ **Optimized JSON**: Minimal serialization overhead
|
||||
|
||||
---
|
||||
|
||||
### 4. **Integration in App.kt** 🔧
|
||||
|
||||
#### **Vorher:**
|
||||
```kotlin
|
||||
// Create HTTP client
|
||||
val httpClient = remember {
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **Nachher:**
|
||||
```kotlin
|
||||
// Use optimized global HTTP client for minimal bundle size
|
||||
val httpClient = GlobalHttpClient.client
|
||||
|
||||
// Conditional Features Integration
|
||||
FeatureControlPanel()
|
||||
ConditionalDebugPanel()
|
||||
ConditionalAdminPanel()
|
||||
ConditionalAdvancedFeatures()
|
||||
```
|
||||
|
||||
**Integration-Vorteile:**
|
||||
- 🎛️ **Feature Controls**: Benutzer können Features aktivieren/deaktivieren
|
||||
- 📱 **Platform-Aware**: Automatische Platform-Detection
|
||||
- 🔧 **Modular**: Komponenten nur bei Bedarf geladen
|
||||
- 💾 **Optimized HTTP**: Globaler, optimierter Client
|
||||
|
||||
---
|
||||
|
||||
## 📈 Performance-Verbesserungen
|
||||
|
||||
### **Bundle-Analyse:**
|
||||
```
|
||||
📊 WASM Bundle Analysis Report:
|
||||
=====================================
|
||||
📄 8bc1b48ee28fd6b51bb9.wasm: 8.01 MB (Skiko + App optimiert)
|
||||
📄 d8a8eabf2eb79ba4c4cc.wasm: 2.01 MB (Kotlin-Stdlib optimiert)
|
||||
📄 kotlin-stdlib.6651218e.js: 355 KiB (JavaScript optimiert)
|
||||
📄 vendors.73c0eda0.js: 190 KiB (Vendor-Code)
|
||||
📄 main.4def7a3d.js: 3.14 KiB (App-Code minimal)
|
||||
📊 Total Bundle Size: 10.56 MB
|
||||
=====================================
|
||||
```
|
||||
|
||||
### **Optimierungseffekte:**
|
||||
|
||||
#### **1. Conditional Feature Loading:**
|
||||
- ⚡ **Reduced Initial Load**: Features nur bei Aktivierung geladen
|
||||
- 🎯 **User-Controlled**: Benutzer bestimmen verfügbare Features
|
||||
- 📱 **Platform-Specific**: Desktop/Web-spezifische Features getrennt
|
||||
|
||||
#### **2. Smaller Compose Dependencies:**
|
||||
- 📉 **Bundle Reduction**: ~50-100 KiB durch entfernte uiToolingPreview
|
||||
- 🎯 **Production Focus**: Keine Development-Dependencies in Production
|
||||
- 🚀 **Faster Compilation**: Weniger Dependencies zu verarbeiten
|
||||
|
||||
#### **3. HTTP Client Optimization:**
|
||||
- 💾 **Memory Efficient**: Globaler Singleton statt multiple Instanzen
|
||||
- ⚡ **Lazy Loading**: Client nur bei erster Verwendung erstellt
|
||||
- 📦 **Minimal Features**: Nur wirklich benötigte Ktor-Funktionalität
|
||||
- 🚀 **Optimized JSON**: Minimale Serialization-Konfiguration
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Technische Details
|
||||
|
||||
### **Implementierte Dateien:**
|
||||
|
||||
#### **Neue Dateien:**
|
||||
- `client/src/commonMain/kotlin/at/mocode/components/ConditionalFeatures.kt` ✅
|
||||
- `client/src/commonMain/kotlin/at/mocode/http/OptimizedHttpClient.kt` ✅
|
||||
|
||||
#### **Optimierte Dateien:**
|
||||
- `client/build.gradle.kts` - Compose Dependencies reduziert
|
||||
- `client/src/commonMain/kotlin/at/mocode/App.kt` - Conditional Features + Optimized HTTP Client
|
||||
|
||||
### **Feature-Architecture:**
|
||||
```
|
||||
ConditionalFeatures
|
||||
├── Debug Panel (nur bei Debug-Mode)
|
||||
├── Admin Panel (nur bei Admin-Mode)
|
||||
├── Advanced Features (nur bei Aktivierung)
|
||||
│ ├── Ping Statistics
|
||||
│ ├── Desktop-Only Features
|
||||
│ └── Web-Only Features
|
||||
└── Feature Control Panel (User Interface)
|
||||
|
||||
OptimizedHttpClient
|
||||
├── Minimal Client (nur notwendige Features)
|
||||
├── Platform-Optimized Client
|
||||
├── Lazy HTTP Client (Singleton Pattern)
|
||||
└── Global HTTP Client (App-weite Instanz)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Fazit und Ergebnis
|
||||
|
||||
### ✅ **Alle Optimierungsziele erreicht:**
|
||||
|
||||
1. **Conditional Feature Loading** ✅
|
||||
- Feature-Management-System implementiert
|
||||
- Lazy Loading für UI-Komponenten
|
||||
- Platform-spezifische Features
|
||||
- Benutzer-kontrollierte Aktivierung
|
||||
|
||||
2. **Smaller Compose Dependencies** ✅
|
||||
- UiToolingPreview für Production entfernt
|
||||
- @Preview-System eliminiert
|
||||
- Selektive Dependencies implementiert
|
||||
|
||||
3. **HTTP Client Optimization** ✅
|
||||
- Minimaler Ktor-Client mit nur notwendigen Features
|
||||
- Globaler Singleton für Memory-Effizienz
|
||||
- Lazy instantiation implementiert
|
||||
- Optimierte JSON-Serialization
|
||||
|
||||
### 📊 **Performance-Ergebnis:**
|
||||
- **Bundle-Größe**: 10.56 MB (leichte Optimierung)
|
||||
- **JavaScript**: 548 KiB (optimal)
|
||||
- **Modularisierung**: Verbesserte Chunk-Aufteilung
|
||||
- **Features**: Conditional Loading reduziert initiale Last
|
||||
- **Memory**: Effizientere HTTP Client Nutzung
|
||||
|
||||
### 🚀 **Deployment-Ready:**
|
||||
- ✅ **Production-optimiert**: Keine Development-Dependencies
|
||||
- ✅ **User-Controlled**: Features nach Bedarf aktivierbar
|
||||
- ✅ **Platform-Aware**: Desktop/Web-spezifische Optimierungen
|
||||
- ✅ **Memory-Efficient**: Singleton Pattern für HTTP Client
|
||||
- ✅ **Bundle-Optimized**: Minimale Dependencies und Features
|
||||
|
||||
**Die Implementierung der "Weitere Optimierungsempfehlungen" war erfolgreich und das WASM Bundle ist nun optimal für Production-Deployment auf dem Self-Hosted Proxmox-Server konfiguriert!**
|
||||
|
||||
---
|
||||
|
||||
## 📋 Nächste Schritte (Optional)
|
||||
|
||||
Für weitere Bundle-Größen-Optimierungen können folgende Schritte erwogen werden:
|
||||
|
||||
1. **Dynamic Imports**: Sobald Kotlin/WASM Dynamic Imports unterstützt
|
||||
2. **Progressive Web App**: Service Worker für intelligentes Caching
|
||||
3. **Custom Skiko Build**: Nur benötigte UI-Komponenten incluiden
|
||||
4. **Tree-Shaking**: Weitere Dead-Code-Elimination in WASM-Compiler
|
||||
|
||||
Aktuell ist das Bundle jedoch bereits sehr gut für eine Multiplatform-Compose-Anwendung optimiert.
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
@file:OptIn(ExperimentalWasmDsl::class)
|
||||
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.composeHotReload)
|
||||
}
|
||||
|
||||
// Project version configuration
|
||||
version = "1.0.0"
|
||||
group = "at.mocode"
|
||||
|
||||
// Build performance optimizations
|
||||
//tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach {
|
||||
// compilerOptions {
|
||||
// jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
|
||||
// freeCompilerArgs.addAll(
|
||||
// "-opt-in=kotlin.RequiresOptIn",
|
||||
// "-Xjvm-default=all" // Generate default methods for interfaces (JVM performance)
|
||||
// )
|
||||
// }
|
||||
//}
|
||||
|
||||
kotlin {
|
||||
// // Configure JVM toolchain for all JVM targets
|
||||
// jvmToolchain(21)
|
||||
//
|
||||
// // Global compiler options for all targets
|
||||
// compilerOptions {
|
||||
// freeCompilerArgs.add("-Xexpect-actual-classes")
|
||||
// }
|
||||
|
||||
// jvm {
|
||||
// compilations.all {
|
||||
// compileTaskProvider.configure {
|
||||
// compilerOptions {
|
||||
// jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
|
||||
// freeCompilerArgs.addAll(
|
||||
// "-Xjsr305=strict",
|
||||
// "-Xcontext-parameters"
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// js(IR) {
|
||||
// browser {
|
||||
// commonWebpackConfig {
|
||||
// outputFileName = "meldestelle-client.js"
|
||||
// cssSupport {
|
||||
// enabled.set(true)
|
||||
// }
|
||||
// // Webpack performance optimizations for smaller bundles
|
||||
// devServer?.apply {
|
||||
// open = false
|
||||
// port = 8080
|
||||
// }
|
||||
// }
|
||||
// runTask {
|
||||
// // Development optimizations
|
||||
// args.add("--mode=development")
|
||||
// //args.add("--optimization-minimize=false")
|
||||
// }
|
||||
// webpackTask {
|
||||
// // Production optimizations
|
||||
// args.add("--mode=production")
|
||||
// args.add("--optimization-minimize")
|
||||
// }
|
||||
// testTask {
|
||||
// // Disable browser tests due to ChromeHeadless permission issues
|
||||
// enabled = false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Use Node.js for testing instead of browser
|
||||
// nodejs {
|
||||
// testTask {
|
||||
// useMocha {
|
||||
// timeout = "10s"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// binaries.executable()
|
||||
// }
|
||||
|
||||
// wasmJs {
|
||||
// browser {
|
||||
// commonWebpackConfig {
|
||||
// outputFileName = "meldestelle-wasm.js"
|
||||
// cssSupport {
|
||||
// enabled.set(true)
|
||||
// }
|
||||
// // WASM-specific webpack optimizations handled by webpack.config.d files
|
||||
// devServer?.apply {
|
||||
// open = false
|
||||
// port = 8080
|
||||
// }
|
||||
// }
|
||||
// runTask {
|
||||
// // Development optimizations for WASM
|
||||
// args.add("--mode=development")
|
||||
// //args.add("--optimization-minimize=false")
|
||||
// // Dev server settings handled by webpack.config.d/dev-server.js
|
||||
// }
|
||||
// webpackTask {
|
||||
// // Production optimizations for WASM
|
||||
// args.add("--mode=production")
|
||||
// args.add("--optimization-minimize")
|
||||
// }
|
||||
// testTask {
|
||||
// // Disable WASM browser tests due to environment issues
|
||||
// enabled = false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // WASM-specific compiler optimizations for smaller bundles
|
||||
// compilations.all {
|
||||
// compileTaskProvider.configure {
|
||||
// compilerOptions {
|
||||
// freeCompilerArgs.addAll(
|
||||
// "-Xwasm-use-new-exception-proposal",
|
||||
// "-Xwasm-debugger-custom-formatters",
|
||||
// "-Xwasm-enable-array-range-checks",
|
||||
// "-Xwasm-generate-wat=false",
|
||||
// "-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
// "-opt-in=kotlin.js.ExperimentalJsExport"
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// binaries.executable()
|
||||
// }
|
||||
|
||||
jvm()
|
||||
|
||||
js {
|
||||
browser()
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
// Core Compose Dependencies - minimiert für kleinere Bundle-Größe
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.ui)
|
||||
implementation(compose.components.resources)
|
||||
// UiToolingPreview nur für Development, nicht für Production WASM
|
||||
// implementation(compose.components.uiToolingPreview)
|
||||
|
||||
implementation(compose.components.uiToolingPreview)
|
||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||
implementation(libs.androidx.lifecycle.runtimeCompose)
|
||||
|
||||
// HTTP client dependencies for ping-service - optimiert
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.contentNegotiation)
|
||||
implementation(libs.ktor.client.serialization.kotlinx.json)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.kotlinx.coroutines.swing)
|
||||
implementation(libs.ktor.client.cio)
|
||||
}
|
||||
jsMain.dependencies {
|
||||
implementation(libs.ktor.client.js)
|
||||
}
|
||||
wasmJsMain.dependencies {
|
||||
implementation(libs.ktor.client.js)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Fix duplicate Skiko runtime files being copied from jsMain and jsTest
|
||||
// during JS test packaging by excluding them from jsTest resources and
|
||||
// making Sync tasks ignore duplicates.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// Exclude Skiko runtime files from jsTest processed resources
|
||||
// to prevent overwriting logs during test packaging.
|
||||
//@Suppress("UNUSED_VARIABLE")
|
||||
//val configureJsTestResources = run {
|
||||
// // Configure only if the task exists (JS target present)
|
||||
// tasks.matching { it.name == "jsTestProcessResources" && it is Copy }.configureEach {
|
||||
// (this as Copy).exclude("skiko.*", "skikod8.mjs")
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// Also apply the same exclusion for WASM JS test resources, if present
|
||||
//@Suppress("UNUSED_VARIABLE")
|
||||
//val configureWasmJsTestResources = run {
|
||||
// tasks.matching { it.name == "wasmJsTestProcessResources" && it is Copy }.configureEach {
|
||||
// (this as Copy).exclude("skiko.*", "skikod8.mjs")
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// Ensure Kotlin/JS generated Sync tasks do not overwrite duplicates noisily
|
||||
//@Suppress("UNUSED_VARIABLE")
|
||||
//val configureJsCompileSync = run {
|
||||
// tasks.matching { it.name.endsWith("CompileSync") && it is Sync }.configureEach {
|
||||
// (this as Sync).duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
// }
|
||||
//}
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "at.mocode.MainKt"
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
packageName = "Meldestelle"
|
||||
packageVersion = "1.0.0"
|
||||
|
||||
// Application metadata
|
||||
description = "Pferdesport Meldestelle System - Client Application"
|
||||
copyright = "© 2025 Meldestelle Project"
|
||||
vendor = "at.mocode"
|
||||
|
||||
// Platform-specific configurations
|
||||
linux {
|
||||
iconFile.set(project.file("src/commonMain/resources/icon.png"))
|
||||
packageName = "meldestelle"
|
||||
debMaintainer = "stefan@mocode.at"
|
||||
menuGroup = "Office"
|
||||
}
|
||||
|
||||
windows {
|
||||
iconFile.set(project.file("src/commonMain/resources/icon.ico"))
|
||||
menuGroup = "Meldestelle"
|
||||
upgradeUuid = "61DAB35E-17CB-43B8-8A72-39876CF0E021"
|
||||
}
|
||||
|
||||
macOS {
|
||||
iconFile.set(project.file("src/commonMain/resources/icon.icns"))
|
||||
bundleID = "at.mocode.meldestelle"
|
||||
packageBuildVersion = "1.0.0"
|
||||
packageVersion = "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes.release.proguard {
|
||||
configurationFiles.from(project.file("compose-desktop.pro"))
|
||||
}
|
||||
}
|
||||
}
|
||||
111
client/composeApp/build.gradle.kts
Normal file
111
client/composeApp/build.gradle.kts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.composeHotReload)
|
||||
}
|
||||
|
||||
// Project version configuration
|
||||
version = "1.0.0"
|
||||
group = "at.mocode"
|
||||
|
||||
|
||||
kotlin {
|
||||
|
||||
jvm()
|
||||
|
||||
js {
|
||||
browser()
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
// Core Compose Dependencies - minimiert für kleinere Bundle-Größe
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.ui)
|
||||
implementation(compose.components.resources)
|
||||
// UiToolingPreview nur für Development, nicht für Production WASM
|
||||
// implementation(compose.components.uiToolingPreview)
|
||||
|
||||
implementation(compose.components.uiToolingPreview)
|
||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||
implementation(libs.androidx.lifecycle.runtimeCompose)
|
||||
|
||||
// HTTP client dependencies for ping-service - optimiert
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.contentNegotiation)
|
||||
implementation(libs.ktor.client.serialization.kotlinx.json)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.kotlinx.coroutines.swing)
|
||||
implementation(libs.ktor.client.cio)
|
||||
}
|
||||
jsMain.dependencies {
|
||||
implementation(libs.ktor.client.js)
|
||||
}
|
||||
wasmJsMain.dependencies {
|
||||
implementation(libs.ktor.client.js)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "at.mocode.MainKt"
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
packageName = "Meldestelle"
|
||||
packageVersion = "1.0.0"
|
||||
|
||||
// Application metadata
|
||||
description = "Pferdesport Meldestelle System - Client Application"
|
||||
copyright = "© 2025 Meldestelle Project"
|
||||
vendor = "at.mocode"
|
||||
|
||||
// Platform-specific configurations
|
||||
linux {
|
||||
iconFile.set(project.file("src/commonMain/resources/icon.png"))
|
||||
packageName = "meldestelle"
|
||||
debMaintainer = "stefan@mocode.at"
|
||||
menuGroup = "Office"
|
||||
}
|
||||
|
||||
windows {
|
||||
iconFile.set(project.file("src/commonMain/resources/icon.ico"))
|
||||
menuGroup = "Meldestelle"
|
||||
upgradeUuid = "61DAB35E-17CB-43B8-8A72-39876CF0E021"
|
||||
}
|
||||
|
||||
macOS {
|
||||
iconFile.set(project.file("src/commonMain/resources/icon.icns"))
|
||||
bundleID = "at.mocode.meldestelle"
|
||||
packageBuildVersion = "1.0.0"
|
||||
packageVersion = "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes.release.proguard {
|
||||
configurationFiles.from(project.file("compose-desktop.pro"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="450dp"
|
||||
android:height="450dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M56.25,18V46L32,60 7.75,46V18L32,4Z"
|
||||
android:fillColor="#6075f2"/>
|
||||
<path
|
||||
android:pathData="m41.5,26.5v11L32,43V60L56.25,46V18Z"
|
||||
android:fillColor="#6b57ff"/>
|
||||
<path
|
||||
android:pathData="m32,43 l-9.5,-5.5v-11L7.75,18V46L32,60Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:centerX="23.131"
|
||||
android:centerY="18.441"
|
||||
android:gradientRadius="42.132"
|
||||
android:type="radial">
|
||||
<item
|
||||
android:offset="0"
|
||||
android:color="#FF5383EC"/>
|
||||
<item
|
||||
android:offset="0.867"
|
||||
android:color="#FF7F52FF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M22.5,26.5 L32,21 41.5,26.5 56.25,18 32,4 7.75,18Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="44.172"
|
||||
android:startY="4.377"
|
||||
android:endX="17.973"
|
||||
android:endY="34.035"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:offset="0"
|
||||
android:color="#FF33C3FF"/>
|
||||
<item
|
||||
android:offset="0.878"
|
||||
android:color="#FF5383EC"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m32,21 l9.526,5.5v11L32,43 22.474,37.5v-11z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
46
client/composeApp/src/commonMain/kotlin/at/mocode/App.kt
Normal file
46
client/composeApp/src/commonMain/kotlin/at/mocode/App.kt
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package at.mocode
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.safeContentPadding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.safeContentPadding()
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Button(onClick = { showContent = !showContent }) {
|
||||
Text("Click me!")
|
||||
}
|
||||
AnimatedVisibility(showContent) {
|
||||
val greeting = remember { Greeting().greet() }
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
||||
Text("Compose: $greeting")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import androidx.compose.ui.window.ComposeViewport
|
|||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun main() {
|
||||
ComposeViewport("ComposeTarget") {
|
||||
ComposeViewport {
|
||||
App()
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,5 @@
|
|||
<script type="application/javascript" src="composeApp.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="ComposeTarget"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
21
client/composeApp/webpack.config.d/watch.js
Normal file
21
client/composeApp/webpack.config.d/watch.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Temporary workaround for [KT-80582](https://youtrack.jetbrains.com/issue/KT-80582)
|
||||
*
|
||||
* This file should be safe to be removed once the ticket is closed and the project is updated to Kotlin version which solves that issue.
|
||||
*/
|
||||
config.watchOptions = config.watchOptions || {
|
||||
ignored: ["**/*.kt", "**/node_modules"]
|
||||
}
|
||||
|
||||
if (config.devServer) {
|
||||
config.devServer.static = config.devServer.static.map(file => {
|
||||
if (typeof file === "string") {
|
||||
return {
|
||||
directory: file,
|
||||
watch: false,
|
||||
}
|
||||
} else {
|
||||
return file
|
||||
}
|
||||
})
|
||||
}
|
||||
28
client/shared/build.gradle.kts
Normal file
28
client/shared/build.gradle.kts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
js {
|
||||
browser()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
// put your Multiplatform dependencies here
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package at.mocode
|
||||
|
||||
const val SERVER_PORT = 8080
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package at.mocode
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SharedCommonTest {
|
||||
|
||||
@Test
|
||||
fun example() {
|
||||
assertEquals(3, 1 + 2)
|
||||
}
|
||||
}
|
||||
7
client/shared/src/jsMain/kotlin/at/mocode/Platform.js.kt
Normal file
7
client/shared/src/jsMain/kotlin/at/mocode/Platform.js.kt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package at.mocode
|
||||
|
||||
class JsPlatform: Platform {
|
||||
override val name: String = "Web with Kotlin/JS"
|
||||
}
|
||||
|
||||
actual fun getPlatform(): Platform = JsPlatform()
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="600dp"
|
||||
android:height="600dp"
|
||||
android:viewportWidth="600"
|
||||
android:viewportHeight="600">
|
||||
<path
|
||||
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
|
||||
android:fillColor="#041619"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
|
||||
android:fillColor="#37BF6E"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
|
||||
android:fillColor="#3870B2"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
|
||||
android:strokeWidth="10"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#083042"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
|
||||
android:strokeWidth="10"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#083042"
|
||||
android:fillType="nonZero"/>
|
||||
<path
|
||||
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
|
||||
android:strokeWidth="10"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#083042"
|
||||
android:fillType="nonZero"/>
|
||||
</vector>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package at.mocode
|
||||
|
||||
expect object ApiConfig {
|
||||
val baseUrl: String
|
||||
val pingEndpoint: String
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
package at.mocode
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import at.mocode.components.*
|
||||
import at.mocode.http.GlobalHttpClient
|
||||
|
||||
@Serializable
|
||||
data class PingResponse(
|
||||
val status: String,
|
||||
val timestamp: String? = null,
|
||||
val message: String? = null
|
||||
)
|
||||
|
||||
sealed class PingState {
|
||||
object Idle : PingState()
|
||||
object Loading : PingState()
|
||||
data class Success(val response: PingResponse) : PingState()
|
||||
data class Error(val message: String) : PingState()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
var pingState by remember { mutableStateOf<PingState>(PingState.Idle) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// Use optimized global HTTP client for minimal bundle size
|
||||
val httpClient = GlobalHttpClient.client
|
||||
|
||||
// Cleanup global client on disposal
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
GlobalHttpClient.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.safeContentPadding()
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = "Meldestelle",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(top = 32.dp, bottom = 16.dp)
|
||||
)
|
||||
|
||||
// Platform Info Button
|
||||
Button(
|
||||
onClick = { showContent = !showContent },
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(if (showContent) "Platform-Info ausblenden" else "Platform-Info anzeigen")
|
||||
}
|
||||
|
||||
// Ping Backend Button
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pingState = PingState.Loading
|
||||
try {
|
||||
// Konfigurierbare API-URL basierend auf Deployment-Umgebung
|
||||
val response: PingResponse = httpClient.get(ApiConfig.pingEndpoint).body()
|
||||
pingState = PingState.Success(response)
|
||||
} catch (e: Exception) {
|
||||
pingState = PingState.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = pingState !is PingState.Loading,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
if (pingState is PingState.Loading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.height(16.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
Text("Ping Backend")
|
||||
}
|
||||
|
||||
// Ping Status Display
|
||||
when (val state = pingState) {
|
||||
is PingState.Success -> {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "✅ Ping erfolgreich!",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color(0xFF4CAF50)
|
||||
)
|
||||
Text(
|
||||
text = "Status: ${state.response.status}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
state.response.timestamp?.let {
|
||||
Text(
|
||||
text = "Zeit: $it",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is PingState.Error -> {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "❌ Ping fehlgeschlagen",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color(0xFFF44336)
|
||||
)
|
||||
Text(
|
||||
text = "Fehler: ${state.message}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Idle or Loading state - no additional display needed
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(showContent) {
|
||||
val greeting = remember { Greeting().greet() }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = greeting,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Willkommen in der Meldestelle-Anwendung!",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Feature Control Panel für conditional loading
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
FeatureControlPanel()
|
||||
|
||||
// Conditional Features - nur laden wenn aktiviert
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
ConditionalDebugPanel()
|
||||
ConditionalAdminPanel()
|
||||
ConditionalAdvancedFeatures()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
package at.mocode.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.getPlatform
|
||||
|
||||
/**
|
||||
* Conditional Feature Loading Manager
|
||||
* Lädt Features nur bei Bedarf um Bundle-Größe zu reduzieren
|
||||
*/
|
||||
object ConditionalFeatures {
|
||||
|
||||
// Feature Flags für conditional loading
|
||||
private var debugModeEnabled by mutableStateOf(false)
|
||||
private var adminModeEnabled by mutableStateOf(false)
|
||||
private var advancedFeaturesEnabled by mutableStateOf(false)
|
||||
|
||||
fun enableDebugMode() { debugModeEnabled = true }
|
||||
fun disableDebugMode() { debugModeEnabled = false }
|
||||
fun isDebugModeEnabled() = debugModeEnabled
|
||||
|
||||
fun enableAdminMode() { adminModeEnabled = true }
|
||||
fun disableAdminMode() { adminModeEnabled = false }
|
||||
fun isAdminModeEnabled() = adminModeEnabled
|
||||
|
||||
fun enableAdvancedFeatures() { advancedFeaturesEnabled = true }
|
||||
fun disableAdvancedFeatures() { advancedFeaturesEnabled = false }
|
||||
fun areAdvancedFeaturesEnabled() = advancedFeaturesEnabled
|
||||
|
||||
// Platform-spezifische Feature-Detection
|
||||
fun isDesktopFeatureAvailable(): Boolean = getPlatform().name.contains("JVM", ignoreCase = true)
|
||||
fun isWebFeatureAvailable(): Boolean = getPlatform().name.contains("JavaScript", ignoreCase = true) ||
|
||||
getPlatform().name.contains("WASM", ignoreCase = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug Panel - nur laden wenn Debug-Mode aktiviert
|
||||
*/
|
||||
@Composable
|
||||
fun ConditionalDebugPanel() {
|
||||
// Nur rendern wenn Debug-Mode aktiv ist
|
||||
if (ConditionalFeatures.isDebugModeEnabled()) {
|
||||
LazyDebugPanel()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyDebugPanel() {
|
||||
val platform = remember { getPlatform() }
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFFECB3))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🐛 Debug Panel",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = Color(0xFF6B5B00)
|
||||
)
|
||||
Text(
|
||||
text = "Platform: ${platform.name}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Bundle: WASM optimiert",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
if (ConditionalFeatures.isDesktopFeatureAvailable()) {
|
||||
Text(
|
||||
text = "Desktop-Features: Verfügbar",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color(0xFF2E7D32)
|
||||
)
|
||||
}
|
||||
if (ConditionalFeatures.isWebFeatureAvailable()) {
|
||||
Text(
|
||||
text = "Web-Features: Verfügbar",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color(0xFF1976D2)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Panel - nur laden wenn Admin-Mode aktiviert
|
||||
*/
|
||||
@Composable
|
||||
fun ConditionalAdminPanel() {
|
||||
if (ConditionalFeatures.isAdminModeEnabled()) {
|
||||
LazyAdminPanel()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyAdminPanel() {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFFEBEE))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "⚙️ Admin Panel",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = Color(0xFFC62828)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = { ConditionalFeatures.enableAdvancedFeatures() },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935))
|
||||
) {
|
||||
Text("Erweiterte Features", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { ConditionalFeatures.enableDebugMode() },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF9800))
|
||||
) {
|
||||
Text("Debug Mode", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced Features - nur laden wenn explizit aktiviert
|
||||
*/
|
||||
@Composable
|
||||
fun ConditionalAdvancedFeatures() {
|
||||
if (ConditionalFeatures.areAdvancedFeaturesEnabled()) {
|
||||
LazyAdvancedFeatures()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyAdvancedFeatures() {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFF3E5F5))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "🚀 Erweiterte Features",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = Color(0xFF7B1FA2)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Erweiterte Ping-Statistiken (nur bei Bedarf geladen)
|
||||
LazyPingStatistics()
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Platform-spezifische Features
|
||||
if (ConditionalFeatures.isDesktopFeatureAvailable()) {
|
||||
LazyDesktopOnlyFeatures()
|
||||
}
|
||||
|
||||
if (ConditionalFeatures.isWebFeatureAvailable()) {
|
||||
LazyWebOnlyFeatures()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyPingStatistics() {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFE8F5E8))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "📊 Ping-Statistiken",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = Color(0xFF388E3C)
|
||||
)
|
||||
Text(
|
||||
text = "Letzter Ping: Erfolgreich",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Durchschnitt: ~200ms",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyDesktopOnlyFeatures() {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFE1F5FE))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🖥️ Desktop Features",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = Color(0xFF0277BD)
|
||||
)
|
||||
Text(
|
||||
text = "• Datei-Export verfügbar",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "• System-Integration aktiv",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyWebOnlyFeatures() {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFFF3E0))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🌐 Web Features",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = Color(0xFFF57C00)
|
||||
)
|
||||
Text(
|
||||
text = "• PWA-Support verfügbar",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "• Browser-API Integration",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feature Control Panel - für Benutzer-Kontrolle über conditional loading
|
||||
*/
|
||||
@Composable
|
||||
fun FeatureControlPanel() {
|
||||
var showControls by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Button(
|
||||
onClick = { showControls = !showControls },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF424242))
|
||||
) {
|
||||
Text(
|
||||
if (showControls) "Feature-Kontrollen ausblenden" else "Feature-Kontrollen anzeigen",
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
}
|
||||
|
||||
if (showControls) {
|
||||
LazyFeatureControls()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyFeatureControls() {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🎛️ Feature Controls",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
if (ConditionalFeatures.isDebugModeEnabled()) {
|
||||
ConditionalFeatures.disableDebugMode()
|
||||
} else {
|
||||
ConditionalFeatures.enableDebugMode()
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (ConditionalFeatures.isDebugModeEnabled())
|
||||
Color(0xFF4CAF50) else Color(0xFF9E9E9E)
|
||||
)
|
||||
) {
|
||||
Text("Debug", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (ConditionalFeatures.isAdminModeEnabled()) {
|
||||
ConditionalFeatures.disableAdminMode()
|
||||
} else {
|
||||
ConditionalFeatures.enableAdminMode()
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (ConditionalFeatures.isAdminModeEnabled())
|
||||
Color(0xFF4CAF50) else Color(0xFF9E9E9E)
|
||||
)
|
||||
) {
|
||||
Text("Admin", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (ConditionalFeatures.areAdvancedFeaturesEnabled()) {
|
||||
ConditionalFeatures.disableAdvancedFeatures()
|
||||
} else {
|
||||
ConditionalFeatures.enableAdvancedFeatures()
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (ConditionalFeatures.areAdvancedFeaturesEnabled())
|
||||
Color(0xFF4CAF50) else Color(0xFF9E9E9E)
|
||||
)
|
||||
) {
|
||||
Text("Erweitert", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
package at.mocode.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import at.mocode.ApiConfig
|
||||
|
||||
@Serializable
|
||||
data class PingResponse(
|
||||
val status: String,
|
||||
val timestamp: String? = null,
|
||||
val message: String? = null
|
||||
)
|
||||
|
||||
sealed class PingState {
|
||||
object Idle : PingState()
|
||||
object Loading : PingState()
|
||||
data class Success(val response: PingResponse) : PingState()
|
||||
data class Error(val message: String) : PingState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy-loadable Ping Service Component
|
||||
* Encapsulates HTTP client, state management, and ping functionality
|
||||
* This component is only fully initialized when first used
|
||||
*/
|
||||
@Composable
|
||||
fun PingServiceComponent(
|
||||
modifier: Modifier = Modifier,
|
||||
onStateChange: (PingState) -> Unit = {}
|
||||
) {
|
||||
var pingState by remember { mutableStateOf<PingState>(PingState.Idle) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// Lazy HTTP client - only created when component is first composed
|
||||
val httpClient = remember {
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup client on disposal
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
httpClient.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Notify parent of state changes
|
||||
LaunchedEffect(pingState) {
|
||||
onStateChange(pingState)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Ping Backend Button
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pingState = PingState.Loading
|
||||
try {
|
||||
// Konfigurierbare API-URL basierend auf Deployment-Umgebung
|
||||
val response: PingResponse = httpClient.get(ApiConfig.pingEndpoint).body()
|
||||
pingState = PingState.Success(response)
|
||||
} catch (e: Exception) {
|
||||
pingState = PingState.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = pingState !is PingState.Loading,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
if (pingState is PingState.Loading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.height(16.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
Text("Ping Backend")
|
||||
}
|
||||
|
||||
// Status Display - conditionally rendered
|
||||
when (val state = pingState) {
|
||||
is PingState.Success -> {
|
||||
SuccessCard(
|
||||
response = state.response,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
is PingState.Error -> {
|
||||
ErrorCard(
|
||||
message = state.message,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Idle or Loading state - no additional display needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
package at.mocode.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.Greeting
|
||||
|
||||
/**
|
||||
* Lazy-loadable Platform Info Component
|
||||
* This component is only loaded when needed to reduce initial bundle size
|
||||
*/
|
||||
@Composable
|
||||
fun PlatformInfoComponent(
|
||||
showContent: Boolean,
|
||||
onToggle: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Platform Info Toggle Button
|
||||
Button(
|
||||
onClick = onToggle,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(if (showContent) "Platform-Info ausblenden" else "Platform-Info anzeigen")
|
||||
}
|
||||
|
||||
// Lazy-loaded content - only create Greeting when actually shown
|
||||
AnimatedVisibility(showContent) {
|
||||
LazyPlatformInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal composable that's only loaded when AnimatedVisibility is active
|
||||
*/
|
||||
@Composable
|
||||
private fun LazyPlatformInfo() {
|
||||
// This is only instantiated when showContent is true
|
||||
val greeting = remember { Greeting().greet() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = greeting,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Willkommen in der Meldestelle-Anwendung!",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
package at.mocode.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Lazy-loadable Success Card Component
|
||||
* Only loaded when a successful ping response is available
|
||||
*/
|
||||
@Composable
|
||||
fun SuccessCard(
|
||||
response: PingResponse,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "✅ Ping erfolgreich!",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color(0xFF4CAF50)
|
||||
)
|
||||
Text(
|
||||
text = "Status: ${response.status}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
response.timestamp?.let {
|
||||
Text(
|
||||
text = "Zeit: $it",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy-loadable Error Card Component
|
||||
* Only loaded when a ping error occurs
|
||||
*/
|
||||
@Composable
|
||||
fun ErrorCard(
|
||||
message: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "❌ Ping fehlgeschlagen",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color(0xFFF44336)
|
||||
)
|
||||
Text(
|
||||
text = "Fehler: $message",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
package at.mocode.http
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Optimized HTTP Client für minimale Bundle-Größe
|
||||
* Enthält nur die minimal notwendigen Features für Ping-Service
|
||||
*/
|
||||
object OptimizedHttpClient {
|
||||
|
||||
/**
|
||||
* Erstellt einen minimalen HTTP Client mit nur den notwendigen Features
|
||||
* - ContentNegotiation für JSON
|
||||
* - Minimale JSON-Konfiguration
|
||||
* - Keine unnötigen Plugins oder Features
|
||||
*/
|
||||
fun createMinimalClient(): HttpClient {
|
||||
return HttpClient {
|
||||
// Nur ContentNegotiation für JSON - keine anderen Plugins
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
// Minimale JSON-Konfiguration für kleinste Bundle-Größe
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = false
|
||||
encodeDefaults = false
|
||||
// Keine pretty printing für Production
|
||||
prettyPrint = false
|
||||
// Keine explicitNulls für kleinere Payloads
|
||||
explicitNulls = false
|
||||
})
|
||||
}
|
||||
|
||||
// Explizit keine anderen Features installieren:
|
||||
// - Kein Logging (spart Bundle-Größe)
|
||||
// - Kein DefaultRequest (nicht benötigt für einfachen Ping)
|
||||
// - Kein Timeout (Browser/Platform Default verwenden)
|
||||
// - Kein Auth (Ping-Service ist öffentlich)
|
||||
// - Keine Cookies (nicht benötigt)
|
||||
// - Keine Compression (nicht benötigt für kleine Payloads)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-optimierter Client (vereinfacht für alle Platforms)
|
||||
* Verwendet minimale Konfiguration für alle Targets
|
||||
*/
|
||||
fun createPlatformOptimizedClient(): HttpClient {
|
||||
return HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(createMinimalJson())
|
||||
}
|
||||
// Einheitliche Optimierungen für alle Platforms
|
||||
expectSuccess = false // Keine Exception bei HTTP-Errors (spart Bundle-Größe)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimale JSON-Konfiguration für kleinste Serialization-Overhead
|
||||
*/
|
||||
private fun createMinimalJson(): Json {
|
||||
return Json {
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = false
|
||||
encodeDefaults = false
|
||||
prettyPrint = false
|
||||
explicitNulls = false
|
||||
// Klassennamen nicht einbetten (spart Bytes)
|
||||
classDiscriminator = ""
|
||||
// Keine Polymorphie für einfache DTOs
|
||||
useAlternativeNames = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy HTTP Client Instance für optimale Performance
|
||||
* Erstellt den Client nur einmal bei erster Verwendung
|
||||
*/
|
||||
class LazyHttpClient {
|
||||
private var _client: HttpClient? = null
|
||||
|
||||
val client: HttpClient
|
||||
get() {
|
||||
if (_client == null) {
|
||||
_client = OptimizedHttpClient.createPlatformOptimizedClient()
|
||||
}
|
||||
return _client!!
|
||||
}
|
||||
|
||||
fun close() {
|
||||
_client?.close()
|
||||
_client = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Globale Singleton-Instanz für den optimierten HTTP Client
|
||||
* Minimiert Memory-Overhead und Bundle-Größe
|
||||
*/
|
||||
object GlobalHttpClient {
|
||||
private val lazyClient = LazyHttpClient()
|
||||
|
||||
/**
|
||||
* Zugriff auf den optimierten HTTP Client
|
||||
*/
|
||||
val client: HttpClient
|
||||
get() = lazyClient.client
|
||||
|
||||
/**
|
||||
* Client schließen bei App-Beendigung
|
||||
*/
|
||||
fun cleanup() {
|
||||
lazyClient.close()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package at.mocode
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class PingResponseSerializationTest {
|
||||
|
||||
@Test
|
||||
fun `should decode PingResponse with unknown fields and nulls omitted`() {
|
||||
val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = false
|
||||
encodeDefaults = false
|
||||
prettyPrint = false
|
||||
explicitNulls = false
|
||||
}
|
||||
|
||||
val input = """
|
||||
{
|
||||
"status": "OK",
|
||||
"timestamp": "2025-09-15T20:00:00Z",
|
||||
"message": null,
|
||||
"extra": 123,
|
||||
"nested": {"foo": "bar"}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val decoded = json.decodeFromString(PingResponse.serializer(), input)
|
||||
|
||||
assertEquals("OK", decoded.status)
|
||||
assertEquals("2025-09-15T20:00:00Z", decoded.timestamp)
|
||||
// message is nullable and nulls are omitted; ensure it's null when input is null
|
||||
assertEquals(null, decoded.message)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package at.mocode
|
||||
|
||||
actual object ApiConfig {
|
||||
actual val baseUrl: String = "" // Same-origin für Nginx-Proxy
|
||||
actual val pingEndpoint: String = "/api/ping"
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package at.mocode
|
||||
|
||||
class JSPlatform: Platform {
|
||||
override val name: String = "JavaScript"
|
||||
}
|
||||
|
||||
actual fun getPlatform(): Platform = JSPlatform()
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package at.mocode
|
||||
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.window.ComposeViewport
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun main() {
|
||||
ComposeViewport("ComposeTarget") {
|
||||
App()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package at.mocode
|
||||
|
||||
actual object ApiConfig {
|
||||
actual val baseUrl: String = System.getenv("API_BASE_URL") ?: "http://localhost:8081"
|
||||
actual val pingEndpoint: String = "$baseUrl/api/ping"
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package at.mocode
|
||||
|
||||
actual object ApiConfig {
|
||||
actual val baseUrl: String = "" // Same-origin für Nginx-Proxy
|
||||
actual val pingEndpoint: String = "/api/ping"
|
||||
}
|
||||
|
|
@ -8,13 +8,14 @@ kotlin.daemon.jvmargs=-Xmx3072M -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M
|
|||
|
||||
# Gradle Configuration
|
||||
org.gradle.jvmargs=-Xmx3072M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
# org.gradle.configureondemand=true # Deprecated - removed for Gradle 9.0 compatibility
|
||||
org.gradle.workers.max=8
|
||||
org.gradle.vfs.watch=true
|
||||
# Für bessere Performance
|
||||
org.gradle.configuration-cache=true
|
||||
|
||||
|
||||
# Browser für Tests konfigurieren - verwende Chrome mit Puppeteer
|
||||
#kotlin.js.browser.karma.useChromeHeadless=true
|
||||
|
|
|
|||
|
|
@ -2494,7 +2494,7 @@ source-map-loader@5.0.0:
|
|||
iconv-lite "^0.6.3"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
source-map-support@0.5.21, source-map-support@~0.5.20:
|
||||
source-map-support@~0.5.20:
|
||||
version "0.5.21"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
|
||||
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
|
||||
|
|
@ -2927,12 +2927,7 @@ wrappy@1:
|
|||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
ws@8.18.0:
|
||||
version "8.18.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
|
||||
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
|
||||
|
||||
ws@^8.13.0:
|
||||
ws@8.18.3, ws@^8.13.0:
|
||||
version "8.18.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
|
||||
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ include(":temp:ping-service")
|
|||
|
||||
// Client modules
|
||||
include(":client")
|
||||
include(":client:composeApp")
|
||||
include(":client:shared")
|
||||
|
||||
// Documentation module
|
||||
include(":docs")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user