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 {
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.compose.multiplatform) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.spring.boot) 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
}
// 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
plugins.withId("java") {
val javaExt = extensions.getByType<JavaPluginExtension>()

View File

@ -6,8 +6,9 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.compose.multiplatform)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.composeHotReload)
}
// Project version configuration
@ -15,126 +16,139 @@ 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)
)
}
}
//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)
// // Configure JVM toolchain for all JVM targets
// jvmToolchain(21)
//
// // Global compiler options for all targets
// compilerOptions {
// freeCompilerArgs.add("-Xexpect-actual-classes")
// }
// 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"
// )
// }
// }
// }
// }
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()
// }
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
}
}
// 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()
// }
// Use Node.js for testing instead of browser
nodejs {
testTask {
useMocha {
timeout = "10s"
}
}
}
jvm()
js {
browser()
binaries.executable()
}
@OptIn(ExperimentalWasmDsl::class)
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"
)
}
}
}
browser()
binaries.executable()
}
@ -149,6 +163,10 @@ kotlin {
// 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)
@ -161,6 +179,7 @@ kotlin {
}
jvmMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.ktor.client.cio)
}
jsMain.dependencies {
@ -180,29 +199,29 @@ kotlin {
// 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
}
}
//@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 {

View File

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

View File

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

View File

@ -1,11 +1,11 @@
package at.mocode
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.CanvasBasedWindow
import androidx.compose.ui.window.ComposeViewport
@OptIn(ExperimentalComposeUiApi::class)
fun main() {
CanvasBasedWindow(canvasElementId = "ComposeTarget") {
ComposeViewport("ComposeTarget") {
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 = "2.2.10"
kotlinx = "1.9.0"
kotlinxSerializationJson = "1.7.3"
kotlinxDatetime = "0.7.1"
kotlinLogging = "7.0.3"
@ -19,10 +20,12 @@ springDependencyManagement = "1.1.7"
springdoc = "2.8.11"
# --- Ktor (API Layer & Client) ---
ktor = "3.2.3"
ktor = "3.3.0"
# --- Compose UI ---
composeMultiplatform = "1.8.2"
androidx-lifecycle = "2.9.4"
composeHotReload = "1.0.0-beta07"
composeMultiplatform = "1.9.0"
# --- 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-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-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-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-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-testHost = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor" }
# --- Ktor Client ---
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" }
# --- 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-foundation = { module = "org.jetbrains.compose.foundation:foundation", 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-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }
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-dependencyManagement = { id = "io.spring.dependency-management", version.ref = "springDependencyManagement" }