fixing web-app

This commit is contained in:
stefan 2025-09-22 14:24:05 +02:00
parent f6f2f75c90
commit 232ba1f86d
9 changed files with 183 additions and 437 deletions

View File

@ -2,8 +2,8 @@
plugins { plugins {
alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.multiplatform) apply false alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.compose.multiplatform) apply false alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.spring.boot) apply false alias(libs.plugins.spring.boot) apply false
alias(libs.plugins.spring.dependencyManagement) apply false alias(libs.plugins.spring.dependencyManagement) apply false
} }
@ -28,6 +28,14 @@ subprojects {
// The agent configuration was causing Task.project access at execution time // The agent configuration was causing Task.project access at execution time
} }
// Erzwinge eine stabile Version von kotlinx-serialization-json für alle Konfigurationen,
// um Auflösungsfehler (z.B. 1.10.2, nicht verfügbar auf Maven Central) zu vermeiden
configurations.configureEach {
resolutionStrategy {
force("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}
}
// Dedicated performance test task per JVM subproject // Dedicated performance test task per JVM subproject
plugins.withId("java") { plugins.withId("java") {
val javaExt = extensions.getByType<JavaPluginExtension>() val javaExt = extensions.getByType<JavaPluginExtension>()

View File

@ -6,8 +6,9 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins { plugins {
alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.compose.multiplatform) alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.compose.compiler) alias(libs.plugins.composeCompiler)
alias(libs.plugins.composeHotReload)
} }
// Project version configuration // Project version configuration
@ -15,126 +16,139 @@ version = "1.0.0"
group = "at.mocode" group = "at.mocode"
// Build performance optimizations // Build performance optimizations
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach { //tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach {
compilerOptions { // compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) // jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
freeCompilerArgs.addAll( // freeCompilerArgs.addAll(
"-opt-in=kotlin.RequiresOptIn", // "-opt-in=kotlin.RequiresOptIn",
"-Xjvm-default=all" // Generate default methods for interfaces (JVM performance) // "-Xjvm-default=all" // Generate default methods for interfaces (JVM performance)
) // )
} // }
} //}
kotlin { kotlin {
// Configure JVM toolchain for all JVM targets // // Configure JVM toolchain for all JVM targets
jvmToolchain(21) // jvmToolchain(21)
//
// // Global compiler options for all targets
// compilerOptions {
// freeCompilerArgs.add("-Xexpect-actual-classes")
// }
// Global compiler options for all targets // jvm {
compilerOptions { // compilations.all {
freeCompilerArgs.add("-Xexpect-actual-classes") // compileTaskProvider.configure {
} // compilerOptions {
// jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
// freeCompilerArgs.addAll(
// "-Xjsr305=strict",
// "-Xcontext-parameters"
// )
// }
// }
// }
// }
jvm { // js(IR) {
compilations.all { // browser {
compileTaskProvider.configure { // commonWebpackConfig {
compilerOptions { // outputFileName = "meldestelle-client.js"
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) // cssSupport {
freeCompilerArgs.addAll( // enabled.set(true)
"-Xjsr305=strict", // }
"-Xcontext-parameters" // // 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()
// }
js(IR) { // wasmJs {
browser { // browser {
commonWebpackConfig { // commonWebpackConfig {
outputFileName = "meldestelle-client.js" // outputFileName = "meldestelle-wasm.js"
cssSupport { // cssSupport {
enabled.set(true) // enabled.set(true)
} // }
// Webpack performance optimizations for smaller bundles // // WASM-specific webpack optimizations handled by webpack.config.d files
devServer?.apply { // devServer?.apply {
open = false // open = false
port = 8080 // port = 8080
} // }
} // }
runTask { // runTask {
// Development optimizations // // Development optimizations for WASM
args.add("--mode=development") // args.add("--mode=development")
//args.add("--optimization-minimize=false") // //args.add("--optimization-minimize=false")
} // // Dev server settings handled by webpack.config.d/dev-server.js
webpackTask { // }
// Production optimizations // webpackTask {
args.add("--mode=production") // // Production optimizations for WASM
args.add("--optimization-minimize") // args.add("--mode=production")
} // args.add("--optimization-minimize")
testTask { // }
// Disable browser tests due to ChromeHeadless permission issues // testTask {
enabled = false // // 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()
// }
// Use Node.js for testing instead of browser jvm()
nodejs {
testTask {
useMocha {
timeout = "10s"
}
}
}
js {
browser()
binaries.executable() binaries.executable()
} }
@OptIn(ExperimentalWasmDsl::class)
wasmJs { wasmJs {
browser { 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() binaries.executable()
} }
@ -149,6 +163,10 @@ kotlin {
// UiToolingPreview nur für Development, nicht für Production WASM // UiToolingPreview nur für Development, nicht für Production WASM
// implementation(compose.components.uiToolingPreview) // 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 // HTTP client dependencies for ping-service - optimiert
implementation(libs.ktor.client.core) implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentNegotiation) implementation(libs.ktor.client.contentNegotiation)
@ -161,6 +179,7 @@ kotlin {
} }
jvmMain.dependencies { jvmMain.dependencies {
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.ktor.client.cio) implementation(libs.ktor.client.cio)
} }
jsMain.dependencies { jsMain.dependencies {
@ -180,29 +199,29 @@ kotlin {
// Exclude Skiko runtime files from jsTest processed resources // Exclude Skiko runtime files from jsTest processed resources
// to prevent overwriting logs during test packaging. // to prevent overwriting logs during test packaging.
@Suppress("UNUSED_VARIABLE") //@Suppress("UNUSED_VARIABLE")
val configureJsTestResources = run { //val configureJsTestResources = run {
// Configure only if the task exists (JS target present) // // Configure only if the task exists (JS target present)
tasks.matching { it.name == "jsTestProcessResources" && it is Copy }.configureEach { // tasks.matching { it.name == "jsTestProcessResources" && it is Copy }.configureEach {
(this as Copy).exclude("skiko.*", "skikod8.mjs") // (this as Copy).exclude("skiko.*", "skikod8.mjs")
} // }
} //}
//
// Also apply the same exclusion for WASM JS test resources, if present //// Also apply the same exclusion for WASM JS test resources, if present
@Suppress("UNUSED_VARIABLE") //@Suppress("UNUSED_VARIABLE")
val configureWasmJsTestResources = run { //val configureWasmJsTestResources = run {
tasks.matching { it.name == "wasmJsTestProcessResources" && it is Copy }.configureEach { // tasks.matching { it.name == "wasmJsTestProcessResources" && it is Copy }.configureEach {
(this as Copy).exclude("skiko.*", "skikod8.mjs") // (this as Copy).exclude("skiko.*", "skikod8.mjs")
} // }
} //}
//
// Ensure Kotlin/JS generated Sync tasks do not overwrite duplicates noisily //// Ensure Kotlin/JS generated Sync tasks do not overwrite duplicates noisily
@Suppress("UNUSED_VARIABLE") //@Suppress("UNUSED_VARIABLE")
val configureJsCompileSync = run { //val configureJsCompileSync = run {
tasks.matching { it.name.endsWith("CompileSync") && it is Sync }.configureEach { // tasks.matching { it.name.endsWith("CompileSync") && it is Sync }.configureEach {
(this as Sync).duplicatesStrategy = DuplicatesStrategy.EXCLUDE // (this as Sync).duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} // }
} //}
compose.desktop { compose.desktop {
application { application {

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meldestelle</title> <title>Meldestelle</title>
<link type="text/css" rel="stylesheet" href="styles.css"> <link type="text/css" rel="stylesheet" href="styles.css">
<script type="application/javascript" src="composeApp.js"></script>
</head> </head>
<body> <body>
<canvas id="ComposeTarget"></canvas> <canvas id="ComposeTarget"></canvas>

View File

@ -1,11 +1,11 @@
package at.mocode package at.mocode
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.CanvasBasedWindow import androidx.compose.ui.window.ComposeViewport
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
fun main() { fun main() {
CanvasBasedWindow(canvasElementId = "ComposeTarget") { ComposeViewport("ComposeTarget") {
App() App()
} }
} }

View File

@ -1,11 +1,11 @@
package at.mocode package at.mocode
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.CanvasBasedWindow import androidx.compose.ui.window.ComposeViewport
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
fun main() { fun main() {
CanvasBasedWindow(canvasElementId = "ComposeTarget") { ComposeViewport("ComposeTarget") {
App() App()
} }
} }

View File

@ -1,122 +0,0 @@
// Bundle Analyzer Configuration for WASM Bundle Size Monitoring
// Helps identify which parts of the bundle are largest and can be optimized
// Enable bundle analysis based on environment variable
const enableAnalyzer = process.env.ANALYZE_BUNDLE === 'true';
// Ensure mutable config sections exist to avoid spread on undefined
config.plugins = config.plugins || [];
config.resolve = config.resolve || {};
config.module = config.module || {};
if (enableAnalyzer) {
console.log('📊 Bundle analyzer enabled - generating bundle report...');
// Simple bundle size logging without external dependencies
const originalEmit = config.plugins.find(plugin => plugin.constructor.name === 'DefinePlugin');
// Add a custom plugin to log bundle sizes
config.plugins.push({
apply: (compiler) => {
compiler.hooks.done.tap('BundleSizeLogger', (stats) => {
const json = stats.toJson({ all: false, assets: true });
const assets = (json && json.assets) ? json.assets : [];
console.log('\n📦 WASM Bundle Analysis Report:');
console.log('=====================================');
// Sort assets by size (largest first)
const sortedAssets = assets
.filter(asset => !asset.name.endsWith('.map'))
.sort((a, b) => b.size - a.size);
let totalSize = 0;
sortedAssets.forEach(asset => {
const sizeKB = (asset.size / 1024).toFixed(2);
const sizeMB = (asset.size / (1024 * 1024)).toFixed(2);
totalSize += asset.size;
console.log(`📄 ${asset.name}:`);
console.log(` Size: ${sizeKB} KB (${sizeMB} MB)`);
// Identify what type of asset this likely is
if (asset.name.includes('skiko')) {
console.log(' Type: 🎨 Skiko (Compose UI Framework)');
} else if (asset.name.includes('ktor')) {
console.log(' Type: 🌐 Ktor (HTTP Client)');
} else if (asset.name.includes('kotlin')) {
console.log(' Type: 📚 Kotlin Standard Library');
} else if (asset.name.includes('wasm')) {
console.log(' Type: ⚡ WebAssembly Binary');
} else if (asset.name.includes('meldestelle')) {
console.log(' Type: 🏠 Application Code');
} else {
console.log(' Type: 📦 Other/Vendor');
}
console.log('');
});
const totalSizeKB = (totalSize / 1024).toFixed(2);
const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
console.log(`📊 Total Bundle Size: ${totalSizeKB} KB (${totalSizeMB} MB)`);
console.log('=====================================');
// Provide optimization recommendations
const wasmAsset = sortedAssets.find(asset => asset.name.includes('.wasm'));
const jsAsset = sortedAssets.find(asset => asset.name.includes('meldestelle-wasm.js'));
if (wasmAsset && jsAsset) {
const wasmSizeMB = (wasmAsset.size / (1024 * 1024)).toFixed(2);
const jsSizeKB = (jsAsset.size / 1024).toFixed(2);
console.log('\n💡 Optimization Recommendations:');
console.log('=====================================');
if (wasmAsset.size > 5 * 1024 * 1024) { // > 5MB
console.log(`⚠️ WASM binary is large (${wasmSizeMB}MB). Consider:`);
console.log(' - Reducing Compose UI components');
console.log(' - Lazy loading features');
console.log(' - Tree-shaking unused dependencies');
}
if (jsAsset.size > 500 * 1024) { // > 500KB
console.log(`⚠️ JS bundle is large (${jsSizeKB}KB). Consider:`);
console.log(' - Code splitting');
console.log(' - Dynamic imports');
console.log(' - Removing unused imports');
}
if (sortedAssets.length > 10) {
console.log('✅ Good chunk splitting - multiple small files for better caching');
}
}
console.log('\n🎯 To analyze specific chunks, set ANALYZE_BUNDLE=true and rebuild');
console.log('=====================================\n');
});
}
});
}
// Additional tree-shaking optimizations
config.resolve = {
...(config.resolve || {}),
// Prioritize ES6 modules for better tree-shaking
mainFields: ['module', 'browser', 'main'],
// Add extensions for better resolution
extensions: ['.js', '.mjs', '.wasm', '.json']
};
// Mark packages as side-effect-free for better tree-shaking
config.module = {
...(config.module || {}),
rules: [
...(config.module && config.module.rules ? config.module.rules : []),
{
// Mark Kotlin-generated code as side-effect-free where possible
test: /\.js$/,
include: /kotlin/,
sideEffects: false
}
]
};

View File

@ -1,48 +0,0 @@
// Development server configuration with API proxy
// This forwards API requests from webpack-dev-server to the gateway
const path = require('path');
if (config.mode !== 'production') {
config.devServer = {
...config.devServer,
// Proxy API requests to the gateway - using modern object syntax
proxy: {
'/api/**': {
target: 'http://localhost:8081',
changeOrigin: true,
secure: false,
logLevel: 'debug',
pathRewrite: {
'^/api': '/api' // Keep the /api prefix for gateway routing
}
}
},
// Disable all caches as requested in previous issue
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
},
// Development middleware settings
devMiddleware: {
writeToDisk: false,
stats: 'minimal'
},
// Static files configuration
/* static: {
directory: path.resolve(__dirname, '../../build/dist/wasmJs/developmentExecutable'),
serveIndex: true,
watch: true
},*/
// CORS settings for development
allowedHosts: 'all',
historyApiFallback: true,
hot: true,
liveReload: true
};
}

View File

@ -1,122 +0,0 @@
// WASM Bundle Size Optimization Configuration
// Advanced Webpack configuration for smaller WASM bundles
//const path = require('path');
// Bundle size optimization configuration
config.optimization = {
...(config.optimization || {}),
// Enable aggressive tree shaking
usedExports: true,
sideEffects: true,
// Split chunks for better caching and smaller initial bundle
splitChunks: {
chunks: 'all',
cacheGroups: {
// Separate Skiko (Compose UI) into its own chunk
skiko: {
test: /[\\/]skiko[\\/]/,
name: 'skiko',
chunks: 'all',
priority: 30,
reuseExistingChunk: true,
enforce: true
},
// Separate Ktor client into its own chunk
ktor: {
test: /[\\/]ktor[\\/]/,
name: 'ktor',
chunks: 'all',
priority: 20,
reuseExistingChunk: true
},
// Separate Kotlin stdlib into its own chunk
kotlinStdlib: {
test: /[\\/]kotlin[\\/]/,
name: 'kotlin-stdlib',
chunks: 'all',
priority: 15,
reuseExistingChunk: true
},
// Default vendor chunk for remaining dependencies
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
reuseExistingChunk: true
},
// Application code chunk
default: {
name: 'app',
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
// Minimize bundle size - conditional based on mode
minimize: config.mode === 'production'
// Note: minimizer is automatically configured by Kotlin/JS
};
// Performance optimization
config.performance = {
...(config.performance || {}),
// Realistic hint limits for WASM bundles (which are naturally larger)
maxAssetSize: 20000000, // 20MB for individual assets (WASM files can be large)
maxEntrypointSize: 5000000, // 5MB for entrypoints
hints: 'warning' // Show warnings but don't fail the build
};
// Resolve optimization for faster builds
config.resolve = {
...(config.resolve || {}),
// Skip looking in these directories to speed up resolution
modules: ['node_modules'],
// Cache module resolution
cache: true
};
// Module optimization
config.module = {
...(config.module || {}),
// Disable parsing for known pre-built modules
noParse: [
/kotlin\.js$/,
/kotlinx-.*\.js$/
]
};
// Development vs Production optimizations
if (config.mode === 'production') {
// Production-specific optimizations
config.output = {
...(config.output || {}),
// Use conditional filename to match HTML template expectations for main chunk only
filename: (chunkData) => {
return chunkData.chunk.name === 'main' ? 'meldestelle-wasm.js' : '[name].[contenthash:8].js';
},
chunkFilename: '[name].[contenthash:8].chunk.js'
};
// Additional production optimizations
config.optimization = {
...(config.optimization || {}),
// Enable module concatenation (scope hoisting)
concatenateModules: true,
// Remove empty chunks
removeEmptyChunks: true,
// Merge duplicate chunks
mergeDuplicateChunks: true
};
} else {
// Development optimizations for faster builds
config.cache = {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
};
}

View File

@ -9,6 +9,7 @@ agp = "8.1.4"
# --- Kotlin Ecosystem --- # --- Kotlin Ecosystem ---
kotlin = "2.2.10" kotlin = "2.2.10"
kotlinx = "1.9.0" kotlinx = "1.9.0"
kotlinxSerializationJson = "1.7.3"
kotlinxDatetime = "0.7.1" kotlinxDatetime = "0.7.1"
kotlinLogging = "7.0.3" kotlinLogging = "7.0.3"
@ -19,10 +20,12 @@ springDependencyManagement = "1.1.7"
springdoc = "2.8.11" springdoc = "2.8.11"
# --- Ktor (API Layer & Client) --- # --- Ktor (API Layer & Client) ---
ktor = "3.2.3" ktor = "3.3.0"
# --- Compose UI --- # --- Compose UI ---
composeMultiplatform = "1.8.2" androidx-lifecycle = "2.9.4"
composeHotReload = "1.0.0-beta07"
composeMultiplatform = "1.9.0"
# --- Database & Persistence --- # --- Database & Persistence ---
@ -78,7 +81,7 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" }
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing" } # Version from BOM kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing" } # Version from BOM
kotlinx-coroutines-reactor = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor" } # Version from BOM kotlinx-coroutines-reactor = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor" } # Version from BOM
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx" }
@ -98,6 +101,7 @@ ktor-server-metrics-micrometer = { module = "io.ktor:ktor-server-metrics-microme
ktor-server-openapi = { module = "io.ktor:ktor-server-openapi", version.ref = "ktor" } ktor-server-openapi = { module = "io.ktor:ktor-server-openapi", version.ref = "ktor" }
ktor-server-swagger = { module = "io.ktor:ktor-server-swagger", version.ref = "ktor" } ktor-server-swagger = { module = "io.ktor:ktor-server-swagger", version.ref = "ktor" }
ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" } ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" }
ktor-server-testHost = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor" }
# --- Ktor Client --- # --- Ktor Client ---
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
@ -171,6 +175,9 @@ jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-dat
jakarta-annotation-api = { module = "jakarta.annotation:jakarta.annotation-api", version.ref = "jakartaAnnotation" } jakarta-annotation-api = { module = "jakarta.annotation:jakarta.annotation-api", version.ref = "jakartaAnnotation" }
# --- Compose UI --- # --- Compose UI ---
androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" } compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" }
compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" } compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" }
compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "composeMultiplatform" } compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "composeMultiplatform" }
@ -288,7 +295,10 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
kotlin-jpa = { id = "org.jetbrains.kotlin.plugin.jpa", version.ref = "kotlin" } kotlin-jpa = { id = "org.jetbrains.kotlin.plugin.jpa", version.ref = "kotlin" }
kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }
ktor = { id = "io.ktor.plugin", version.ref = "ktor" } ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
spring-boot = { id = "org.springframework.boot", version.ref = "springBoot" } spring-boot = { id = "org.springframework.boot", version.ref = "springBoot" }
spring-dependencyManagement = { id = "io.spring.dependency-management", version.ref = "springDependencyManagement" } spring-dependencyManagement = { id = "io.spring.dependency-management", version.ref = "springDependencyManagement" }