refactor(page): 删除旧版页面文件
This commit is contained in:
@@ -0,0 +1,478 @@
|
||||
# Full Restructure: Extracting All Platform Entry Points
|
||||
|
||||
This guide covers the complete extraction of platform-specific entry points from a monolithic `composeApp` module into dedicated per-platform application modules. This is the most thorough migration path and results in a clean architecture where `shared` contains only cross-platform code.
|
||||
|
||||
---
|
||||
|
||||
## Target Architecture
|
||||
|
||||
```
|
||||
shared/ # KMP library (all shared code)
|
||||
build.gradle.kts # kotlin.multiplatform + com.android.kotlin.multiplatform.library
|
||||
src/
|
||||
commonMain/kotlin/ # Shared business logic + UI
|
||||
androidMain/kotlin/ # Android expect/actual implementations
|
||||
iosMain/kotlin/ # iOS expect/actual implementations
|
||||
|
||||
androidApp/ # Android application entry point
|
||||
build.gradle.kts # com.android.application
|
||||
src/main/
|
||||
|
||||
desktopApp/ # Desktop (JVM) application entry point
|
||||
build.gradle.kts # org.jetbrains.compose + application {}
|
||||
src/main/kotlin/
|
||||
|
||||
webApp/ # Wasm/JS web application entry point
|
||||
build.gradle.kts # kotlin.multiplatform + wasmJs target
|
||||
src/wasmJsMain/kotlin/
|
||||
|
||||
iosApp/ # iOS application (Xcode project, usually already separate)
|
||||
iosApp.xcodeproj/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Desktop Extraction
|
||||
|
||||
### Create desktopApp/build.gradle.kts
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinJvm)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":shared"))
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3)
|
||||
}
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "com.example.app.MainKt"
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(
|
||||
org.jetbrains.compose.desktop.application.dsl.TargetFormat.Dmg,
|
||||
org.jetbrains.compose.desktop.application.dsl.TargetFormat.Msi,
|
||||
org.jetbrains.compose.desktop.application.dsl.TargetFormat.Deb
|
||||
)
|
||||
packageName = "com.example.app"
|
||||
packageVersion = "1.0.0"
|
||||
|
||||
macOS {
|
||||
iconFile.set(project.file("icons/icon.icns"))
|
||||
}
|
||||
windows {
|
||||
iconFile.set(project.file("icons/icon.ico"))
|
||||
}
|
||||
linux {
|
||||
iconFile.set(project.file("icons/icon.png"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Move Desktop Entry Point
|
||||
|
||||
```
|
||||
composeApp/src/desktopMain/kotlin/com/example/app/main.kt
|
||||
--> desktopApp/src/main/kotlin/com/example/app/main.kt
|
||||
```
|
||||
|
||||
Update to call shared code:
|
||||
|
||||
```kotlin
|
||||
// desktopApp/src/main/kotlin/com/example/app/main.kt
|
||||
package com.example.app
|
||||
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import com.example.shared.App
|
||||
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "My App"
|
||||
) {
|
||||
App()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Remove Desktop from shared
|
||||
|
||||
In `shared/build.gradle.kts`, remove the `jvm("desktop")` target entirely. The desktop target only needs to exist in `desktopApp`.
|
||||
|
||||
**Before (in composeApp):**
|
||||
```kotlin
|
||||
kotlin {
|
||||
jvm("desktop")
|
||||
// ...
|
||||
sourceSets {
|
||||
val desktopMain by getting {
|
||||
dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "com.example.app.MainKt"
|
||||
nativeDistributions { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After (in shared):**
|
||||
```kotlin
|
||||
kotlin {
|
||||
// jvm("desktop") -- REMOVED
|
||||
// No desktop target in shared module
|
||||
// No compose.desktop block
|
||||
}
|
||||
```
|
||||
|
||||
If you have shared JVM code that both Android and Desktop use, you have two options:
|
||||
1. Keep a `jvm()` target in shared (without the `application {}` block) and use intermediate source sets.
|
||||
2. Put all shared code in `commonMain` and rely on the JVM dependency from `desktopApp`.
|
||||
|
||||
---
|
||||
|
||||
## Web/WasmJS Extraction
|
||||
|
||||
### Create webApp/build.gradle.kts
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
wasmJs {
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
outputFileName = "app.js"
|
||||
}
|
||||
}
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
wasmJsMain.dependencies {
|
||||
implementation(project(":shared"))
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.ui)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Move Web Entry Point
|
||||
|
||||
```
|
||||
composeApp/src/wasmJsMain/kotlin/com/example/app/main.kt
|
||||
--> webApp/src/wasmJsMain/kotlin/com/example/app/main.kt
|
||||
```
|
||||
|
||||
Update to call shared code:
|
||||
|
||||
```kotlin
|
||||
// webApp/src/wasmJsMain/kotlin/com/example/app/main.kt
|
||||
package com.example.app
|
||||
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.window.CanvasBasedWindow
|
||||
import com.example.shared.App
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun main() {
|
||||
CanvasBasedWindow(canvasElementId = "ComposeTarget") {
|
||||
App()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Move Web Resources
|
||||
|
||||
```
|
||||
composeApp/src/wasmJsMain/resources/index.html
|
||||
--> webApp/src/wasmJsMain/resources/index.html
|
||||
```
|
||||
|
||||
Update `index.html` if the output JS filename changed.
|
||||
|
||||
### Remove WasmJS from shared
|
||||
|
||||
In `shared/build.gradle.kts`, remove the `wasmJs {}` target:
|
||||
|
||||
```kotlin
|
||||
kotlin {
|
||||
// wasmJs { ... } -- REMOVED
|
||||
}
|
||||
```
|
||||
|
||||
If you need shared Wasm-compatible code, keep `wasmJs()` in shared as a library target (no `binaries.executable()`, no `browser {}` config).
|
||||
|
||||
---
|
||||
|
||||
## iOS Handling
|
||||
|
||||
iOS is typically already a separate Xcode project. The main considerations during restructure:
|
||||
|
||||
### Framework Export Stays in shared
|
||||
|
||||
```kotlin
|
||||
// shared/build.gradle.kts
|
||||
kotlin {
|
||||
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
|
||||
it.binaries.framework {
|
||||
baseName = "Shared" // Update if renamed from "ComposeApp"
|
||||
isStatic = true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Update Xcode Project
|
||||
|
||||
If the module was renamed from `composeApp` to `shared`:
|
||||
|
||||
1. **Framework import:** Change `import ComposeApp` to `import Shared` in all `.swift` files (must match `baseName` in the framework config).
|
||||
|
||||
2. **Gradle task path:** Update the Run Script build phase in `project.pbxproj` (or via Xcode > Build Phases):
|
||||
```bash
|
||||
# In Xcode Build Phases > Run Script
|
||||
cd "$SRCROOT/.."
|
||||
./gradlew :shared:embedAndSignAppleFrameworkForXcode
|
||||
```
|
||||
|
||||
3. **App struct name:** If the SwiftUI `@main` struct was named after the old module (e.g., `ComposeAppApp`), rename it to something appropriate for your project.
|
||||
|
||||
4. **Framework search paths:** Update Build Settings if they reference the old module directory path.
|
||||
|
||||
5. **Cocoapods (if used):** Update the pod spec name:
|
||||
```kotlin
|
||||
// shared/build.gradle.kts
|
||||
kotlin {
|
||||
cocoapods {
|
||||
name = "Shared"
|
||||
summary = "Shared KMP module"
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Rename: composeApp to shared
|
||||
|
||||
### 1. Rename the Directory
|
||||
|
||||
```bash
|
||||
mv composeApp shared
|
||||
```
|
||||
|
||||
### 2. Update settings.gradle.kts
|
||||
|
||||
```kotlin
|
||||
// Before
|
||||
include(":composeApp")
|
||||
|
||||
// After
|
||||
include(":shared")
|
||||
include(":androidApp")
|
||||
include(":desktopApp")
|
||||
include(":webApp")
|
||||
```
|
||||
|
||||
### 3. Update Cross-Module Dependencies
|
||||
|
||||
Search all `build.gradle.kts` files for references to `:composeApp`:
|
||||
|
||||
```kotlin
|
||||
// Before
|
||||
implementation(project(":composeApp"))
|
||||
|
||||
// After
|
||||
implementation(project(":shared"))
|
||||
```
|
||||
|
||||
### 4. Update .idea / Workspace Files
|
||||
|
||||
If using IntelliJ/Android Studio, the IDE may cache the old module name. Either:
|
||||
- Delete `.idea/` and re-import
|
||||
- Or manually update `.idea/modules.xml` and related files
|
||||
|
||||
---
|
||||
|
||||
## Variant: Native UI (sharedLogic + sharedUI Split)
|
||||
|
||||
For projects where each platform has its own native UI and only business logic is shared:
|
||||
|
||||
```
|
||||
sharedLogic/ # Pure KMP library (no Compose)
|
||||
build.gradle.kts # kotlin.multiplatform + com.android.kotlin.multiplatform.library
|
||||
src/
|
||||
commonMain/kotlin/ # ViewModels, repositories, models, networking
|
||||
androidMain/kotlin/ # Android-specific implementations
|
||||
iosMain/kotlin/ # iOS-specific implementations
|
||||
|
||||
sharedUI/ # Optional: Compose Multiplatform UI
|
||||
build.gradle.kts # kotlin.multiplatform + com.android.kotlin.multiplatform.library + compose
|
||||
src/
|
||||
commonMain/kotlin/ # Shared composables
|
||||
androidMain/kotlin/ # Android-specific composables
|
||||
|
||||
androidApp/ # Native Android app
|
||||
build.gradle.kts
|
||||
src/main/ # Android UI (Compose or XML), depends on sharedLogic (and optionally sharedUI)
|
||||
|
||||
iosApp/ # Native iOS app (SwiftUI/UIKit)
|
||||
# Depends on sharedLogic framework
|
||||
```
|
||||
|
||||
### sharedLogic/build.gradle.kts
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.androidKmpLibrary)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
android {
|
||||
namespace = "com.example.shared.logic"
|
||||
compileSdk = 35
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
|
||||
it.binaries.framework {
|
||||
baseName = "SharedLogic"
|
||||
isStatic = true
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This variant is useful when:
|
||||
- iOS uses SwiftUI and does not want Compose Multiplatform
|
||||
- Desktop is not a target
|
||||
- You want to minimize the shared surface area
|
||||
|
||||
---
|
||||
|
||||
## Variant: Server (Backend Module)
|
||||
|
||||
For projects that include a Ktor/Spring server:
|
||||
|
||||
```
|
||||
shared/ # KMP library (shared models, API contracts)
|
||||
androidApp/
|
||||
iosApp/
|
||||
server/ # JVM server application
|
||||
build.gradle.kts # kotlin("jvm") + ktor/spring plugin
|
||||
src/main/kotlin/
|
||||
```
|
||||
|
||||
### server/build.gradle.kts
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinJvm)
|
||||
alias(libs.plugins.ktor) // or spring boot
|
||||
application
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("com.example.server.ApplicationKt")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":shared"))
|
||||
implementation(libs.ktor.server.core)
|
||||
implementation(libs.ktor.server.netty)
|
||||
implementation(libs.logback.classic)
|
||||
}
|
||||
```
|
||||
|
||||
The server module is a plain JVM module. It depends on `:shared` for common models and API contracts. It is unaffected by the AGP 9.0 migration except that:
|
||||
- If shared previously had a `jvm()` target that the server depended on, verify it still exists after restructuring.
|
||||
- If shared was renamed, update the dependency path.
|
||||
|
||||
---
|
||||
|
||||
## settings.gradle.kts -- Final State
|
||||
|
||||
```kotlin
|
||||
rootProject.name = "MyProject"
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google {
|
||||
content {
|
||||
includeGroupByRegex("com\\.android.*")
|
||||
includeGroupByRegex("com\\.google.*")
|
||||
includeGroupByRegex("androidx.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
include(":shared")
|
||||
include(":androidApp")
|
||||
include(":desktopApp")
|
||||
include(":webApp")
|
||||
// include(":server") // if applicable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Checklist
|
||||
|
||||
- [ ] Create `androidApp/` with pure Android application plugin (see MIGRATION-APP-SPLIT.md)
|
||||
- [ ] Create `desktopApp/` with compose desktop plugin and `application {}` block
|
||||
- [ ] Create `webApp/` with wasmJs target and `binaries.executable()`
|
||||
- [ ] Move `main()` functions from `composeApp/src/{platform}Main/` to respective app modules
|
||||
- [ ] Move `compose.desktop.application {}` config to `desktopApp`
|
||||
- [ ] Move `wasmJs { browser {} }` config to `webApp`
|
||||
- [ ] Rename `composeApp` to `shared`
|
||||
- [ ] Convert shared to KMP library plugin (`com.android.kotlin.multiplatform.library`)
|
||||
- [ ] Remove platform app targets from shared (keep only library targets)
|
||||
- [ ] Update all `settings.gradle.kts` includes
|
||||
- [ ] Update all `project(":composeApp")` references to `project(":shared")`
|
||||
- [ ] Update Xcode project (framework name, Gradle task path, Swift imports)
|
||||
- [ ] Verify each app module builds independently
|
||||
- [ ] Run all platform targets to confirm functionality
|
||||
Reference in New Issue
Block a user